diff --git a/.gitignore b/.gitignore index b829a71..efa382c 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ bin/tests default.profraw bin/tests.profdata bin/tests.profraw +bin/test_n diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2b552f1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "type": "gdb", + "request": "launch", + "target": "./bin/tests", + "cwd": "${workspaceRoot}", + "valuesFormatting": "parseText", + "env": {"LLVM_PROFILE_FILE":"./bin/tests.profraw"} + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile index 397d6e7..aa467f7 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CXX= clang++-8 -CXXFLAGS= --std=c++17 -fprofile-instr-generate -fcoverage-mapping +CXXFLAGS= --std=c++17 -g -fprofile-instr-generate -fcoverage-mapping all: tests @@ -7,7 +7,7 @@ tests: bin/tests LLVM_PROFILE_FILE="./bin/tests.profraw" ./bin/tests @llvm-profdata merge -sparse ./bin/tests.profraw -o ./bin/tests.profdata @llvm-cov report ./bin/tests -instr-profile=./bin/tests.profdata $(wildcard ./tests/*.cpp) include/*.hpp - @llvm-cov report ./bin/tests -instr-profile=./bin/tests.profdata $(wildcard ./tests/*.cpp) include/*.hpp | tail -n 1 | tr -s "\t" | awk '{print "$8" }' + @llvm-cov report ./bin/tests -instr-profile=./bin/tests.profdata ./tests/*.cpp include/*.hpp | tail -n 1 | tr -s " " | sed -e 's/ /,/g' -- | awk -F "," '{print $$9}' | sed -e 's/^/Untested lines: /g' bin/tests: tests.cpp $(wildcard tests/*.cpp) ./tests/test_scaffold.h @mkdir -p $(@D) diff --git a/include/shared_fd.hpp b/include/shared_fd.hpp index b55f6a3..6bc81a8 100644 --- a/include/shared_fd.hpp +++ b/include/shared_fd.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include namespace gp{ using open_opt_flags = int; @@ -18,6 +20,8 @@ namespace gp{ exclusive = O_EXCL, no_access_time = O_NOATIME, no_follow = O_NOFOLLOW, + read = O_RDONLY, + write = O_WRONLY, #ifdef O_TEMP temporary = O_TEMP, #endif @@ -44,13 +48,111 @@ namespace gp{ }; class shared_fd { - const int fd; + int fd; + std::atomic_int* guard; + int last_error; shared_fd(int fd_v) - : fd(fd_v) {} + : fd(fd_v) + , guard(new std::atomic_int{1}) + , last_error(0) + {} public: - static shared_fd open(const std::string& filename, const open_opt_flags& flags) { - + shared_fd() + : fd(-1) + , guard(nullptr) + , last_error(0) + {} + + shared_fd(shared_fd& oth) + : fd(oth.fd) + , guard(oth.guard) + , last_error(0) + { + if(guard) + guard->fetch_add(1, std::memory_order_acq_rel); + } + + shared_fd(shared_fd&& oth) + : last_error(0) + { + fd=oth.fd; + guard=oth.guard; + oth.fd=-1; + oth.guard=nullptr; + } + + void operator=(shared_fd& oth) + { + this->~shared_fd(); + fd = oth.fd; + guard = oth.guard; + if(guard) + guard->fetch_add(1, std::memory_order_acq_rel); + } + + void operator=(shared_fd&& oth) + { + std::swap(fd,oth.fd); + std::swap(guard,oth.guard); + } + + static shared_fd open(const std::string& filename, const open_opt_flags& flags, const open_opt_mode& mode = 0) + { + shared_fd ret{::open(filename.c_str(), flags, mode)}; + return ret; + } + + static shared_fd create(const std::string& filename, const open_opt_mode& mode) + { + shared_fd ret{::creat(filename.c_str(), mode)}; + return ret; + } + + bool is_valid() const { + return guard && (fd>=0); + } + + bool has_failed() const { + return last_error; + } + + std::string_view read(const std::string_view buffer) + { + int sz = ::read(fd, (void*)(buffer.begin()), buffer.size()); + if(sz<0) + last_error = errno; + else + last_error = 0; + sz = sz<0?0:sz; + return std::string_view(buffer.begin(), sz); + } + + std::string_view write(const std::string_view buffer) + { + int sz = ::write(fd, (void*)(buffer.begin()), buffer.size()); + if(sz<0) + last_error = errno; + else + last_error = 0; + sz = sz<0?0:sz; + return std::string_view(buffer.begin()+sz, buffer.size()-sz); + } + + ~shared_fd() + { + if(guard) + if(guard->fetch_sub(1, std::memory_order_acq_rel) == 1) + { + assert(guard->load() == 0); + assert(fd >= 0); + delete guard; + guard = nullptr; + if(fd >= 0) + { + ::close(fd); + } + } } }; } \ No newline at end of file diff --git a/tests.cpp b/tests.cpp index b342b0c..bcd1ca7 100644 --- a/tests.cpp +++ b/tests.cpp @@ -1,5 +1,6 @@ #include "test_scaffold.h" #include "meta_test.cpp" +#include "shared_fd.cpp" #include int main() diff --git a/tests/shared_fd.cpp b/tests/shared_fd.cpp new file mode 100644 index 0000000..13d6b01 --- /dev/null +++ b/tests/shared_fd.cpp @@ -0,0 +1,124 @@ +#include "shared_fd.hpp" +#include "test_scaffold.h" + + +struct create_test : public test_scaffold { + create_test() { + name = __FILE__ ":1"; + } + + virtual int run() { + auto fd = gp::shared_fd::create("./bin/test_n", int(gp::open_modes::user_read) | int(gp::open_modes::user_write)); + + return fd.is_valid()?0:1; + } +}; + +append_test dummy_sdfhuisd3(new create_test{}); + + +struct open_test : public test_scaffold { + open_test() { + name = __FILE__ ":2"; + } + + virtual int run() { + auto fd = gp::shared_fd::open("./bin/test_n", int(gp::open_options::append)); + + return fd.is_valid()?0:1; + } +}; + +append_test dummy_sdf564dd3(new open_test{}); + + +struct manip_test : public test_scaffold { + manip_test() { + name = __FILE__ ":3"; + } + + virtual int run() { + auto fd = gp::shared_fd::open("./bin/test_n", int(gp::open_options::append)); + + int error = fd.is_valid()?0:1; + + auto fd_cpy = fd; + error += fd_cpy.is_valid()?0:1; + + gp::shared_fd constr_cpy(fd_cpy); + error += constr_cpy.is_valid()?0:1; + + gp::shared_fd constr_mv(std::move(constr_cpy)); + error += constr_mv.is_valid()?0:1; + + gp::shared_fd assign_cpy; + assign_cpy.operator=(fd_cpy); + error += assign_cpy.is_valid()?0:1; + + gp::shared_fd assign_mv; + assign_mv.operator=(std::move(assign_cpy)); + error += assign_mv.is_valid()?0:1; + error += (!assign_cpy.is_valid())?0:1; + return error; + } +}; + +append_test dummy_lkjs64dd3(new manip_test{}); + + +struct rw_test : public test_scaffold { + rw_test() { + name = __FILE__ ":4"; + } + + virtual int run() { + auto fd = gp::shared_fd::open("./bin/test_n", int(gp::open_options::write)); + + int error = fd.is_valid()?0:1; + + fd.write("potatoes"); + error += fd.has_failed(); + + fd = gp::shared_fd::open("./bin/test_n", int(gp::open_options::read)); + + std::array buffer; + + auto str = fd.read(std::string_view(buffer.begin(), buffer.size())); + error += fd.has_failed(); + + error += (str != "potatoes"); + + return error; + } +}; + +append_test dummy_l6z5e4rdd3(new rw_test{}); + + +struct rw_err_test : public test_scaffold { + rw_err_test() { + name = __FILE__ ":5"; + } + + virtual int run() { + auto fd = gp::shared_fd::create("./bin/test_n", int(gp::open_modes::user_read) | int(gp::open_modes::user_write)); + fd = gp::shared_fd::open("./bin/test_n", int(gp::open_options::read)); + + int error = fd.is_valid()?0:1; + + fd.write("potatoes"); + error += fd.has_failed(); + + fd = gp::shared_fd::open("./bin/test_n", int(gp::open_options::write)); + error += fd.is_valid()?0:1; + + std::array buffer; + + auto str = fd.read(std::string_view(buffer.begin(), buffer.size())); + error += fd.has_failed(); + + return error == 2 ? 0 : 1; + } +}; + +append_test dummy_l6987erd3(new rw_err_test{}); \ No newline at end of file