susumu.yata
null+****@clear*****
Mon Apr 22 21:40:59 JST 2013
susumu.yata 2013-04-22 21:40:59 +0900 (Mon, 22 Apr 2013) New Revision: c046b9118a96114110d2597fdc199a10e908e7b1 https://github.com/groonga/grnxx/commit/c046b9118a96114110d2597fdc199a10e908e7b1 Message: Add modules for grnxx::Storage. Added files: lib/grnxx/storage/Makefile.am lib/grnxx/storage/file-posix.cpp lib/grnxx/storage/file-posix.hpp lib/grnxx/storage/file-windows.cpp lib/grnxx/storage/file-windows.hpp lib/grnxx/storage/file.cpp lib/grnxx/storage/file.hpp lib/grnxx/storage/path.cpp lib/grnxx/storage/path.hpp lib/grnxx/storage/view-posix.cpp lib/grnxx/storage/view-posix.hpp lib/grnxx/storage/view-windows.cpp lib/grnxx/storage/view-windows.hpp lib/grnxx/storage/view.cpp lib/grnxx/storage/view.hpp test/test_storage.cpp Modified files: .gitignore configure.ac lib/grnxx/Makefile.am lib/grnxx/storage.cpp lib/grnxx/storage.hpp test/Makefile.am Modified: .gitignore (+1 -0) =================================================================== --- .gitignore 2013-04-22 21:29:49 +0900 (030950b) +++ .gitignore 2013-04-22 21:40:59 +0900 (1af17e3) @@ -53,6 +53,7 @@ test/test_mutex test/test_os test/test_recycler test/test_slice +test/test_storage test/test_string test/test_string_builder test/test_string_format Modified: configure.ac (+1 -0) =================================================================== --- configure.ac 2013-04-22 21:29:49 +0900 (278f6bb) +++ configure.ac 2013-04-22 21:40:59 +0900 (7c774b0) @@ -70,6 +70,7 @@ AC_CONFIG_FILES([Makefile lib/grnxx/map/da/Makefile lib/grnxx/map/da/basic/Makefile lib/grnxx/map/da/large/Makefile + lib/grnxx/storage/Makefile lib/grnxx/time/Makefile src/Makefile test/Makefile]) Modified: lib/grnxx/Makefile.am (+9 -1) =================================================================== --- lib/grnxx/Makefile.am 2013-04-22 21:29:49 +0900 (0f3dea6) +++ lib/grnxx/Makefile.am 2013-04-22 21:40:59 +0900 (74c2c57) @@ -1,4 +1,11 @@ -SUBDIRS = alpha charset db io map time +SUBDIRS = \ + alpha \ + charset \ + db \ + io \ + map \ + storage \ + time lib_LTLIBRARIES = libgrnxx.la @@ -8,6 +15,7 @@ libgrnxx_la_LIBADD = \ db/libgrnxx_db.la \ io/libgrnxx_io.la \ map/libgrnxx_map.la \ + storage/libgrnxx_storage.la \ time/libgrnxx_time.la libgrnxx_la_LDFLAGS = @AM_LTLDFLAGS@ Modified: lib/grnxx/storage.cpp (+14 -41) =================================================================== --- lib/grnxx/storage.cpp 2013-04-22 21:29:49 +0900 (6b35d09) +++ lib/grnxx/storage.cpp 2013-04-22 21:40:59 +0900 (1bf05c5) @@ -17,40 +17,8 @@ */ #include "grnxx/storage.hpp" -#include <ostream> - namespace grnxx { -#define GRNXX_FLAGS_WRITE(flag) do { \ - if (flags & flag) { \ - if (!is_first) { \ - builder << " | "; \ - } \ - builder << #flag; \ - is_first = false; \ - } \ -} while (false) - -StringBuilder &operator<<(StringBuilder &builder, StorageFlags flags) { - if (flags) { - bool is_first = true; - GRNXX_FLAGS_WRITE(STORAGE_ANONYMOUS); - GRNXX_FLAGS_WRITE(STORAGE_HUGE_TLB); - GRNXX_FLAGS_WRITE(STORAGE_READ_ONLY); - GRNXX_FLAGS_WRITE(STORAGE_TEMPORARY); - return builder; - } else { - return builder << "0"; - } -} - -std::ostream &operator<<(std::ostream &stream, StorageFlags flags) { - char buf[256]; - StringBuilder builder(buf); - builder << flags; - return stream.write(builder.c_str(), builder.length()); -} - StorageOptions::StorageOptions() : max_num_files(1000), max_file_size(1ULL << 40), @@ -75,34 +43,39 @@ StorageNodeHeader::StorageNodeHeader() Storage::Storage() {} Storage::~Storage() {} -Storage *Storage::create(StorageFlags flags, - const char *path, +Storage *Storage::create(const char *path, + StorageFlags flags, const StorageOptions &options) { // TODO return nullptr; } -Storage *Storage::open(StorageFlags flags, - const char *path) { +Storage *Storage::open(const char *path, + StorageFlags flags) { // TODO return nullptr; } -Storage *Storage::create_or_open(StorageFlags flags, - const char *path, +Storage *Storage::open_or_create(const char *path, + StorageFlags flags, const StorageOptions &options) { // TODO return nullptr; } bool Storage::exists(const char *path) { - std::unique_ptr<Storage> storage(open(STORAGE_READ_ONLY, path)); - return static_cast<bool>(storage); + std::unique_ptr<Storage> storage(open(path, STORAGE_READ_ONLY)); + if (!storage) { + // TODO: Error: memory allocation failed. + return false; + } + return true; } bool Storage::unlink(const char *path) { - std::unique_ptr<Storage> storage(open(STORAGE_READ_ONLY, path)); + std::unique_ptr<Storage> storage(open(path, STORAGE_READ_ONLY)); if (!storage) { + // TODO: Error: memory allocation failed. return false; } // TODO: Remove files. Modified: lib/grnxx/storage.hpp (+36 -41) =================================================================== --- lib/grnxx/storage.hpp 2013-04-22 21:29:49 +0900 (319d159) +++ lib/grnxx/storage.hpp 2013-04-22 21:40:59 +0900 (a8649a4) @@ -48,26 +48,27 @@ struct StorageNodeHeader { // The ID of the chunk to which this node belongs. uint16_t chunk_id; // (Non-phantom) - // The offset of this node in chunk. The actual offset is "offset" << "bits". + // The offset of this node in chunk. + // The actual offset is "offset" << "bits". uint32_t offset; // (Non-phantom) // The size of this node. The actual size is "size" << "bits". uint32_t size; // (Non-phantom) - // The ID of the next node in chunk. INVALID_ID indicates that this node is - // the last node in chunk. + // The ID of the next node in chunk. + // INVALID_ID indicates that this node is the last node in chunk. uint32_t next_node_id; // (Non-phantom) - // The ID of the previous node in chunk. INVALID_ID indicates that this node - // is the first node in chunk. + // The ID of the previous node in chunk. + // INVALID_ID indicates that this node is the first node in chunk. uint32_t prev_node_id; union { // (Phantom) // The ID of the next phantom node. uint32_t next_phantom_node_id; // (Active, marked, or unlinked) - // The ID of the latest child node. INVALID_ID indicates that this node has - // no children. + // The ID of the latest child node. + // INVALID_ID indicates that this node has no children. uint32_t child_id; // (Idle) // The ID of the next idle node. @@ -75,12 +76,12 @@ struct StorageNodeHeader { }; union { // (Active or marked) - // The ID of the next sibling node. INVALID_ID indicates that this node has - // no elder siblings. + // The ID of the next sibling node. + // INVALID_ID indicates that this node has no elder siblings. uint32_t sibling_node_id; // (Unlinked) - // The ID of the next unlinked node. INVALID_ID indicates that this node - // is the last unlinked node. + // The ID of the next unlinked node. + // INVALID_ID indicates that this node is the last unlinked node. uint32_t next_unlinked_node_id; // (Idle) // The ID of the previous idle node. @@ -110,22 +111,16 @@ struct StorageNode { class Storage; typedef FlagsImpl<Storage> StorageFlags; -// Create an anonymous (non-file-backed) temporary storage. All other flags, -// except STORAGE_HUGE_TLB, are ignored. -constexpr StorageFlags STORAGE_ANONYMOUS = StorageFlags::define(0x01); -// Try to use huge pages. If huge pages are not available, regular pages will -// be used. -constexpr StorageFlags STORAGE_HUGE_TLB = StorageFlags::define(0x02); -// Open a storage in read-only mode. If this flag is not specified, a storage -// is opened in read-write mode. -constexpr StorageFlags STORAGE_READ_ONLY = StorageFlags::define(0x04); -// Create a file-backed temporary storage. All other flags, except -// STORAGE_ANONYMOUS and STORAGE_HUGE_TLB, are ignored. -constexpr StorageFlags STORAGE_TEMPORARY = StorageFlags::define(0x08); - -// Generate a string from a set of flags. -StringBuilder &operator<<(StringBuilder &builder, StorageFlags flags); -std::ostream &operator<<(std::ostream &builder, StorageFlags flags); +// Use the default settings. +constexpr StorageFlags STORAGE_DEFAULT = StorageFlags::define(0x00); +// Use huge pages if available, or use regular pages. +constexpr StorageFlags STORAGE_HUGE_TLB = StorageFlags::define(0x01); +// Open a storage in read-only mode. +// If not specified, a storage is opened in read-write mode. +constexpr StorageFlags STORAGE_READ_ONLY = StorageFlags::define(0x02); +// Create an anonymous (non-file-backed) temporary storage if "path" == +// nullptr, or create a file-backed temporary storage. +constexpr StorageFlags STORAGE_TEMPORARY = StorageFlags::define(0x04); struct StorageOptions { // The maximum number of files. @@ -153,20 +148,20 @@ class Storage { Storage(); virtual ~Storage(); - // Create a storage if missing. If "flags" contains STORAGE_ANONYMOUS or - // STORAGE_TEMPORARY, "path" == nullptr is acceptable. Available flags are - // STORAGE_ANONYMOUS, STORAGE_HUGE_TLB, and STORAGE_TEMPORARY. - static Storage *create(StorageFlags flags, - const char *path = nullptr, + // Create a storage. + // "path" == nullptr is acceptable iff "flags" contains STORAGE_TEMPORARY. + // Available flags are STORAGE_HUGE_TLB and STORAGE_TEMPORARY. + static Storage *create(const char *path, + StorageFlags flags = STORAGE_DEFAULT, const StorageOptions &options = StorageOptions()); - // Open an existing storage. Available flags are STORAGE_HUGE_TLB and - // STORAGE_READ_ONLY. - static Storage *open(StorageFlags flags, - const char *path = nullptr); - // Create a storage if missing, or open an existing storage. The available - // flag is STORAGE_HUGE_TLB. - static Storage *create_or_open(StorageFlags flags, - const char *path = nullptr, + // Open a storage. + // Available flags are STORAGE_HUGE_TLB and STORAGE_READ_ONLY. + static Storage *open(const char *path, + StorageFlags flags = STORAGE_DEFAULT); + // Open or create a storage. + // The available flags is STORAGE_HUGE_TLB. + static Storage *open_or_create(const char *path, + StorageFlags flags = STORAGE_DEFAULT, const StorageOptions &options = StorageOptions()); // Return true iff "path" refers to a valid storage. @@ -186,7 +181,7 @@ class Storage { // Return the address to the header. virtual const StorageHeader *header() const = 0; - // Open an existing node. + // Open a node. virtual StorageNode open_node(uint32_t node_id) = 0; // Create a node of at least "size" bytes under "parent_node". virtual StorageNode create_node(const StorageNode &parent_node, Added: lib/grnxx/storage/Makefile.am (+22 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/Makefile.am 2013-04-22 21:40:59 +0900 (c94aee2) @@ -0,0 +1,22 @@ +noinst_LTLIBRARIES = libgrnxx_storage.la + +libgrnxx_storage_la_LDFLAGS = @AM_LTLDFLAGS@ + +libgrnxx_storage_la_SOURCES = \ + file.cpp \ + file-posix.cpp \ + file-windows.cpp \ + path.cpp \ + view.cpp \ + view-posix.cpp \ + view-windows.cpp + +libgrnxx_storage_includedir = ${includedir}/grnxx/storage +libgrnxx_storage_include_HEADERS = \ + file.hpp \ + file-posix.hpp \ + file-windows.hpp \ + path.hpp \ + view.hpp \ + view-posix.hpp \ + view-windows.hpp Added: lib/grnxx/storage/file-posix.cpp (+324 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/file-posix.cpp 2013-04-22 21:40:59 +0900 (b00cf60) @@ -0,0 +1,324 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "grnxx/storage/file-posix.hpp" + +#ifndef GRNXX_WINDOWS + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include "grnxx/error.hpp" +#include "grnxx/logger.hpp" +#include "grnxx/storage/path.hpp" + +namespace grnxx { +namespace storage { +namespace { + +constexpr int UNIQUE_PATH_GENERATION_TRIAL_COUNT = 10; + +} // namespace + +FileImpl::FileImpl() + : File(), + path_(nullptr), + flags_(FILE_DEFAULT), + fd_(-1), + locked_(false) {} + +FileImpl::~FileImpl() { + if (fd_ != -1) { + if (locked_) { + unlock(); + } + if (::close(fd_) != 0) { + GRNXX_ERROR() << "failed to close file: path = " << path_.get() + << ": '::close' " << Error(errno); + } + } +} + +FileImpl *FileImpl::create(const char *path, FileFlags flags) { + std::unique_ptr<FileImpl> file(new (std::nothrow) FileImpl); + if (!file) { + GRNXX_ERROR() << "new grnxx::storage::FileImpl failed"; + return nullptr; + } + if (~flags & FILE_TEMPORARY) { + if (!file->create_persistent_file(path, flags)) { + return nullptr; + } + } else { + if (!file->create_temporary_file(path, flags)) { + return nullptr; + } + } + return file.release(); +} + +FileImpl *FileImpl::open(const char *path, FileFlags flags) { + if (!path) { + GRNXX_ERROR() << "invalid argument: path = nullptr"; + return nullptr; + } + std::unique_ptr<FileImpl> file(new (std::nothrow) FileImpl); + if (!file) { + GRNXX_ERROR() << "new grnxx::storage::FileImpl failed"; + return nullptr; + } + if (!file->open_file(path, flags)) { + return nullptr; + } + return file.release(); +} + +FileImpl *FileImpl::open_or_create(const char *path, FileFlags flags) { + if (!path) { + GRNXX_ERROR() << "invalid argument: path = nullptr"; + return nullptr; + } + std::unique_ptr<FileImpl> file(new (std::nothrow) FileImpl); + if (!file) { + GRNXX_ERROR() << "new grnxx::storage::FileImpl failed"; + return nullptr; + } + if (!file->open_or_create_file(path, flags)) { + return nullptr; + } + return file.release(); +} + +bool FileImpl::exists(const char *path) { + if (!path) { + GRNXX_ERROR() << "invalid argument: path = nullptr"; + return false; + } + struct stat stat; + if (::stat(path, &stat) != 0) { + return false; + } + return S_ISREG(stat.st_mode); +} + +bool FileImpl::unlink(const char *path) { + if (!path) { + GRNXX_ERROR() << "invalid argument: path = nullptr"; + return false; + } + if (::unlink(path) != 0) { + GRNXX_WARNING() << "failed to unlink file: path = " << path + << ": '::unlink' " << Error(errno); + return false; + } + return true; +} + +bool FileImpl::lock(FileLockFlags lock_flags) { + if (locked_) { + GRNXX_ERROR() << "already locked: path = " << path_.get(); + return false; + } + FileLockFlags lock_type = + lock_flags & (FILE_LOCK_SHARED | FILE_LOCK_EXCLUSIVE); + if (!lock_type || (lock_type == (FILE_LOCK_SHARED | FILE_LOCK_EXCLUSIVE))) { + GRNXX_ERROR() << "invalid argument: lock_flags == " << lock_flags; + return false; + } + int operation = 0; + if (lock_flags & FILE_LOCK_SHARED) { + operation |= LOCK_SH; + } + if (lock_flags & FILE_LOCK_EXCLUSIVE) { + operation |= LOCK_EX; + } + if (lock_flags & FILE_LOCK_NONBLOCKING) { + operation |= LOCK_NB; + } + if (::flock(fd_, operation) != 0) { + if (errno == EWOULDBLOCK) { + // The file is locked by others. + return false; + } + GRNXX_ERROR() << "failed to lock file: path = " << path_.get() + << ", lock_flags = " << lock_flags + << ": '::flock' " << Error(errno); + return false; + } + locked_ = true; + return true; +} + +bool FileImpl::unlock() { + if (!locked_) { + GRNXX_WARNING() << "unlocked: path = " << path_.get(); + return false; + } + if (::flock(fd_, LOCK_UN) != 0) { + GRNXX_ERROR() << "failed to unlock file: path = " << path_.get() + << ": '::flock' " << Error(errno); + return false; + } + locked_ = false; + return true; +} + +bool FileImpl::sync() { + if (::fsync(fd_) != 0) { + GRNXX_ERROR() << "failed to sync file: path = " << path_.get() + << ": '::fsync' " << Error(errno); + return false; + } + return true; +} + +bool FileImpl::resize(int64_t size) { + if (flags_ & FILE_READ_ONLY) { + GRNXX_ERROR() << "read-only"; + return false; + } + if ((size < 0) || + (size > std::numeric_limits<off_t>::max())) { + GRNXX_ERROR() << "invalid argument: size = " << size; + return false; + } + if (::ftruncate(fd_, size) != 0) { + GRNXX_ERROR() << "failed to resize file: path = " << path_.get() + << ", size = " << size + << ": '::ftruncate' " << Error(errno); + return false; + } + return true; +} + +int64_t FileImpl::size() const { + struct stat stat; + if (::fstat(fd_, &stat) != 0) { + GRNXX_ERROR() << "failed to stat file: path = " << path_.get() + << ": '::fstat' " << Error(errno); + return -1; + } + return stat.st_size; +} + +const char *FileImpl::path() const { + return path_.get(); +} + +FileFlags FileImpl::flags() const { + return flags_; +} + +const void *FileImpl::handle() const { + return &fd_; +} + +bool FileImpl::create_persistent_file(const char *path, FileFlags flags) { + if (!path) { + GRNXX_ERROR() << "invalid argument: path = nullptr"; + return false; + } + if (!clone_path(path)) { + return false; + } + fd_ = ::open(path, O_RDWR | O_CREAT | O_EXCL, 0644); + if (fd_ == -1) { + GRNXX_ERROR() << "failed to create file: path = " << path + << ", flags = " << flags + << ": '::open' " << Error(errno); + return false; + } + return true; +} + +bool FileImpl::create_temporary_file(const char *path, FileFlags flags) { + flags_ = FILE_TEMPORARY; + int posix_flags = O_RDWR | O_CREAT | O_EXCL | O_NOCTTY; +#ifdef O_NOATIME + posix_flags |= O_NOATIME; +#endif // O_NOATIME +#ifdef O_NOFOLLOW + posix_flags |= O_NOFOLLOW; +#endif // O_NOFOLLOW + for (int i = 0; i < UNIQUE_PATH_GENERATION_TRIAL_COUNT; ++i) { + path_.reset(Path::unique_path(path)); + fd_ = ::open(path_.get(), posix_flags, 0600); + if (fd_ != -1) { + unlink(path_.get()); + return true; + } + GRNXX_WARNING() << "failed to create file: path = " << path_.get() + << ": '::open' " << Error(errno); + } + GRNXX_ERROR() << "failed to create temporary file: " + << "path = " << path + << ", flags = " << flags; + return false; +} + +bool FileImpl::open_file(const char *path, FileFlags flags) { + if (!clone_path(path)) { + return false; + } + int posix_flags = O_RDWR; + if (flags & FILE_READ_ONLY) { + posix_flags = O_RDONLY; + flags_ |= FILE_READ_ONLY; + } + fd_ = ::open(path, posix_flags); + if (fd_ == -1) { + GRNXX_ERROR() << "failed to open file: path = " << path + << ", flags = " << flags + << ": '::open' " << Error(errno); + return false; + } + return true; +} + +bool FileImpl::open_or_create_file(const char *path, FileFlags flags) { + if (!clone_path(path)) { + return false; + } + fd_ = ::open(path, O_RDWR | O_CREAT, 0644); + if (fd_ == -1) { + GRNXX_ERROR() << "failed to open file: path = " << path + << ", flags = " << flags + << ": '::open' " << Error(errno); + return false; + } + return true; +} + +bool FileImpl::clone_path(const char *path) { + const size_t size = std::strlen(path) + 1; + path_.reset(new (std::nothrow) char[size]); + if (!path_) { + GRNXX_ERROR() << "new char[] failed: size = " << size; + return false; + } + std::memcpy(path_.get(), path, size); + return true; +} + +} // namespace storage +} // namespace grnxx + +#endif // GRNXX_WINDOWS Added: lib/grnxx/storage/file-posix.hpp (+71 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/file-posix.hpp 2013-04-22 21:40:59 +0900 (cb4e474) @@ -0,0 +1,71 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef GRNXX_STORAGE_FILE_POSIX_HPP +#define GRNXX_STORAGE_FILE_POSIX_HPP + +#include "grnxx/storage/file.hpp" + +#ifndef GRNXX_WINDOWS + +namespace grnxx { +namespace storage { + +class FileImpl : public File { + public: + FileImpl(); + ~FileImpl(); + + static FileImpl *create(const char *path, FileFlags flags); + static FileImpl *open(const char *path, FileFlags flags); + static FileImpl *open_or_create(const char *path, FileFlags flags); + + static bool exists(const char *path); + static bool unlink(const char *path); + + bool lock(FileLockFlags lock_flags); + bool unlock(); + + bool sync(); + + bool resize(int64_t size); + int64_t size() const; + + const char *path() const; + FileFlags flags() const; + const void *handle() const; + + private: + std::unique_ptr<char[]> path_; + FileFlags flags_; + int fd_; + bool locked_; + + bool create_persistent_file(const char *path, FileFlags flags); + bool create_temporary_file(const char *path_prefix, FileFlags flags); + bool open_file(const char *path, FileFlags flags); + bool open_or_create_file(const char *path, FileFlags flags); + + bool clone_path(const char *path); +}; + +} // namespace storage +} // namespace grnxx + +#endif // GRNXX_WINDOWS + +#endif // GRNXX_STORAGE_FILE_POSIX_HPP Added: lib/grnxx/storage/file-windows.cpp (+354 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/file-windows.cpp 2013-04-22 21:40:59 +0900 (957ab83) @@ -0,0 +1,354 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "grnxx/storage/file-windows.hpp" + +#ifdef GRNXX_WINDOWS + +#include <sys/types.h> +#include <sys/stat.h> +#include <io.h> + +#include "grnxx/error.hpp" +#include "grnxx/logger.hpp" +#include "grnxx/storage/path.hpp" + +namespace grnxx { +namespace storage { +namespace { + +constexpr int UNIQUE_PATH_GENERATION_TRIAL_COUNT = 10; + +} // namespace + +FileImpl::FileImpl() + : File(), + path_(nullptr), + flags_(FILE_DEFAULT), + handle_(INVALID_HANDLE_VALUE), + locked_(false) {} + +FileImpl::~FileImpl() { + if (handle_ != INVALID_HANDLE_VALUE) { + if (locked_) { + unlock(); + } + if (!::CloseHandle(handle_)) { + GRNXX_ERROR() << "failed to close file: file = " << *this + << ": '::CloseHandle' " << Error(::GetLastError()); + } + } +} + + +FileImpl *FileImpl::create(const char *path, FileFlags flags) { + std::unique_ptr<FileImpl> file(new (std::nothrow) FileImpl); + if (!file) { + GRNXX_ERROR() << "new grnxx::storage::FileImpl failed"; + return nullptr; + } + if (~flags & FILE_TEMPORARY) { + if (!file->create_persistent_file(path, flags)) { + return nullptr; + } + } else { + if (!file->create_temporary_file(path, flags)) { + return nullptr; + } + } + return file.release(); +} + +FileImpl *FileImpl::open(const char *path, FileFlags flags) { + if (!path) { + GRNXX_ERROR() << "invalid argument: path = nullptr"; + return nullptr; + } + std::unique_ptr<FileImpl> file(new (std::nothrow) FileImpl); + if (!file) { + GRNXX_ERROR() << "new grnxx::storage::FileImpl failed"; + return nullptr; + } + if (!file->open_file(path, flags)) { + return nullptr; + } + return file.release(); +} + +FileImpl *FileImpl::open_or_create(const char *path, FileFlags flags) { + if (!path) { + GRNXX_ERROR() << "invalid argument: path = nullptr"; + return nullptr; + } + std::unique_ptr<FileImpl> file(new (std::nothrow) FileImpl); + if (!file) { + GRNXX_ERROR() << "new grnxx::storage::FileImpl failed"; + return nullptr; + } + if (!file->open_or_create_file(path, flags)) { + return nullptr; + } + return file.release(); +} + +bool FileImpl::exists(const char *path) { + if (!path) { + GRNXX_ERROR() << "invalid argument: path = nullptr"; + return false; + } + struct _stat stat; + if (::_stat(path, &stat) != 0) { + return false; + } + return stat.st_mode & _S_IFREG; +} + +bool FileImpl::unlink(const char *path) { + if (!path) { + GRNXX_ERROR() << "invalid argument: path = nullptr"; + return false; + } + if (!::DeleteFile(path)) { + GRNXX_WARNING() << "failed to unlink file: path = " << path + << ": '::DeleteFile' " << Error(::GetLastError()); + return false; + } + return true; +} + +bool FileImpl::try_lock(FileLockMode mode) { + if (locked_) { + GRNXX_ERROR() << "already locked: path = " << path_.get(); + return false; + } + FileLockFlags lock_type = + lock_flags & (FILE_LOCK_SHARED | FILE_LOCK_EXCLUSIVE); + if (!lock_type || (lock_type == (FILE_LOCK_SHARED | FILE_LOCK_EXCLUSIVE))) { + GRNXX_ERROR() << "invalid argument: lock_flags == " << lock_flags; + return false; + } + DWORD flags = LOCKFILE_FAIL_IMMEDIATELY; + if (lock_flags & FILE_LOCK_SHARED) { + // Nothing to do. + } + if (lock_flags & FILE_LOCK_EXCLUSIVE) { + flags |= LOCKFILE_EXCLUSIVE_LOCK; + } + if (lock_flags & FILE_LOCK_NONBLOCKING) { + flags |= LOCKFILE_FAIL_IMMEDIATELY; + } + + OVERLAPPED overlapped; + overlapped.Offset = 0; + overlapped.OffsetHigh = 0x80000000U; + if (!::LockFileEx(handle_, flags, 0, 0, 0x80000000U, &overlapped)) { + const DWORD last_error = ::GetLastError(); + if (last_error == ERROR_LOCK_FAILED) { + // The file is locked by others. + return false; + } + GRNXX_ERROR() << "failed to lock file: path = " << path_.get() + << ", mode = " << mode + << ": '::LockFileEx' " << Error(last_error); + return false; + } + locked_ = true; + return true; +} + +bool FileImpl::unlock() { + if (!locked_) { + GRNXX_WARNING() << "unlocked: path = " << path_.get(); + return false; + } + + OVERLAPPED overlapped; + overlapped.Offset = 0; + overlapped.OffsetHigh = 0x80000000U; + if (!::UnlockFileEx(handle_, 0, 0, 0x80000000U, &overlapped)) { + GRNXX_ERROR() << "failed to unlock file: path = " << path_.get() + << ": '::UnlockFileEx' " << Error(::GetLastError()); + return false; + } + locked_ = false; + return true; +} + +void FileImpl::sync() { + if (!::FlushFileBuffers(handle_)) { + GRNXX_ERROR() << "failed to sync file: path = " << path_.get() + << ": '::FlushFileBuffers' " << Error(errno); + return false; + } + return true; +} + +bool FileImpl::resize(int64_t size) { + if (flags_ & FILE_READ_ONLY) { + GRNXX_ERROR() << "read-only"; + return false; + } + if ((size < 0) || + (size > std::numeric_limits<off_t>::max())) { + GRNXX_ERROR() << "invalid argument: size = " << size; + return false; + } + LARGE_INTEGER request; + request.QuadPart = size; + if (!::SetFilePointerEx(handle_, request, nullptr, FILE_BEGIN)) { + GRNXX_ERROR() << "failed to seek file: path = " << path_.get() + << ", offset = " << offset << ", whence = " << whence + << ": '::SetFilePointerEx' " << Error(::GetLastError()); + return false; + } + if (!::SetEndOfFile(handle_)) { + GRNXX_ERROR() << "failed to resize file: path = " << path_.get() + << ", size = " << size + << ": '::SetEndOfFile' " << Error(::GetLastError()); + return false; + } + return true; +} + +int64_t FileImpl::size() const { + LARGE_INTEGER file_size; + if (!::GetFileSizeEx(handle_, &file_size)) { + GRNXX_ERROR() << "failed to get file size: path = " << path_.get() + << ": '::GetFileSizeEx' " << Error(::GetLastError()); + return -1; + } + return file_size.QuadPart; +} + +const char *FileImpl::path() const { + return path_.get(); +} + +FileFlags FileImpl::flags() const { + return flags_; +} + +const void *FileImpl::handle() const { + return &handle_; +} + +bool FileImpl::create_persistent_file(const char *path, FileFlags flags) { + if (!path) { + GRNXX_ERROR() << "invalid argument: path = nullptr"; + return false; + } + if (!clone_path(path)) { + return false; + } + const DWORD desired_access = GENERIC_READ | GENERIC_WRITE; + const DWORD share_mode = + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE; + const DWORD creation_disposition = CREATE_NEW; + const DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL; + handle_ = ::CreateFileA(path, desired_access, share_mode, nullptr, + creation_disposition, flags_and_attributes, nullptr); + if (handle_ == INVALID_HANDLE_VALUE) { + GRNXX_ERROR() << "failed to open file: path = " << path + << ", flags = " << flags + << ": '::CreateFileA' " << Error(::GetLastError()); + return false; + } + return true; +} + +bool FileImpl::create_temporary_file(const char *path, FileFlags flags) { + flags_ = FILE_TEMPORARY; + const DWORD desired_access = GENERIC_READ | GENERIC_WRITE; + const DWORD share_mode = FILE_SHARE_DELETE; + const DWORD creation_disposition = CREATE_NEW; + const DWORD flags_and_attributes = FILE_ATTRIBUTE_TEMPORARY | + FILE_FLAG_DELETE_ON_CLOSE; + for (int i = 0; i < UNIQUE_PATH_GENERATION_TRIAL_COUNT; ++i) { + path_.reset(Path::unique_path(path)); + handle_ = ::CreateFileA(path_.get(), desired_access, + share_mode, nullptr, creation_disposition, + flags_and_attributes, nullptr); + if (handle_ != INVALID_HANDLE_VALUE) { + return true; + } + GRNXX_WARNING() << "failed to create file: path = " << path_.get() + << ": '::CreateFileA' " << Error(::GetLastError()); + } + GRNXX_ERROR() << "failed to create temporary file: path = " << path + << ", flags = " << flags; + return false; +} + +bool FileImpl::open_file(const char *path, FileFlags flags) { + if (!clone_path(path)) { + return false; + } + DWORD desired_access = GENERIC_READ | GENERIC_WRITE; + if (flags_ & FILE_READ_ONLY) { + flags_ |= FILE_READ_ONLY; + desired_access = GENERIC_READ; + } + const DWORD share_mode = + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE; + const DWORD creation_disposition = OPEN_EXISTING; + const DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL; + handle_ = ::CreateFileA(path, desired_access, share_mode, nullptr, + creation_disposition, flags_and_attributes, nullptr); + if (handle_ == INVALID_HANDLE_VALUE) { + GRNXX_ERROR() << "failed to open file: path = " << path + << ", flags = " << flags + << ": '::CreateFileA' " << Error(::GetLastError()); + return false; + } + return true; +} + +bool FileImpl::open_or_create_file(const char *path, FileFlags flags) { + if (!clone_path(path)) { + return false; + } + const DWORD desired_access = GENERIC_READ | GENERIC_WRITE; + const DWORD share_mode = + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE; + const DWORD creation_disposition = OPEN_ALWAYS; + const DWORD flags_and_attributes = FILE_ATTRIBUTE_NORMAL; + handle_ = ::CreateFileA(path, desired_access, share_mode, nullptr, + creation_disposition, flags_and_attributes, nullptr); + if (handle_ == INVALID_HANDLE_VALUE) { + GRNXX_ERROR() << "failed to open file: path = " << path + << ", flags = " << flags + << ": '::CreateFileA' " << Error(::GetLastError()); + return false; + } + return true; +} + +bool FileImpl::clone_path(const char *path) { + const size_t size = std::strlen(path) + 1; + path_.reset(new (std::nothrow) char[size]); + if (!path_) { + GRNXX_ERROR() << "new char[] failed: size = " << size; + return false; + } + std::memcpy(path_.get(), path, size); + return true; +} + +} // namespace storage +} // namespace grnxx + +#endif // GRNXX_WINDOWS Added: lib/grnxx/storage/file-windows.hpp (+79 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/file-windows.hpp 2013-04-22 21:40:59 +0900 (0f4b63d) @@ -0,0 +1,79 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef GRNXX_STORAGE_FILE_WINDOWS_HPP +#define GRNXX_STORAGE_FILE_WINDOWS_HPP + +#include "grnxx/storage/file.hpp" + +#ifdef GRNXX_WINDOWS + +#include <windows.h> + +// FILE_READ_ONLY is defined as a macro in windows.h. +#ifdef FILE_READ_ONLY +# undef FILE_READ_ONLY +#endif // FILE_READ_ONLY + +namespace grnxx { +namespace storage { + +class FileImpl : public File { + public: + FileImpl(); + ~FileImpl(); + + static FileImpl *create(const char *path, FileFlags flags); + static FileImpl *open(const char *path, FileFlags flags); + static FileImpl *open_or_create(const char *path, FileFlags flags); + + static bool exists(const char *path); + static bool unlink(const char *path); + + bool lock(FileLockFlags lock_flags); + bool unlock(); + + bool sync(); + + bool resize(int64_t size); + int64_t size() const; + + const char *path() const; + FileFlags flags() const; + const void *handle() const; + + private: + std::unique_ptr<char[]> path_; + FileFlags flags_; + HANDLE handle_; + int fd_; + bool locked_; + + bool create_persistent_file(const char *path, FileFlags flags); + bool create_temporary_file(const char *path_prefix, FileFlags flags); + bool open_file(const char *path, FileFlags flags); + bool open_or_create_file(const char *path, FileFlags flags); + + bool clone_path(const char *path); +}; + +} // namespace storage +} // namespace grnxx + +#endif // GRNXX_WINDOWS + +#endif // GRNXX_STORAGE_FILE_WINDOWS_HPP Added: lib/grnxx/storage/file.cpp (+84 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/file.cpp 2013-04-22 21:40:59 +0900 (90fbed1) @@ -0,0 +1,84 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "grnxx/storage/file.hpp" + +#include "grnxx/storage/file-posix.hpp" +#include "grnxx/storage/file-windows.hpp" +#include "grnxx/string_builder.hpp" + +namespace grnxx { +namespace storage { + +#define GRNXX_FLAGS_WRITE(flag) do { \ + if (flags & flag) { \ + if (!is_first) { \ + builder << " | "; \ + } \ + builder << #flag; \ + is_first = false; \ + } \ +} while (false) + +StringBuilder &operator<<(StringBuilder &builder, FileFlags flags) { + if (flags) { + bool is_first = true; + GRNXX_FLAGS_WRITE(FILE_READ_ONLY); + GRNXX_FLAGS_WRITE(FILE_TEMPORARY); + return builder; + } else { + return builder << "FILE_DEFAULT"; + } +} + +StringBuilder &operator<<(StringBuilder &builder, FileLockFlags flags) { + if (flags) { + bool is_first = true; + GRNXX_FLAGS_WRITE(FILE_LOCK_SHARED); + GRNXX_FLAGS_WRITE(FILE_LOCK_EXCLUSIVE); + GRNXX_FLAGS_WRITE(FILE_LOCK_NONBLOCKING); + return builder; + } else { + return builder << "0"; + } +} + +File::File() {} +File::~File() {} + +File *File::create(const char *path, FileFlags flags) { + return FileImpl::create(path, flags); +} + +File *File::open(const char *path, FileFlags flags) { + return FileImpl::open(path, flags); +} + +File *File::open_or_create(const char *path, FileFlags flags) { + return FileImpl::open_or_create(path, flags); +} + +bool File::exists(const char *path) { + return FileImpl::exists(path); +} + +bool File::unlink(const char *path) { + return FileImpl::unlink(path); +} + +} // namespace storage +} // namespace grnxx Added: lib/grnxx/storage/file.hpp (+105 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/file.hpp 2013-04-22 21:40:59 +0900 (ceeb21a) @@ -0,0 +1,105 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef GRNXX_STORAGE_FILE_HPP +#define GRNXX_STORAGE_FILE_HPP + +#include "grnxx/basic.hpp" +#include "grnxx/flags_impl.hpp" + +namespace grnxx { + +class StringBuilder; + +namespace storage { + +class File; +typedef FlagsImpl<File> FileFlags; + +// Use the default settings. +constexpr FileFlags FILE_DEFAULT = FileFlags::define(0x00); +// Open a file in read-only mode. +constexpr FileFlags FILE_READ_ONLY = FileFlags::define(0x01); +// Create a temporary file. +constexpr FileFlags FILE_TEMPORARY = FileFlags::define(0x02); + +StringBuilder &operator<<(StringBuilder &builder, FileFlags flags); + +class FileLock; +typedef FlagsImpl<FileLock> FileLockFlags; + +// Apply an exclusive advisory lock. +constexpr FileLockFlags FILE_LOCK_SHARED = FileLockFlags::define(0x01); +// Apply a shared advisory lock. +constexpr FileLockFlags FILE_LOCK_EXCLUSIVE = FileLockFlags::define(0x02); +// Immediately return the result when the file is locked. +constexpr FileLockFlags FILE_LOCK_NONBLOCKING = FileLockFlags::define(0x04); + +StringBuilder &operator<<(StringBuilder &builder, FileLockFlags flags); + +class File { + public: + File(); + virtual ~File(); + + // Create a file. + // "path" == nullptr is acceptable iff "flags" contains FILE_TEMPORARY. + // The available flag is FILE_TEMPORARY. + static File *create(const char *path = nullptr, + FileFlags flags = FILE_DEFAULT); + // Open a file. + // The available flag is FILE_READ_ONLY. + static File *open(const char *path = nullptr, + FileFlags flags = FILE_DEFAULT); + // Open or create a file. + // "path" == nullptr is acceptable iff "flags" contains FILE_TEMPORARY. + // There are no available flags. + static File *open_or_create(const char *path = nullptr, + FileFlags flags = FILE_DEFAULT); + + // Return true iff "path" refers to a regular file. + static bool exists(const char *path); + // Unlink a file and return true on success. + static bool unlink(const char *path); + + // Try to lock a file and return true on success. + // Note that the file is accessible even if it is locked (advisory). + virtual bool lock(FileLockFlags lock_flags) = 0; + // Unlock a file and return true on success. + virtual bool unlock() = 0; + + // Flush modified pages and return true on success. + virtual bool sync() = 0; + + // Extend or truncate a file to "size" bytes. + // Note that the contents of the extended part are undefined. + virtual bool resize(int64_t size) = 0; + // Return the file size on success, or a negative value on failure. + virtual int64_t size() const = 0; + + // Return the file path. + virtual const char *path() const = 0; + // Return the activated flags. + virtual FileFlags flags() const = 0; + // Return a pointer to the file handle + virtual const void *handle() const = 0; +}; + +} // namespace storage +} // namespace grnxx + +#endif // GRNXX_STORAGE_FILE_HPP Added: lib/grnxx/storage/path.cpp (+143 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/path.cpp 2013-04-22 21:40:59 +0900 (4145c9b) @@ -0,0 +1,143 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "grnxx/storage/path.hpp" + +#ifdef GRNXX_WINDOWS +# include <stdlib.h> +#else // GRNXX_WINDOWS +# include <unistd.h> +#endif // GRNXX_WINDOWS + +#include <cerrno> +#include <cstdlib> +#include <random> + +#include "grnxx/error.hpp" +#include "grnxx/logger.hpp" +#include "grnxx/string_builder.hpp" + +namespace grnxx { +namespace storage { +namespace { + +constexpr size_t MAX_PATH_LENGTH = 1023; + +} // namespace + +char *Path::full_path(const char *path) { + if (!path) { + path = ""; + } + char full_path[MAX_PATH_LENGTH + 1]; + size_t full_path_length = 0; +#ifdef GRNXX_WINDOWS + if (!::_fullpath(full_path, path, sizeof(full_path))) { + GRNXX_ERROR() << "failed to generate full path: path = " << path + << ": '::_fullpath' " << Error(errno); + return nullptr; + } + full_path_length = std::strlen(full_path); +#else // GRNXX_WINDOWS + if (path[0] != '/') { + if (!::getcwd(full_path, sizeof(full_path))) { + GRNXX_ERROR() << "failed to get current working directory: '::getcwd' " + << Error(errno); + return nullptr; + } + full_path_length = std::strlen(full_path); + full_path[full_path_length++] = '/'; + } + + const size_t path_length = std::strlen(path); + if ((full_path_length + path_length) >= sizeof(full_path)) { + GRNXX_ERROR() << "failed to generate full path: path = " << path + << ": too long"; + return nullptr; + } + std::memcpy(full_path + full_path_length, path, path_length); + full_path_length += path_length; + full_path[full_path_length] = '\0'; + + size_t src = 0; + size_t dest = 0; + while (full_path[src] != '\0') { + if (full_path[src] == '/') { + if (full_path[src + 1] == '\0') { + full_path[dest++] = full_path[src++]; + break; + } else if (full_path[src + 1] == '/') { + ++src; + continue; + } else if (full_path[src + 1] == '.') { + if ((full_path[src + 2] == '/') || (full_path[src + 2] == '\0')) { + src += 2; + continue; + } else if (full_path[src + 2] == '.') { + if ((full_path[src + 3] == '/') || (full_path[src + 3] == '\0')) { + if (dest > 0) { + do { + --dest; + } while (full_path[dest] != '/'); + } + src += 3; + continue; + } + } + } + } + full_path[dest++] = full_path[src++]; + } + if (dest == 0) { + full_path[0] = '/'; + full_path_length = 1; + } else { + full_path_length = dest; + } +#endif // GRNXX_WINDOWS + char * const buf = new (std::nothrow) char[full_path_length + 1]; + std::memcpy(buf, full_path, full_path_length); + buf[full_path_length] = '\0'; + return buf; +} + +char *Path::unique_path(const char *prefix) { + if (!prefix) { + prefix = ""; + } + constexpr size_t SUFFIX_LENGTH = 8; + const size_t prefix_length = std::strlen(prefix); + const size_t path_size = prefix_length + SUFFIX_LENGTH + 2; + char * const path(new (std::nothrow) char[path_size]); + if (!path) { + GRNXX_ERROR() << "new char[] failed: path_size = " << path_size; + return nullptr; + } + std::memcpy(path, prefix, prefix_length); + path[prefix_length] = '_'; + std::random_device random; + for (size_t i = 0; i < SUFFIX_LENGTH; ++i) { + const int value = static_cast<int>(random() % 36); + path[prefix_length + 1 + i] = + (value < 10) ? ('0' + value) : ('A' + value - 10); + } + path[prefix_length + 1 + SUFFIX_LENGTH] = '\0'; + return path; +} + +} // namespace storage +} // namespace grnxx Added: lib/grnxx/storage/path.hpp (+42 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/path.hpp 2013-04-22 21:40:59 +0900 (1306535) @@ -0,0 +1,42 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef GRNXX_STORAGE_PATH_HPP +#define GRNXX_STORAGE_PATH_HPP + +#include "grnxx/basic.hpp" + +namespace grnxx { +namespace storage { + +class Path { + public: + // Generate a full path from "path" and return an allocated buffer which + // must be freed with delete[]. + static char *full_path(const char *path); + + // Generate an almost unique path by adding a random suffix and return an + // allocated buffer which must be freed with delete[]. + // For example, when "prefix" == "temp", the result is "temp_XXXXXXXX", + // where X indicates a random character ('0'-'9' or 'A'-'Z'). + static char *unique_path(const char *prefix); +}; + +} // namespace storage +} // namespace grnxx + +#endif // GRNXX_STORAGE_PATH_HPP Added: lib/grnxx/storage/view-posix.cpp (+157 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/view-posix.cpp 2013-04-22 21:40:59 +0900 (7063728) @@ -0,0 +1,157 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "grnxx/storage/view-posix.hpp" + +#ifndef GRNXX_WINDOWS + +#include <sys/mman.h> +#include <cerrno> + +#include "grnxx/error.hpp" +#include "grnxx/storage/file.hpp" +#include "grnxx/logger.hpp" + +#ifndef MAP_ANONYMOUS +# ifdef MAP_ANON +# define MAP_ANONYMOUS MAP_ANON +# endif // MAP_ANON +#endif // MAP_ANONYMOUS + +namespace grnxx { +namespace storage { + +ViewImpl::ViewImpl() : flags_(VIEW_DEFAULT), address_(MAP_FAILED), size_(0) {} + +ViewImpl::~ViewImpl() { + if (address_ != MAP_FAILED) { + if (::munmap(address_, static_cast<size_t>(size_)) != 0) { + GRNXX_ERROR() << "failed to unmap view: '::munmap' " << Error(errno); + } + } +} + +View *ViewImpl::create(File *file, int64_t offset, int64_t size, + ViewFlags flags) { + std::unique_ptr<ViewImpl> view(new (std::nothrow) ViewImpl); + if (!view) { + GRNXX_ERROR() << "new grnxx::storage::FileImpl failed"; + return nullptr; + } + if (file) { + view->create_file_backed_view(file, offset, size, flags); + } else { + view->create_anonymous_view(size, flags); + } + return view.release(); +} + +bool ViewImpl::sync(int64_t offset, int64_t size) { + if ((offset < 0) || (offset > size_) || (size > size_) || + ((size >= 0) && ((offset + size) > size_))) { + GRNXX_ERROR() << "invalid argument: offset = " << offset + << ", size = " << size << ", view_size = " << size_; + return false; + } + if (size < 0) { + size = size_ - offset; + } + if (size > 0) { + if (::msync(static_cast<char *>(address_) + offset, size, MS_SYNC) != 0) { + GRNXX_ERROR() << "failed to sync view: offset = " << offset + << ", size = " << size << ": '::msync' " << Error(errno); + return false; + } + } + return true; +} + +ViewFlags ViewImpl::flags() const { + return flags_; +} + +void *ViewImpl::address() const { + return address_; +} + +int64_t ViewImpl::size() const { + return size_; +} + +bool ViewImpl::create_file_backed_view(File *file, int64_t offset, int64_t size, + ViewFlags flags) { + const int64_t file_size = file->size(); + if ((offset < 0) || (offset >= file_size) || (size == 0)) { + GRNXX_ERROR() << "invalid argument: offset = " << offset + << ", size = " << size << ", file_size = " << file_size; + return false; + } + if (file->flags() & FILE_READ_ONLY) { + flags_ |= VIEW_READ_ONLY; + } + size_ = size; + int protection_flags = PROT_READ | PROT_WRITE; + if (flags_ & VIEW_READ_ONLY) { + flags_ |= VIEW_READ_ONLY; + protection_flags = PROT_READ; + } + const int mmap_flags = MAP_SHARED; + address_ = ::mmap(nullptr, size, protection_flags, mmap_flags, + *static_cast<const int *>(file->handle()), offset); + if (address_ == MAP_FAILED) { + GRNXX_ERROR() << "failed to map file-backed view: " + << "file_path = " << file->path() + << ", offset = " << offset << ", size = " << size + << ", flags = " << flags << ": '::mmap' " << Error(errno); + return false; + } + return true; +} + +bool ViewImpl::create_anonymous_view(int64_t size, ViewFlags flags) { + if (size <= 0) { + GRNXX_ERROR() << "invalid argument: size = " << size; + return false; + } + flags_ = VIEW_ANONYMOUS; + size_ = size; + const int protection_flags = PROT_READ | PROT_WRITE; + const int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS; +#ifdef MAP_HUGETLB + if (flags & VIEW_HUGE_TLB) { + address_ = ::mmap(nullptr, size, protection_flags, + mmap_flags | MAP_HUGETLB, -1, 0); + if (address_ != MAP_FAILED) { + flags_ |= VIEW_HUGE_TLB; + } + } +#endif // MAP_HUGETLB + if (address_ == MAP_FAILED) { + address_ = ::mmap(nullptr, size, protection_flags, mmap_flags, -1, 0); + if (address_ == MAP_FAILED) { + GRNXX_ERROR() << "failed to map anonymous view: size = " << size + << ", flags = " << flags << ": '::mmap' " << Error(errno); + return false; + } + } + return true; +} + +} // namespace storage +} // namespace grnxx + +#endif // GRNXX_WINDOWS Added: lib/grnxx/storage/view-posix.hpp (+57 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/view-posix.hpp 2013-04-22 21:40:59 +0900 (c954ee0) @@ -0,0 +1,57 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef GRNXX_STORAGE_VIEW_POSIX_HPP +#define GRNXX_STORAGE_VIEW_POSIX_HPP + +#include "grnxx/storage/view.hpp" + +#ifndef GRNXX_WINDOWS + +namespace grnxx { +namespace storage { + +class ViewImpl : public View { + public: + ViewImpl(); + ~ViewImpl(); + + static View *create(File *file, int64_t offset, int64_t size, + ViewFlags flags); + + bool sync(int64_t offset, int64_t size); + + ViewFlags flags() const; + void *address() const; + int64_t size() const; + + private: + ViewFlags flags_; + void *address_; + int64_t size_; + + bool create_file_backed_view(File *file, int64_t offset, int64_t size, + ViewFlags flags); + bool create_anonymous_view(int64_t size, ViewFlags flags); +}; + +} // namespace storage +} // namespace grnxx + +#endif // GRNXX_WINDOWS + +#endif // GRNXX_STORAGE_VIEW_POSIX_HPP Added: lib/grnxx/storage/view-windows.cpp (+160 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/view-windows.cpp 2013-04-22 21:40:59 +0900 (90ca935) @@ -0,0 +1,160 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "grnxx/storage/view-windows.hpp" + +#ifdef GRNXX_WINDOWS + +#include "grnxx/error.hpp" +#include "grnxx/storage/file.hpp" +#include "grnxx/logger.hpp" + +namespace grnxx { +namespace storage { + +ViewImpl::ViewImpl() + : flags_(VIEW_DEFAULT), + handle_(nullptr), + address_(nullptr), + size_(0) {} + +ViewImpl::~ViewImpl() { + if (address_) { + if (!::UnmapViewOfFile(address_)) { + GRNXX_ERROR() << "failed to unmap view" + << ": '::UnmapViewOfFile' " << Error(::GetLastError()); + } + } + if (handle_) { + if (!::CloseHandle(handle_)) { + GRNXX_ERROR() << "failed to close file mapping" + << ": '::CloseHandle' " << Error(::GetLastError()); + } + } +} + +View *ViewImpl::create(File *file, int64_t offset, int64_t size, + ViewFlags flags) { + std::unique_ptr<ViewImpl> view(new (std::nothrow) ViewImpl); + if (!view) { + GRNXX_ERROR() << "new grnxx::storage::FileImpl failed"; + return nullptr; + } + if (file) { + view->create_file_backed_view(file, offset, size, flags); + } else { + view->create_anonymous_view(size, flags); + } + return view.release(); +} + +bool ViewImpl::sync(int64_t offset, int64_t size) { + if ((offset < 0) || (offset > size_) || (size > size_) || + ((size >= 0) && ((offset + size) > size_))) { + GRNXX_ERROR() << "invalid argument: offset = " << offset + << ", size = " << size << ", view_size = " << size_; + return false; + } + if (size < 0) { + size = size_ - offset; + } + if (size > 0) { + if (!::FlushViewOfFile(static_cast<char *>(address_) + offset, size)) { + GRNXX_ERROR() << "failed to sync view: offset = " << offset + << ", size = " << size + << ": '::FlushViewOfFile' " << Error(::GetLastError()); + return false; + } + } + return true; +} + +bool ViewImpl::create_file_backed_view(File *file, int64_t offset, int64_t size, + ViewFlags flags) { + const int64_t file_size = file->size(); + if ((offset < 0) || (offset >= file_size) || (size == 0)) { + GRNXX_ERROR() << "invalid argument: offset = " << offset + << ", size = " << size << ", file_size = " << file_size; + return false; + } + if (file->flags() & FILE_READ_ONLY) { + flags_ |= VIEW_READ_ONLY; + } + size_ = size; + int protection_mode = PAGE_READWRITE; + DWORD desired_access = FILE_MAP_WRITE; + if (flags_ & VIEW_READ_ONLY) { + protection_mode = PAGE_READONLY; + desired_access = FILE_MAP_READ; + } + const DWORD size_high = static_cast<DWORD>((offset + size) >> 32); + const DWORD size_low = static_cast<DWORD>(offset + size); + handle_ = ::CreateFileMapping(*static_cast<const HANDLE *>(file->handle()), + nullptr, protection_mode, size_high, size_low, + nullptr); + if (!handle_) { + GRNXX_ERROR() << "failed to create file mapping: " + << "file_path = " << file->path() << ", offset = " << offset + << ", size = " << size << ", flags = " << flags + << ": '::CreateFileMapping' " << Error(::GetLastError()); + return false; + } + const DWORD offset_high = static_cast<DWORD>(offset >> 32); + const DWORD offset_low = static_cast<DWORD>(offset); + address_ = ::MapViewOfFile(handle_, desired_access, offset_high, offset_low, + static_cast<SIZE_T>(size)); + if (!address_) { + GRNXX_ERROR() << "failed to map view: " + << "file_path = " << file->path() << ", offset = " << offset + << ", size = " << size << ", flags = " << flags + << ": '::MapViewOfFile' " << Error(::GetLastError()); + return false; + } + return true; +} + +bool ViewImpl::create_anonymous_view(int64_t size, ViewFlags flags) { + if (size <= 0) { + GRNXX_ERROR() << "invalid argument: size = " << size; + return false; + } + flags_ = VIEW_ANONYMOUS; + size_ = size; + const DWORD size_high = static_cast<DWORD>(size >> 32); + const DWORD size_low = static_cast<DWORD>(size); + handle_ = ::CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, + size_high, size_low, nullptr); + if (!handle_) { + GRNXX_ERROR() << "failed to create anonymous file mapping: " + << "size = " << size << ", flags = " << flags + << ": '::CreateFileMapping' " << Error(::GetLastError()); + return false; + } + address_ = ::MapViewOfFile(handle_, FILE_MAP_WRITE, 0, 0, 0); + if (!address_) { + GRNXX_ERROR() << "failed to map anonymous view: " + << "size = " << size << ", flags = " << flags + << ": '::MapViewOfFile' " << Error(::GetLastError()); + return false; + } + return true; +} + +} // namespace storage +} // namespace grnxx + +#endif // GRNXX_WINDOWS Added: lib/grnxx/storage/view-windows.hpp (+65 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/view-windows.hpp 2013-04-22 21:40:59 +0900 (0152902) @@ -0,0 +1,65 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef GRNXX_STORAGE_VIEW_WINDOWS_HPP +#define GRNXX_STORAGE_VIEW_WINDOWS_HPP + +#include "grnxx/storage/view.hpp" + +#ifdef GRNXX_WINDOWS + +#include <windows.h> + +// FILE_READ_ONLY is defined as a macro in windows.h. +#ifdef FILE_READ_ONLY +# undef FILE_READ_ONLY +#endif // FILE_READ_ONLY + +namespace grnxx { +namespace storage { + +class ViewImpl : public View { + public: + ViewImpl(); + ~ViewImpl(); + + static View *create(File *file, int64_t offset, int64_t size, + ViewFlags flags); + + bool sync(int64_t offset, int64_t size); + + ViewFlags flags() const; + void *address() const; + int64_t size() const; + + private: + ViewFlags flags_; + HANDLE handle_; + void *address_; + uint64_t size_; + + bool create_file_backed_view(File *file, int64_t offset, int64_t size, + ViewFlags flags); + bool create_anonymous_view(int64_t size, ViewFlags flags); +}; + +} // namespace storage +} // namespace grnxx + +#endif // GRNXX_WINDOWS + +#endif // GRNXX_STORAGE_VIEW_WINDOWS_HPP Added: lib/grnxx/storage/view.cpp (+57 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/view.cpp 2013-04-22 21:40:59 +0900 (72c3987) @@ -0,0 +1,57 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "grnxx/storage/view.hpp" + +#include "grnxx/storage/view-posix.hpp" +#include "grnxx/storage/view-windows.hpp" +#include "grnxx/string_builder.hpp" + +namespace grnxx { +namespace storage { + +#define GRNXX_FLAGS_WRITE(flag) do { \ + if (flags & flag) { \ + if (!is_first) { \ + builder << " | "; \ + } \ + builder << #flag; \ + is_first = false; \ + } \ +} while (false) + +StringBuilder &operator<<(StringBuilder &builder, ViewFlags flags) { + if (flags) { + bool is_first = true; + GRNXX_FLAGS_WRITE(VIEW_ANONYMOUS); + GRNXX_FLAGS_WRITE(VIEW_HUGE_TLB); + GRNXX_FLAGS_WRITE(VIEW_READ_ONLY); + return builder; + } else { + return builder << "0"; + } +} + +View::View() {} +View::~View() {} + +View *View::create(File *file, int64_t offset, int64_t size, ViewFlags flags) { + return ViewImpl::create(file, offset, size, flags); +} + +} // namespace storage +} // namespace grnxx Added: lib/grnxx/storage/view.hpp (+75 -0) 100644 =================================================================== --- /dev/null +++ lib/grnxx/storage/view.hpp 2013-04-22 21:40:59 +0900 (666a45e) @@ -0,0 +1,75 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef GRNXX_STORAGE_VIEW_HPP +#define GRNXX_STORAGE_VIEW_HPP + +#include "grnxx/basic.hpp" +#include "grnxx/flags_impl.hpp" + +namespace grnxx { + +class StringBuilder; + +namespace storage { + +class View; +typedef FlagsImpl<View> ViewFlags; + +// Use the default settings. +constexpr ViewFlags VIEW_DEFAULT = ViewFlags::define(0x00); +// Create an anonymous memory mapping. +// This flag is automatically enabled if "file" == nullptr. +constexpr ViewFlags VIEW_ANONYMOUS = ViewFlags::define(0x01); +// Use huge pages if available, or use regular pages. +constexpr ViewFlags VIEW_HUGE_TLB = ViewFlags::define(0x02); +// Create a read-only memory mapping. +// This flag is automatically enabled if "file" is read-only. +constexpr ViewFlags VIEW_READ_ONLY = ViewFlags::define(0x04); + +StringBuilder &operator<<(StringBuilder &builder, ViewFlags flags); + +class File; + +class View { + public: + View(); + virtual ~View(); + + // Create a file-backed memory mapping on "file" if "file" != nullptr, or + // create an anonymous memory mapping. + // The available flag is VIEW_HUGE_TLB. + static View *create(File *file, + int64_t offset = 0, + int64_t size = -1, + ViewFlags flags = ViewFlags()); + + // Flush modified pages. + virtual bool sync(int64_t offset = 0, int64_t size = -1) = 0; + + // Return the enabled flags. + virtual ViewFlags flags() const = 0; + // Return the starting address. + virtual void *address() const = 0; + // Return the size. + virtual int64_t size() const = 0; +}; + +} // namespace storage +} // namespace grnxx + +#endif // GRNXX_STORAGE_VIEW_HPP Modified: test/Makefile.am (+4 -0) =================================================================== --- test/Makefile.am 2013-04-22 21:29:49 +0900 (95611ab) +++ test/Makefile.am 2013-04-22 21:40:59 +0900 (f6d9ef4) @@ -27,6 +27,7 @@ TESTS = \ test_os \ test_recycler \ test_slice \ + test_storage \ test_string \ test_string_builder \ test_string_format \ @@ -113,6 +114,9 @@ test_recycler_LDADD = ../lib/grnxx/libgrnxx.la test_slice_SOURCES = test_slice.cpp test_slice_LDADD = ../lib/grnxx/libgrnxx.la +test_storage_SOURCES = test_storage.cpp +test_storage_LDADD = ../lib/grnxx/libgrnxx.la + test_string_SOURCES = test_string.cpp test_string_LDADD = ../lib/grnxx/libgrnxx.la Added: test/test_storage.cpp (+243 -0) 100644 =================================================================== --- /dev/null +++ test/test_storage.cpp 2013-04-22 21:40:59 +0900 (8ef498b) @@ -0,0 +1,243 @@ +/* + Copyright (C) 2012-2013 Brazil, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include <cassert> +#include <sstream> + +#include "grnxx/storage/file.hpp" +#include "grnxx/storage/path.hpp" +#include "grnxx/logger.hpp" + +namespace { + +void test_full_path(const char *path, const char *answer) { + std::unique_ptr<char[]> full_path(grnxx::storage::Path::full_path(path)); + assert(full_path); + assert(std::strcmp(full_path.get(), answer) == 0); +} + +void test_full_path() { + std::unique_ptr<char[]> full_path(grnxx::storage::Path::full_path(nullptr)); + assert(full_path); + GRNXX_NOTICE() << "full_path = " << full_path.get(); + + full_path.reset(grnxx::storage::Path::full_path("temp.grn")); + assert(full_path); + GRNXX_NOTICE() << "full_path = " << full_path.get(); + + test_full_path("/", "/"); + test_full_path("/.", "/"); + test_full_path("/..", "/"); + + test_full_path("/usr/local/lib", "/usr/local/lib"); + test_full_path("/usr/local/lib/", "/usr/local/lib/"); + test_full_path("/usr/local/lib/.", "/usr/local/lib"); + test_full_path("/usr/local/lib/./", "/usr/local/lib/"); + test_full_path("/usr/local/lib/..", "/usr/local"); + test_full_path("/usr/local/lib/../", "/usr/local/"); +} + +void test_unique_path() { + std::unique_ptr<char[]> unique_path( + grnxx::storage::Path::unique_path(nullptr)); + assert(unique_path); + GRNXX_NOTICE() << "unique_path = " << unique_path.get(); + + unique_path.reset(grnxx::storage::Path::unique_path("temp.grn")); + GRNXX_NOTICE() << "unique_path = " << unique_path.get(); +} + +void test_file_create() { + const char FILE_PATH[] = "temp.grn"; + grnxx::storage::File::unlink(FILE_PATH); + std::unique_ptr<grnxx::storage::File> file; + + file.reset(grnxx::storage::File::create(FILE_PATH)); + assert(file); + file.reset(grnxx::storage::File::create(FILE_PATH)); + assert(!file); + + file.reset(grnxx::storage::File::create(FILE_PATH, + grnxx::storage::FILE_TEMPORARY)); + assert(file); + file.reset(grnxx::storage::File::create(FILE_PATH, + grnxx::storage::FILE_TEMPORARY)); + assert(file); + + file.reset(grnxx::storage::File::create(nullptr, + grnxx::storage::FILE_TEMPORARY)); + assert(file); + file.reset(grnxx::storage::File::create(nullptr, + grnxx::storage::FILE_TEMPORARY)); + assert(file); + + grnxx::storage::File::unlink(FILE_PATH); +} + +void test_file_open() { + const char FILE_PATH[] = "temp.grn"; + grnxx::storage::File::unlink(FILE_PATH); + std::unique_ptr<grnxx::storage::File> file; + + file.reset(grnxx::storage::File::open(FILE_PATH)); + assert(!file); + + file.reset(grnxx::storage::File::create(FILE_PATH)); + file.reset(grnxx::storage::File::open(FILE_PATH)); + assert(file); + + file.reset(); + grnxx::storage::File::unlink(FILE_PATH); +} + +void test_file_open_or_create() { + const char FILE_PATH[] = "temp.grn"; + grnxx::storage::File::unlink(FILE_PATH); + std::unique_ptr<grnxx::storage::File> file; + + file.reset(grnxx::storage::File::open_or_create(FILE_PATH)); + assert(file); + file.reset(grnxx::storage::File::open_or_create(FILE_PATH)); + assert(file); + + file.reset(); + grnxx::storage::File::unlink(FILE_PATH); +} + +void test_file_exists_and_unlink() { + const char FILE_PATH[] = "temp.grn"; + std::unique_ptr<grnxx::storage::File>( + grnxx::storage::File::open_or_create(FILE_PATH)); + + assert(grnxx::storage::File::exists(FILE_PATH)); + assert(grnxx::storage::File::unlink(FILE_PATH)); + assert(!grnxx::storage::File::unlink(FILE_PATH)); + assert(!grnxx::storage::File::exists(FILE_PATH)); +} + +void test_file_lock_and_unlock() { + const char FILE_PATH[] = "temp.grn"; + std::unique_ptr<grnxx::storage::File> file_1; + file_1.reset(grnxx::storage::File::open_or_create(FILE_PATH)); + assert(file_1); + + assert(file_1->lock(grnxx::storage::FILE_LOCK_SHARED)); + assert(!file_1->lock(grnxx::storage::FILE_LOCK_SHARED)); + assert(file_1->unlock()); + assert(!file_1->unlock()); + + assert(file_1->lock(grnxx::storage::FILE_LOCK_EXCLUSIVE)); + assert(!file_1->lock(grnxx::storage::FILE_LOCK_EXCLUSIVE)); + assert(file_1->unlock()); + assert(!file_1->unlock()); + + std::unique_ptr<grnxx::storage::File> file_2; + file_2.reset(grnxx::storage::File::open(FILE_PATH)); + assert(file_2); + + assert(file_1->lock(grnxx::storage::FILE_LOCK_SHARED)); + assert(file_2->lock(grnxx::storage::FILE_LOCK_SHARED | + grnxx::storage::FILE_LOCK_NONBLOCKING)); + assert(file_2->unlock()); + assert(!file_2->lock(grnxx::storage::FILE_LOCK_EXCLUSIVE | + grnxx::storage::FILE_LOCK_NONBLOCKING)); + assert(file_1->unlock()); + + assert(file_1->lock(grnxx::storage::FILE_LOCK_EXCLUSIVE)); + assert(!file_2->lock(grnxx::storage::FILE_LOCK_SHARED | + grnxx::storage::FILE_LOCK_NONBLOCKING)); + assert(!file_2->lock(grnxx::storage::FILE_LOCK_EXCLUSIVE | + grnxx::storage::FILE_LOCK_NONBLOCKING)); + assert(file_1->unlock()); + + file_1.reset(); + file_2.reset(); + grnxx::storage::File::unlink(FILE_PATH); +} + +void test_file_sync() { + std::unique_ptr<grnxx::storage::File> file( + grnxx::storage::File::create(nullptr, grnxx::storage::FILE_TEMPORARY)); + assert(file); + + assert(file->sync()); +} + +void test_file_resize_and_size() { + std::unique_ptr<grnxx::storage::File> file( + grnxx::storage::File::create(nullptr, grnxx::storage::FILE_TEMPORARY)); + assert(file); + + assert(file->size() == 0); + assert(file->resize(65536)); + assert(file->size() == 65536); + assert(file->resize(1024)); + assert(file->size() == 1024); + assert(!file->resize(-1)); +} + +void test_file_path() { + const char FILE_PATH[] = "temp.grn"; + std::unique_ptr<grnxx::storage::File> file; + + file.reset(grnxx::storage::File::create(FILE_PATH)); + assert(std::strcmp(file->path(), FILE_PATH) == 0); + + file.reset(grnxx::storage::File::create(FILE_PATH, + grnxx::storage::FILE_TEMPORARY)); + assert(std::strcmp(file->path(), FILE_PATH) != 0); + + assert(grnxx::storage::File::unlink(FILE_PATH)); +} + +void test_file_handle() { + std::unique_ptr<grnxx::storage::File> file( + grnxx::storage::File::create(nullptr, grnxx::storage::FILE_TEMPORARY)); + assert(file); + + assert(file->handle()); +} + +void test_path() { + test_full_path(); + test_unique_path(); +} + +void test_file() { + test_file_create(); + test_file_open(); + test_file_open_or_create(); + test_file_exists_and_unlink(); + test_file_lock_and_unlock(); + test_file_sync(); + test_file_resize_and_size(); + test_file_path(); + test_file_handle(); +} + +} // namespace + +int main() { + grnxx::Logger::set_flags(grnxx::LOGGER_WITH_ALL | + grnxx::LOGGER_ENABLE_COUT); + grnxx::Logger::set_max_level(grnxx::NOTICE_LOGGER); + + test_path(); + test_file(); + + return 0; +}