├── .clang-format ├── format.sh ├── src ├── base │ ├── FileUtils.cpp │ ├── EasyLoggingWrapper.cpp │ ├── TimeHandler.cpp │ ├── FileUtils.hpp │ ├── DaemonCreator.hpp │ ├── LogHandler.hpp │ ├── ZmqBiDirectionalRpc.hpp │ ├── TimeHandler.hpp │ ├── RpcId.hpp │ ├── PidController.hpp │ ├── PidController.cpp │ ├── DaemonCreator.cpp │ ├── MessageWriter.hpp │ ├── MessageReader.hpp │ ├── FileSystem.cpp │ ├── LogHandler.cpp │ ├── ZmqBiDirectionalRpc.cpp │ ├── BiDirectionalRpc.hpp │ ├── FileSystem.hpp │ ├── Headers.hpp │ ├── BiDirectionalRpc.cpp │ └── zmq_addon.hpp ├── client │ ├── ClientFuseAdapter.hpp │ ├── Client.hpp │ ├── Main.cpp │ ├── ClientFileSystem.hpp │ ├── ClientFuseAdapter.cpp │ └── Client.cpp └── server │ ├── Server.hpp │ ├── fswatchexample.cpp │ ├── ServerFileSystem.hpp │ ├── Main.cpp │ ├── ServerFileSystem.cpp │ └── Server.cpp ├── fio └── basic_verify.fio ├── coverage.sh ├── .gitignore ├── cmake ├── FindZeroMQ.cmake ├── Findfswatch.cmake ├── FindGFlags.cmake ├── FindOSXFuse.cmake └── FindLibFUSE.cmake ├── test ├── TestMain.cpp └── TestRpc.cpp ├── .gitmodules ├── proto └── CodeFS.proto ├── README.md ├── external └── fswatch_config │ └── libfswatch_config.h ├── CMakeLists.txt └── LICENSE /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | find ./ -type f | grep "\.[hc]pp" | grep -v /ext/ | grep -v /external/ | grep -v /build/ | xargs clang-format --style=Google -i 3 | -------------------------------------------------------------------------------- /src/base/FileUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "FileUtils.hpp" 2 | 3 | void FileUtils::touch(const string& path) { 4 | FILE *fp = ::fopen(path.c_str(), "ab+"); 5 | ::fclose(fp); 6 | } -------------------------------------------------------------------------------- /src/base/EasyLoggingWrapper.cpp: -------------------------------------------------------------------------------- 1 | // Include headers to get the right #defines 2 | #include "Headers.hpp" 3 | 4 | // Now include easylogging++.cc 5 | #include "easyloggingpp/src/easylogging++.cc" -------------------------------------------------------------------------------- /src/base/TimeHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "TimeHandler.hpp" 2 | 3 | namespace codefs { 4 | std::chrono::time_point 5 | TimeHandler::initialTime = std::chrono::high_resolution_clock::now(); 6 | } -------------------------------------------------------------------------------- /src/base/FileUtils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FILE_UTILS_H__ 2 | #define __FILE_UTILS_H__ 3 | 4 | #include "Headers.hpp" 5 | 6 | class FileUtils { 7 | public: 8 | static void touch(const string& path); 9 | }; 10 | 11 | #endif // __FILE_UTILS_H__ -------------------------------------------------------------------------------- /fio/basic_verify.fio: -------------------------------------------------------------------------------- 1 | # The most basic form of data verification. Write the device randomly 2 | # in 4K chunks, then read it back and verify the contents. 3 | [write-and-verify] 4 | rw=randwrite 5 | bs=4k 6 | direct=1 7 | ioengine=libaio 8 | iodepth=16 9 | verify=crc32c 10 | size=1G -------------------------------------------------------------------------------- /src/base/DaemonCreator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __DAEMON_CREATOR_H__ 2 | #define __DAEMON_CREATOR_H__ 3 | 4 | #include "Headers.hpp" 5 | 6 | namespace et { 7 | class DaemonCreator { 8 | public: 9 | static int create(); 10 | static const int PARENT = 1; 11 | static const int CHILD = 2; 12 | }; 13 | } // namespace et 14 | 15 | #endif // __DAEMON_CREATOR_H__ 16 | -------------------------------------------------------------------------------- /coverage.sh: -------------------------------------------------------------------------------- 1 | pushd ./build 2 | cmake ../ -DBUILD_TEST=ON -DBUILD_GTEST=ON -DCODE_COVERAGE=ON 3 | make -j8 4 | find . -name "*.gcda" -print0 | xargs -0 rm 5 | popd 6 | ./build/test/et-test 7 | lcov --directory ./build --capture --output-file ./code-coverage.info -rc lcov_branch_coverage=1 8 | genhtml code-coverage.info --branch-coverage --output-directory ./code_coverage_report/ 9 | echo "Report generated in code_coverage_report" 10 | open code_coverage_report/index.html 11 | -------------------------------------------------------------------------------- /src/client/ClientFuseAdapter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CLIENT_FUSE_ADAPTER_H__ 2 | #define __CLIENT_FUSE_ADAPTER_H__ 3 | 4 | #include "Headers.hpp" 5 | 6 | namespace codefs { 7 | class ClientFileSystem; 8 | class Client; 9 | 10 | class ClientFuseAdapter { 11 | public: 12 | virtual void assignCallbacks(shared_ptr _fileSystem, 13 | shared_ptr _client, 14 | fuse_operations* ops); 15 | }; 16 | } // namespace codefs 17 | 18 | #endif // __CLIENT_FUSE_ADAPTER_H__ -------------------------------------------------------------------------------- /src/base/LogHandler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CODEFS_LOG_HANDLER__ 2 | #define __CODEFS_LOG_HANDLER__ 3 | 4 | #include "Headers.hpp" 5 | 6 | namespace codefs { 7 | class LogHandler { 8 | public: 9 | static el::Configurations setupLogHandler(int *argc, char ***argv); 10 | static void setupLogFile(el::Configurations *defaultConf, string filename, 11 | string maxlogsize = "20971520"); 12 | static void rolloutHandler(const char *filename, std::size_t size); 13 | static string stderrToFile(const string &pathPrefix); 14 | }; 15 | } // namespace codefs 16 | #endif // __CODEFS_LOG_HANDLER__ 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Code Coverage 2 | code-coverage.info 3 | code_coverage_report 4 | 5 | # VSCode 6 | .vscode 7 | 8 | # OS/X Junk file 9 | .DS_Store 10 | 11 | # Cmake build dir 12 | build 13 | 14 | # Generated by cmake protobuf 15 | Makefile 16 | 17 | # GTags 18 | GPATH 19 | GRTAGS 20 | GTAGS 21 | 22 | # Compiled Object files 23 | *.slo 24 | *.lo 25 | *.o 26 | *.obj 27 | 28 | # Precompiled Headers 29 | *.gch 30 | *.pch 31 | 32 | # Compiled Dynamic libraries 33 | *.so 34 | *.dylib 35 | *.dll 36 | 37 | # Fortran module files 38 | *.mod 39 | *.smod 40 | 41 | # Compiled Static libraries 42 | *.lai 43 | *.la 44 | *.a 45 | *.lib 46 | 47 | # Executables 48 | *.exe 49 | *.out 50 | *.app 51 | -------------------------------------------------------------------------------- /cmake/FindZeroMQ.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find ZMQ 2 | # Once done this will define 3 | # ZMQ_FOUND - System has ZMQ 4 | # ZMQ_INCLUDE_DIRS - The ZMQ include directories 5 | # ZMQ_LIBRARIES - The libraries needed to use ZMQ 6 | # ZMQ_DEFINITIONS - Compiler switches required for using ZMQ 7 | 8 | find_path ( ZMQ_INCLUDE_DIR zmq.h ) 9 | find_library ( ZMQ_LIBRARY NAMES zmq ) 10 | 11 | set ( ZMQ_LIBRARIES ${ZMQ_LIBRARY} ) 12 | set ( ZMQ_INCLUDE_DIRS ${ZMQ_INCLUDE_DIR} ) 13 | 14 | include ( FindPackageHandleStandardArgs ) 15 | # handle the QUIETLY and REQUIRED arguments and set ZMQ_FOUND to TRUE 16 | # if all listed variables are TRUE 17 | find_package_handle_standard_args ( ZMQ DEFAULT_MSG ZMQ_LIBRARY ZMQ_INCLUDE_DIR ) 18 | 19 | -------------------------------------------------------------------------------- /src/base/ZmqBiDirectionalRpc.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __ZMQ_BIDIRECTIONAL_RPC_H__ 2 | #define __ZMQ_BIDIRECTIONAL_RPC_H__ 3 | 4 | #include "BiDirectionalRpc.hpp" 5 | 6 | namespace codefs { 7 | class ZmqBiDirectionalRpc : public BiDirectionalRpc { 8 | public: 9 | ZmqBiDirectionalRpc(const string& address, bool bind); 10 | virtual ~ZmqBiDirectionalRpc(); 11 | void shutdown(); 12 | void update(); 13 | 14 | void reconnect(); 15 | 16 | protected: 17 | shared_ptr context; 18 | shared_ptr socket; 19 | 20 | zmq::message_t clientIdentity; 21 | 22 | string address; 23 | bool bind; 24 | 25 | virtual void send(const string& message); 26 | }; 27 | } // namespace codefs 28 | 29 | #endif // __BIDIRECTIONAL_RPC_H__ -------------------------------------------------------------------------------- /src/base/TimeHandler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __TIME_HANDLER_H__ 2 | #define __TIME_HANDLER_H__ 3 | 4 | #include "Headers.hpp" 5 | 6 | namespace codefs { 7 | class TimeHandler { 8 | public: 9 | static int64_t currentTimeMs() { 10 | return std::chrono::duration_cast( 11 | std::chrono::high_resolution_clock::now() - initialTime) 12 | .count(); 13 | } 14 | 15 | static int64_t currentTimeMicros() { 16 | return std::chrono::duration_cast( 17 | std::chrono::high_resolution_clock::now() - initialTime) 18 | .count(); 19 | } 20 | 21 | static std::chrono::time_point 22 | initialTime; 23 | }; 24 | } // namespace codefs 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /cmake/Findfswatch.cmake: -------------------------------------------------------------------------------- 1 | # Findfswatch 2 | # ----------- 3 | # 4 | # Try to find the fswatch file system watcher library 5 | # 6 | # Defined variables once found: 7 | # 8 | # :: 9 | # 10 | # fswatch_FOUND - System has fswatch 11 | # fswatch_INCLUDE_DIRS - The fswatch include directory 12 | # fswatch_LIBRARIES - The libraries needed to use fswatch 13 | 14 | find_path( 15 | fswatch_INCLUDE_DIRS 16 | NAMES libfswatch/c++/monitor.hpp 17 | ) 18 | 19 | mark_as_advanced(fswatch_INCLUDE_DIRS) 20 | 21 | find_library( 22 | fswatch_LIBRARIES 23 | NAMES fswatch 24 | ) 25 | 26 | mark_as_advanced(fswatch_LIBRARY) 27 | 28 | find_package_handle_standard_args( 29 | fswatch 30 | FOUND_VAR fswatch_FOUND 31 | REQUIRED_VARS fswatch_INCLUDE_DIRS fswatch_LIBRARIES 32 | ) -------------------------------------------------------------------------------- /src/base/RpcId.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __RPC_ID_H__ 2 | #define __RPC_ID_H__ 3 | 4 | #include "Headers.hpp" 5 | 6 | namespace codefs { 7 | class RpcId { 8 | public: 9 | RpcId() : barrier(0), id(0) {} 10 | RpcId(int64_t _barrier, uint64_t _id) : barrier(_barrier), id(_id) {} 11 | bool operator==(const RpcId& other) const { 12 | return barrier == other.barrier && id == other.id; 13 | } 14 | bool operator<(const RpcId& other) const { 15 | return barrier < other.barrier || 16 | (barrier == other.barrier && id < other.id); 17 | } 18 | string str() const { return to_string(barrier) + "/" + to_string(id); } 19 | bool empty() { return barrier == 0 && id == 0; } 20 | 21 | int64_t barrier; 22 | uint64_t id; 23 | }; 24 | 25 | } // namespace codefs 26 | 27 | #endif -------------------------------------------------------------------------------- /src/base/PidController.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __PID_CONTROLLER_H__ 2 | #define __PID_CONTROLLER_H__ 3 | 4 | #include "Headers.hpp" 5 | 6 | class PidController { 7 | public: 8 | // Kp - proportional gain 9 | // Ki - Integral gain 10 | // Kd - derivative gain 11 | // dt - loop interval time 12 | // max - maximum value of manipulated variable 13 | // min - minimum value of manipulated variable 14 | PidController(double dt, double max, double min, double Kp, double Kd, 15 | double Ki); 16 | 17 | // Returns the manipulated variable given a setpoint and current process value 18 | double calculate(double setpoint, double pv); 19 | ~PidController(); 20 | 21 | private: 22 | double _dt; 23 | double _max; 24 | double _min; 25 | double _Kp; 26 | double _Kd; 27 | double _Ki; 28 | double _pre_error; 29 | double _integral; 30 | }; 31 | 32 | #endif -------------------------------------------------------------------------------- /src/base/PidController.cpp: -------------------------------------------------------------------------------- 1 | #include "PidController.hpp" 2 | 3 | PidController::PidController(double dt, double max, double min, double Kp, 4 | double Kd, double Ki) 5 | : _dt(dt), 6 | _max(max), 7 | _min(min), 8 | _Kp(Kp), 9 | _Kd(Kd), 10 | _Ki(Ki), 11 | _pre_error(0), 12 | _integral(0) {} 13 | 14 | double PidController::calculate(double setpoint, double pv) { 15 | // Calculate error 16 | double error = setpoint - pv; 17 | 18 | // Proportional term 19 | double Pout = _Kp * error; 20 | 21 | // Integral term 22 | _integral += error * _dt; 23 | double Iout = _Ki * _integral; 24 | 25 | // Derivative term 26 | double derivative = (error - _pre_error) / _dt; 27 | double Dout = _Kd * derivative; 28 | 29 | // Calculate total output 30 | double output = Pout + Iout + Dout; 31 | 32 | // Restrict to max/min 33 | if (output > _max) 34 | output = _max; 35 | else if (output < _min) 36 | output = _min; 37 | 38 | // Save error to previous error 39 | _pre_error = error; 40 | 41 | return output; 42 | } 43 | 44 | PidController::~PidController() {} 45 | -------------------------------------------------------------------------------- /src/server/Server.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CODEFS_SERVER_HPP__ 2 | #define __CODEFS_SERVER_HPP__ 3 | 4 | #include "Headers.hpp" 5 | 6 | #include "MessageReader.hpp" 7 | #include "MessageWriter.hpp" 8 | #include "ServerFileSystem.hpp" 9 | #include "ZmqBiDirectionalRpc.hpp" 10 | 11 | namespace codefs { 12 | class Server : public ServerFileSystem::Handler { 13 | public: 14 | Server(const string& address, shared_ptr _fileSystem); 15 | 16 | virtual ~Server() {} 17 | 18 | void init(); 19 | int update(); 20 | inline void heartbeat() { rpc->heartbeat(); } 21 | 22 | virtual void metadataUpdated(const string& path, const FileData& fileData); 23 | 24 | protected: 25 | RpcId request(const string& payload) { 26 | lock_guard lock(rpcMutex); 27 | return rpc->request(payload); 28 | } 29 | void reply(const RpcId& rpcId, const string& payload) { 30 | lock_guard lock(rpcMutex); 31 | rpc->reply(rpcId, payload); 32 | } 33 | 34 | string address; 35 | shared_ptr rpc; 36 | int port; 37 | shared_ptr fileSystem; 38 | int clientFd; 39 | recursive_mutex rpcMutex; 40 | }; 41 | } // namespace codefs 42 | 43 | #endif // __CODEFS_SERVER_HPP__ -------------------------------------------------------------------------------- /src/base/DaemonCreator.cpp: -------------------------------------------------------------------------------- 1 | #include "DaemonCreator.hpp" 2 | 3 | namespace et { 4 | int DaemonCreator::create() { 5 | pid_t pid; 6 | 7 | /* Fork off the parent process */ 8 | pid = fork(); 9 | 10 | /* An error occurred */ 11 | if (pid < 0) exit(EXIT_FAILURE); 12 | 13 | /* Success: Return so the parent can continue */ 14 | if (pid > 0) return PARENT; 15 | 16 | /* On success: The child process becomes session leader */ 17 | if (setsid() < 0) exit(EXIT_FAILURE); 18 | 19 | /* Catch, ignore and handle signals */ 20 | // TODO: Implement a working signal handler */ 21 | signal(SIGCHLD, SIG_IGN); 22 | signal(SIGHUP, SIG_IGN); 23 | 24 | /* Fork off for the second time*/ 25 | pid = fork(); 26 | 27 | /* An error occurred */ 28 | if (pid < 0) exit(EXIT_FAILURE); 29 | 30 | /* Success: Let the parent terminate */ 31 | if (pid > 0) exit(EXIT_SUCCESS); 32 | 33 | /* Set new file permissions */ 34 | umask(0); 35 | 36 | /* Change the working directory to the root directory */ 37 | /* or another appropriated directory */ 38 | chdir("/"); 39 | 40 | /* Close all open file descriptors */ 41 | int x; 42 | for (x = sysconf(_SC_OPEN_MAX); x >= 0; x--) { 43 | close(x); 44 | } 45 | return CHILD; 46 | } 47 | } // namespace et -------------------------------------------------------------------------------- /src/server/fswatchexample.cpp: -------------------------------------------------------------------------------- 1 | #include "Headers.hpp" 2 | 3 | void cb(const std::vector &events, void *context) { 4 | cout << "GOT EVENT: " << events.size() << endl; 5 | for (const auto &it : events) { 6 | cout << it.get_path() << " " << it.get_time() << " ("; 7 | for (const fsw_event_flag &it2 : it.get_flags()) { 8 | cout << fsw_get_event_flag_name(it2) << ", "; 9 | } 10 | cout << ")" << endl; 11 | } 12 | } 13 | 14 | int mmain(int argc, char **argv) { 15 | // Create the default platform monitor 16 | fsw::monitor *active_monitor = fsw::monitor_factory::create_monitor( 17 | fsw_monitor_type::system_default_monitor_type, {"/usr/local"}, cb); 18 | 19 | // Configure the monitor 20 | // active_monitor->set_properties(monitor_properties); 21 | active_monitor->set_allow_overflow(true); 22 | // active_monitor->set_latency(latency); 23 | active_monitor->set_recursive(true); 24 | // active_monitor->set_directory_only(directory_only); 25 | // active_monitor->set_event_type_filters(event_filters); 26 | // active_monitor->set_filters(filters); 27 | active_monitor->set_follow_symlinks(true); 28 | // active_monitor->set_watch_access(watch_access); 29 | 30 | // Start the monitor 31 | active_monitor->start(); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /src/base/MessageWriter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __MESSAGE_WRITER_H__ 2 | #define __MESSAGE_WRITER_H__ 3 | 4 | #include "Headers.hpp" 5 | 6 | namespace codefs { 7 | class MessageWriter { 8 | public: 9 | MessageWriter() : packHandler(buffer) {} 10 | 11 | inline void start() { buffer.clear(); } 12 | 13 | template 14 | inline void writePrimitive(const T& t) { 15 | packHandler.pack(t); 16 | } 17 | 18 | template 19 | inline void writeMap(const map& m) { 20 | packHandler.pack_map(m.size()); 21 | for (auto& it : m) { 22 | writePrimitive(it.first); 23 | writePrimitive(it.second); 24 | } 25 | } 26 | 27 | template 28 | inline void writeClass(const T& t) { 29 | string s(sizeof(T), '\0'); 30 | memcpy(&s[0], &t, sizeof(T)); 31 | writePrimitive(s); 32 | } 33 | 34 | template 35 | inline void writeProto(const T& t) { 36 | string s; 37 | t.SerializeToString(&s); 38 | writePrimitive(s); 39 | } 40 | 41 | inline string finish() { 42 | string s(buffer.data(), buffer.size()); 43 | start(); 44 | return s; 45 | } 46 | 47 | inline int64_t size() { return buffer.size(); } 48 | 49 | protected: 50 | msgpack::sbuffer buffer; 51 | msgpack::packer packHandler; 52 | }; 53 | } // namespace codefs 54 | 55 | #endif // __MESSAGE_WRITER_H__ -------------------------------------------------------------------------------- /cmake/FindGFlags.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find GFLAGS 2 | # 3 | # The following variables are optionally searched for defaults 4 | # GFLAGS_ROOT_DIR: Base directory where all GFLAGS components are found 5 | # 6 | # The following are set after configuration is done: 7 | # GFLAGS_FOUND 8 | # GFLAGS_INCLUDE_DIRS 9 | # GFLAGS_LIBRARIES 10 | # GFLAGS_LIBRARYRARY_DIRS 11 | 12 | include(FindPackageHandleStandardArgs) 13 | 14 | set(GFLAGS_ROOT_DIR "" CACHE PATH "Folder contains Gflags") 15 | 16 | # We are testing only a couple of files in the include directories 17 | if(WIN32) 18 | find_path(GFLAGS_INCLUDE_DIR gflags/gflags.h 19 | PATHS ${GFLAGS_ROOT_DIR}/src/windows) 20 | else() 21 | find_path(GFLAGS_INCLUDE_DIR gflags/gflags.h 22 | PATHS ${GFLAGS_ROOT_DIR}) 23 | endif() 24 | 25 | if(MSVC) 26 | find_library(GFLAGS_LIBRARY_RELEASE 27 | NAMES libgflags 28 | PATHS ${GFLAGS_ROOT_DIR} 29 | PATH_SUFFIXES Release) 30 | 31 | find_library(GFLAGS_LIBRARY_DEBUG 32 | NAMES libgflags-debug 33 | PATHS ${GFLAGS_ROOT_DIR} 34 | PATH_SUFFIXES Debug) 35 | 36 | set(GFLAGS_LIBRARY optimized ${GFLAGS_LIBRARY_RELEASE} debug ${GFLAGS_LIBRARY_DEBUG}) 37 | else() 38 | find_library(GFLAGS_LIBRARY gflags) 39 | endif() 40 | 41 | find_package_handle_standard_args(GFLAGS DEFAULT_MSG 42 | GFLAGS_INCLUDE_DIR GFLAGS_LIBRARY) 43 | 44 | 45 | if(GFLAGS_FOUND) 46 | set(GFLAGS_INCLUDE_DIRS ${GFLAGS_INCLUDE_DIR}) 47 | set(GFLAGS_LIBRARIES ${GFLAGS_LIBRARY}) 48 | endif() 49 | -------------------------------------------------------------------------------- /test/TestMain.cpp: -------------------------------------------------------------------------------- 1 | #include "Headers.hpp" 2 | 3 | #include "LogHandler.hpp" 4 | 5 | #define CATCH_CONFIG_RUNNER 6 | #include "Catch2/single_include/catch2/catch.hpp" 7 | 8 | namespace codefs { 9 | int main(int argc, char** argv) { 10 | srand(1); 11 | 12 | // Setup easylogging configurations 13 | el::Configurations defaultConf = LogHandler::setupLogHandler(&argc, &argv); 14 | defaultConf.setGlobally(el::ConfigurationType::ToStandardOutput, "true"); 15 | defaultConf.setGlobally(el::ConfigurationType::ToFile, "true"); 16 | // el::Loggers::setVerboseLevel(9); 17 | 18 | string stderrPathPrefix = 19 | string("/tmp/et_test_") + to_string(rand()) + string("_"); 20 | string stderrPath = LogHandler::stderrToFile(stderrPathPrefix); 21 | cout << "Writing stderr to " << stderrPath << endl; 22 | 23 | string logDirectoryPattern = string("/tmp/et_test_XXXXXXXX"); 24 | string logDirectory = string(mkdtemp(&logDirectoryPattern[0])); 25 | string logPath = string(logDirectory) + "/log"; 26 | cout << "Writing log to " << logPath << endl; 27 | LogHandler::setupLogFile(&defaultConf, logPath); 28 | 29 | // Reconfigure default logger to apply settings above 30 | el::Loggers::reconfigureLogger("default", defaultConf); 31 | 32 | int result = Catch::Session().run(argc, argv); 33 | 34 | FATAL_FAIL(::remove(stderrPath.c_str())); 35 | FATAL_FAIL(::remove(logPath.c_str())); 36 | FATAL_FAIL(::remove(logDirectory.c_str())); 37 | return result; 38 | } 39 | } // namespace codefs 40 | 41 | int main(int argc, char** argv) { return codefs::main(argc, argv); } 42 | -------------------------------------------------------------------------------- /cmake/FindOSXFuse.cmake: -------------------------------------------------------------------------------- 1 | # Find the osxfuse includes and library 2 | # 3 | # OSXFUSE_INCLUDE_DIR - where to find fuse.h, etc. 4 | # OSXFUSE_LIBRARIES - List of libraries when using osxfuse. 5 | # OSXFUSE_FOUND - True if osxfuse lib is found. 6 | 7 | # check if already in cache, be silent 8 | IF (OSXFUSE_INCLUDE_DIR) 9 | SET (OSXFUSE_FIND_QUIETLY TRUE) 10 | ENDIF (OSXFUSE_INCLUDE_DIR) 11 | 12 | # find includes 13 | FIND_PATH (OSXFUSE_INCLUDE_DIR fuse.h 14 | /usr/local/include 15 | /usr/include 16 | /usr/local/include/osxfuse 17 | ) 18 | 19 | # find lib 20 | SET(OSXFUSE_NAMES osxfuse) 21 | FIND_LIBRARY(OSXFUSE_LIBRARY 22 | NAMES ${OSXFUSE_NAMES} 23 | PATHS /usr/lib /usr/local/lib osxfuse 24 | NO_DEFAULT_PATH 25 | ) 26 | 27 | # check if lib was found and include is present 28 | IF (OSXFUSE_INCLUDE_DIR AND OSXFUSE_LIBRARY) 29 | SET (OSXFUSE_FOUND TRUE) 30 | SET (OSXFUSE_LIBRARIES ${OSXFUSE_LIBRARY}) 31 | ELSE (OSXFUSE_INCLUDE_DIR AND OSXFUSE_LIBRARY) 32 | SET (OSXFUSE_FOUND FALSE) 33 | SET (OSXFUSE_LIBRARIES) 34 | ENDIF (OSXFUSE_INCLUDE_DIR AND OSXFUSE_LIBRARY) 35 | 36 | # let world know the results 37 | IF (OSXFUSE_FOUND) 38 | IF (NOT OSXFUSE_FIND_QUIETLY) 39 | MESSAGE(STATUS "Found osxfuse: ${OSXFUSE_LIBRARY}") 40 | ENDIF (NOT OSXFUSE_FIND_QUIETLY) 41 | ELSE (OSXFUSE_FOUND) 42 | IF (OSXFUSE_FIND_REQUIRED) 43 | MESSAGE(STATUS "Looked for osxfuse libraries named ${OSXFUSE_NAMES}.") 44 | MESSAGE(FATAL_ERROR "Could NOT find osxfuse library") 45 | ENDIF (OSXFUSE_FIND_REQUIRED) 46 | ENDIF (OSXFUSE_FOUND) 47 | 48 | mark_as_advanced (OSXFUSE_INCLUDE_DIR OSXFUSE_LIBRARY) -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/Optional"] 2 | path = external/Optional 3 | url = https://github.com/akrzemi1/Optional.git 4 | [submodule "external/simpleini"] 5 | path = external/simpleini 6 | url = https://github.com/brofield/simpleini.git 7 | [submodule "external/CTPL"] 8 | path = external/CTPL 9 | url = https://github.com/vit-vit/CTPL.git 10 | [submodule "external/asio"] 11 | path = external/asio 12 | url = https://github.com/chriskohlhoff/asio.git 13 | [submodule "external/Catch2"] 14 | path = external/Catch2 15 | url = https://github.com/catchorg/Catch2.git 16 | [submodule "external/cxxopts"] 17 | path = external/cxxopts 18 | url = https://github.com/jarro2783/cxxopts.git 19 | [submodule "external/fmt"] 20 | path = external/fmt 21 | url = https://github.com/fmtlib/fmt.git 22 | [submodule "external/easyloggingpp"] 23 | path = external/easyloggingpp 24 | url = https://github.com/zuhd-org/easyloggingpp.git 25 | [submodule "external/cppcodec"] 26 | path = external/cppcodec 27 | url = https://github.com/tplgy/cppcodec.git 28 | [submodule "external/json"] 29 | path = external/json 30 | url = https://github.com/nlohmann/json.git 31 | [submodule "external/sole"] 32 | path = external/sole 33 | url = https://github.com/r-lyeh-archived/sole.git 34 | [submodule "external/msgpack-c"] 35 | path = external/msgpack-c 36 | url = https://github.com/msgpack/msgpack-c.git 37 | [submodule "external/UniversalStacktrace"] 38 | path = external/UniversalStacktrace 39 | url = https://github.com/MisterTea/UniversalStacktrace.git 40 | [submodule "external/sanitizers-cmake"] 41 | path = external/sanitizers-cmake 42 | url = git://github.com/arsenm/sanitizers-cmake.git 43 | [submodule "external/cotire"] 44 | path = external/cotire 45 | url = https://github.com/sakra/cotire.git 46 | [submodule "external/fswatch"] 47 | path = external/fswatch 48 | url = https://github.com/emcrisostomo/fswatch.git 49 | -------------------------------------------------------------------------------- /cmake/FindLibFUSE.cmake: -------------------------------------------------------------------------------- 1 | # Try pkg-config first 2 | find_package(PkgConfig) 3 | pkg_check_modules(PKGC_LIBFUSE QUIET fuse) 4 | 5 | if(PKGC_LIBFUSE_FOUND) 6 | # Found lib using pkg-config. 7 | if(CMAKE_DEBUG) 8 | message(STATUS "\${PKGC_LIBFUSE_LIBRARIES} = ${PKGC_LIBFUSE_LIBRARIES}") 9 | message(STATUS "\${PKGC_LIBFUSE_LIBRARY_DIRS} = ${PKGC_LIBFUSE_LIBRARY_DIRS}") 10 | message(STATUS "\${PKGC_LIBFUSE_LDFLAGS} = ${PKGC_LIBFUSE_LDFLAGS}") 11 | message(STATUS "\${PKGC_LIBFUSE_LDFLAGS_OTHER} = ${PKGC_LIBFUSE_LDFLAGS_OTHER}") 12 | message(STATUS "\${PKGC_LIBFUSE_INCLUDE_DIRS} = ${PKGC_LIBFUSE_INCLUDE_DIRS}") 13 | message(STATUS "\${PKGC_LIBFUSE_CFLAGS} = ${PKGC_LIBFUSE_CFLAGS}") 14 | message(STATUS "\${PKGC_LIBFUSE_CFLAGS_OTHER} = ${PKGC_LIBFUSE_CFLAGS_OTHER}") 15 | endif(CMAKE_DEBUG) 16 | 17 | set(LIBFUSE_LIBRARIES ${PKGC_LIBFUSE_LIBRARIES}) 18 | set(LIBFUSE_INCLUDE_DIRS ${PKGC_LIBFUSE_INCLUDE_DIRS}) 19 | #set(LIBFUSE_DEFINITIONS ${PKGC_LIBFUSE_CFLAGS_OTHER}) 20 | else(PKGC_LIBFUSE_FOUND) 21 | # Didn't find lib using pkg-config. Try to find it manually 22 | message(WARNING "Unable to find LibFUSE using pkg-config! If compilation fails, make sure pkg-config is installed and PKG_CONFIG_PATH is set correctly") 23 | 24 | find_path(LIBFUSE_INCLUDE_DIR fuse.h 25 | PATH_SUFFIXES fuse) 26 | find_library(LIBFUSE_LIBRARY NAMES fuse libfuse) 27 | 28 | if(CMAKE_DEBUG) 29 | message(STATUS "\${LIBFUSE_LIBRARY} = ${LIBFUSE_LIBRARY}") 30 | message(STATUS "\${LIBFUSE_INCLUDE_DIR} = ${LIBFUSE_INCLUDE_DIR}") 31 | endif(CMAKE_DEBUG) 32 | 33 | set(LIBFUSE_LIBRARIES ${LIBFUSE_LIBRARY}) 34 | set(LIBFUSE_INCLUDE_DIRS ${LIBFUSE_INCLUDE_DIR}) 35 | endif(PKGC_LIBFUSE_FOUND) 36 | 37 | include(FindPackageHandleStandardArgs) 38 | # Handle the QUIETLY and REQUIRED arguments and set _FOUND to TRUE if 39 | # all listed variables are TRUE 40 | find_package_handle_standard_args(LibFUSE DEFAULT_MSG LIBFUSE_LIBRARIES) 41 | 42 | -------------------------------------------------------------------------------- /src/base/MessageReader.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __MESSAGE_READER_H__ 2 | #define __MESSAGE_READER_H__ 3 | 4 | #include "Headers.hpp" 5 | 6 | namespace codefs { 7 | class MessageReader { 8 | public: 9 | MessageReader() {} 10 | 11 | inline void load(const string& s) { 12 | unpackHandler.remove_nonparsed_buffer(); 13 | unpackHandler.reserve_buffer(s.size()); 14 | memcpy(unpackHandler.buffer(), s.c_str(), s.length()); 15 | unpackHandler.buffer_consumed(s.length()); 16 | } 17 | 18 | template 19 | inline void load(const std::array& a, int size) { 20 | unpackHandler.remove_nonparsed_buffer(); 21 | unpackHandler.reserve_buffer(size); 22 | memcpy(unpackHandler.buffer(), &a[0], size); 23 | unpackHandler.buffer_consumed(size); 24 | } 25 | 26 | template 27 | inline T readPrimitive() { 28 | msgpack::object_handle oh; 29 | FATAL_IF_FALSE(unpackHandler.next(oh)); 30 | T t = oh.get().convert(); 31 | return t; 32 | } 33 | 34 | template 35 | inline map readMap() { 36 | msgpack::object_handle oh; 37 | FATAL_IF_FALSE(unpackHandler.next(oh)); 38 | map t = oh.get().convert(); 39 | return t; 40 | } 41 | 42 | template 43 | inline T readClass() { 44 | T t; 45 | string s = readPrimitive(); 46 | if (s.length() != sizeof(T)) { 47 | throw std::runtime_error("Invalid Class Size"); 48 | } 49 | memcpy(&t, &s[0], sizeof(T)); 50 | return t; 51 | } 52 | 53 | template 54 | inline T readProto() { 55 | T t; 56 | string s = readPrimitive(); 57 | if (!t.ParseFromString(s)) { 58 | throw std::runtime_error("Invalid proto"); 59 | } 60 | return t; 61 | } 62 | 63 | inline int64_t sizeRemaining() { 64 | // TODO: Make sure this is accurate 65 | return unpackHandler.nonparsed_size(); 66 | } 67 | 68 | protected: 69 | msgpack::unpacker unpackHandler; 70 | }; 71 | } // namespace codefs 72 | 73 | #endif // __MESSAGE_READER_H__ -------------------------------------------------------------------------------- /src/base/FileSystem.cpp: -------------------------------------------------------------------------------- 1 | #include "FileSystem.hpp" 2 | 3 | #include "MessageReader.hpp" 4 | #include "MessageWriter.hpp" 5 | 6 | namespace codefs { 7 | string FileSystem::serializeFileDataCompressed(const string& path) { 8 | std::lock_guard lock(mutex); 9 | MessageWriter writer; 10 | if (allFileData.find(path) == allFileData.end()) { 11 | writer.writePrimitive(0); 12 | } else { 13 | const auto& fileData = allFileData.at(path); 14 | int numChildren = 0; 15 | for (auto& it : fileData.child_node()) { 16 | auto childPath = (boost::filesystem::path(path) / it).string(); 17 | if (allFileData.find(childPath) == allFileData.end()) { 18 | numChildren++; 19 | } 20 | } 21 | writer.writePrimitive(1 + numChildren); 22 | writer.writeProto(fileData); 23 | for (auto& it : fileData.child_node()) { 24 | auto childPath = (boost::filesystem::path(path) / it).string(); 25 | if (allFileData.find(childPath) != allFileData.end()) { 26 | VLOG(1) << "SCANNING: " << path << " / " << it << " = " << childPath; 27 | const auto& childFileData = allFileData.at(childPath); 28 | writer.writeProto(childFileData); 29 | } 30 | } 31 | } 32 | return compressString(writer.finish()); 33 | } 34 | 35 | void FileSystem::deserializeFileDataCompressed(const string& path, 36 | const string& s) { 37 | std::lock_guard lock(mutex); 38 | MessageReader reader; 39 | reader.load(decompressString(s)); 40 | int numFiles = reader.readPrimitive(); 41 | VLOG(1) << "DESERIALIZING " << numFiles << " FILES"; 42 | for (int a = 0; a < numFiles; a++) { 43 | auto fileData = reader.readProto(); 44 | VLOG(1) << "GOT FILE: " << fileData.path(); 45 | if (fileData.invalid()) { 46 | LOGFATAL << "Got an invalid file from the server!"; 47 | } 48 | allFileData.erase(fileData.path()); 49 | allFileData.insert(make_pair(fileData.path(), fileData)); 50 | } 51 | } 52 | } // namespace codefs -------------------------------------------------------------------------------- /src/base/LogHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "LogHandler.hpp" 2 | 3 | INITIALIZE_EASYLOGGINGPP 4 | 5 | namespace codefs { 6 | el::Configurations LogHandler::setupLogHandler(int *argc, char ***argv) { 7 | // easylogging parse verbose arguments, see [Application Arguments] 8 | // in https://github.com/muflihun/easyloggingpp/blob/master/README.md 9 | START_EASYLOGGINGPP(*argc, *argv); 10 | 11 | // Easylogging configurations 12 | el::Configurations defaultConf; 13 | defaultConf.setToDefault(); 14 | defaultConf.setGlobally(el::ConfigurationType::Format, 15 | "[%level %datetime %thread %fbase:%line] %msg"); 16 | defaultConf.setGlobally(el::ConfigurationType::Enabled, "true"); 17 | defaultConf.setGlobally(el::ConfigurationType::SubsecondPrecision, "3"); 18 | defaultConf.setGlobally(el::ConfigurationType::PerformanceTracking, "false"); 19 | defaultConf.setGlobally(el::ConfigurationType::LogFlushThreshold, "1"); 20 | defaultConf.set(el::Level::Verbose, el::ConfigurationType::Format, 21 | "[%levshort%vlevel %datetime %thread %fbase:%line] %msg"); 22 | return defaultConf; 23 | } 24 | 25 | void LogHandler::setupLogFile(el::Configurations *defaultConf, string filename, 26 | string maxlogsize) { 27 | // Enable strict log file size check 28 | el::Loggers::addFlag(el::LoggingFlag::StrictLogFileSizeCheck); 29 | defaultConf->setGlobally(el::ConfigurationType::Filename, filename); 30 | defaultConf->setGlobally(el::ConfigurationType::ToFile, "true"); 31 | defaultConf->setGlobally(el::ConfigurationType::MaxLogFileSize, maxlogsize); 32 | } 33 | 34 | void LogHandler::rolloutHandler(const char *filename, std::size_t size) { 35 | // SHOULD NOT LOG ANYTHING HERE BECAUSE LOG FILE IS CLOSED! 36 | std::stringstream ss; 37 | // REMOVE OLD LOG 38 | ss << "rm " << filename; 39 | system(ss.str().c_str()); 40 | } 41 | 42 | string LogHandler::stderrToFile(const string &pathPrefix) { 43 | time_t rawtime; 44 | struct tm *timeinfo; 45 | char buffer[80]; 46 | time(&rawtime); 47 | timeinfo = localtime(&rawtime); 48 | strftime(buffer, sizeof(buffer), "%Y-%m-%d_%I-%M", timeinfo); 49 | string current_time(buffer); 50 | string stderrFilename = pathPrefix + "_stderr_" + current_time; 51 | FILE *stderr_stream = freopen(stderrFilename.c_str(), "w", stderr); 52 | setvbuf(stderr_stream, NULL, _IOLBF, BUFSIZ); // set to line buffering 53 | return stderrFilename; 54 | } 55 | } // namespace codefs 56 | -------------------------------------------------------------------------------- /src/client/Client.hpp: -------------------------------------------------------------------------------- 1 | #include "Headers.hpp" 2 | 3 | #include "ClientFileSystem.hpp" 4 | #include "MessageReader.hpp" 5 | #include "MessageWriter.hpp" 6 | #include "ZmqBiDirectionalRpc.hpp" 7 | 8 | namespace codefs { 9 | class Client { 10 | public: 11 | Client(const string& _address, shared_ptr _fileSystem); 12 | int update(); 13 | inline void heartbeat() { 14 | lock_guard lock(mutex); 15 | rpc->heartbeat(); 16 | } 17 | 18 | optional getNode(const string& path) { return getNodes({path})[0]; } 19 | vector> getNodes(const vector& paths); 20 | optional getNodeAndChildren(const string& path, 21 | vector* children); 22 | inline bool hasDirectory(const string& path) { 23 | auto fileData = getNode(path); 24 | return fileData && S_ISDIR(fileData->stat_data().mode()); 25 | } 26 | 27 | int open(const string& path, int flags); 28 | int create(const string& path, int flags, mode_t mode); 29 | int close(const string& path, int fd); 30 | int pread(const string& path, char* buf, int64_t size, int64_t offset); 31 | int pwrite(const string& path, const char* buf, int64_t size, int64_t offset); 32 | 33 | int mkdir(const string& path, mode_t mode); 34 | int unlink(const string& path); 35 | int rmdir(const string& path); 36 | 37 | int symlink(const string& from, const string& to); 38 | int rename(const string& from, const string& to); 39 | int link(const string& from, const string& to); 40 | 41 | int chmod(const string& path, int mode); 42 | int lchown(const string& path, int64_t uid, int64_t gid); 43 | int truncate(const string& path, int64_t size); 44 | int statvfs(struct statvfs* stbuf); 45 | 46 | int utimensat(const string& path, const struct timespec ts[2]); 47 | int lremovexattr(const string& path, const string& name); 48 | int lsetxattr(const string& path, const string& name, const string& value, 49 | int64_t size, int flags); 50 | 51 | optional getSizeOverride(const string& path) { 52 | return fileSystem->getSizeOverride(path); 53 | } 54 | 55 | protected: 56 | string address; 57 | shared_ptr rpc; 58 | shared_ptr fileSystem; 59 | recursive_mutex mutex; 60 | int twoPathsNoReturn(unsigned char header, const string& from, 61 | const string& to); 62 | int singlePathNoReturn(unsigned char header, const string& path); 63 | string fileRpc(const string& payload); 64 | }; 65 | } // namespace codefs -------------------------------------------------------------------------------- /proto/CodeFS.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package codefs; 3 | 4 | enum MessageHeaders { 5 | SERVER_CLIENT_METADATA_UPDATE = 1; 6 | 7 | CLIENT_SERVER_FETCH_METADATA = 2; 8 | CLIENT_SERVER_MKDIR = 3; 9 | CLIENT_SERVER_UNLINK = 4; 10 | CLIENT_SERVER_RMDIR = 5; 11 | CLIENT_SERVER_SYMLINK = 6; 12 | CLIENT_SERVER_RENAME = 7; 13 | CLIENT_SERVER_LINK = 8; 14 | CLIENT_SERVER_CHMOD = 9; 15 | CLIENT_SERVER_LCHOWN = 10; 16 | CLIENT_SERVER_TRUNCATE = 11; 17 | CLIENT_SERVER_CREATE_FILE = 12; 18 | CLIENT_SERVER_REQUEST_FILE = 13; 19 | CLIENT_SERVER_STATVFS = 14; 20 | CLIENT_SERVER_RETURN_FILE = 15; 21 | CLIENT_SERVER_UTIMENSAT = 16; 22 | CLIENT_SERVER_LREMOVEXATTR = 17; 23 | CLIENT_SERVER_LSETXATTR = 18; 24 | } 25 | 26 | message StatVfsData { 27 | optional int64 bsize = 1; /* Filesystem block size */ 28 | optional int64 frsize = 2; /* Fragment size */ 29 | optional int64 blocks = 3; /* Size of fs in f_frsize units */ 30 | optional int64 bfree = 4; /* Number of free blocks */ 31 | optional int64 bavail = 5; /* Number of free blocks for 32 | unprivileged users */ 33 | optional int64 files = 6; /* Number of inodes */ 34 | optional int64 ffree = 7; /* Number of free inodes */ 35 | optional int64 favail = 8; /* Number of free inodes for 36 | unprivileged users */ 37 | optional int64 fsid = 9; /* Filesystem ID */ 38 | optional int64 flag = 10; /* Mount flags */ 39 | optional int64 namemax = 11; /* Maximum filename length */ 40 | } 41 | 42 | message StatData { 43 | optional int64 dev = 1; /* ID of device containing file */ 44 | optional int64 ino = 2; /* inode number */ 45 | optional int64 mode = 3; /* protection */ 46 | optional int64 nlink = 4; /* number of hard links */ 47 | optional int64 uid = 5; /* user ID of owner */ 48 | optional int64 gid = 6; /* group ID of owner */ 49 | optional int64 rdev = 7; /* device ID (if special file) */ 50 | optional int64 size = 8; /* total size, in bytes */ 51 | optional int64 blksize = 9; /* blocksize for file system I/O */ 52 | optional int64 blocks = 10; /* number of 512B blocks allocated */ 53 | optional int64 atime = 11; /* time of last access */ 54 | optional int64 mtime = 12; /* time of last modification */ 55 | optional int64 ctime = 13; /* time of last status change */ 56 | }; 57 | 58 | message FileData { 59 | optional string path = 1; 60 | optional bool can_read = 2; 61 | optional bool can_write = 3; 62 | optional bool can_execute = 4; 63 | optional StatData stat_data = 5; 64 | optional bytes symlink_contents = 6; 65 | repeated string xattr_key = 7; 66 | repeated bytes xattr_value = 8; 67 | repeated string child_node = 9; 68 | optional bool deleted = 10; 69 | optional bool invalid = 11; 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodeFS 2 | CodeFS is a filesystem for remote software development. 3 | 4 | # Why make CodeFS? 5 | 6 | Remote development is a key component of software engineering. Whether developing for embedded devices or building large ML pipelines in the cloud, one often finds oneself needing to work jointly on a local laptop and on a desktop, embedded device, or virtual server. 7 | 8 | There are already several approaches for this, here is a breakdown of their pros and cons: 9 | 10 | | Tool | Pros | Cons | 11 | | ----------------------------- | ----------------------- | ---------------------------------- | 12 | | sshfs | POSIX interface | Slow, especially to fetch metadata | 13 | | rmate/nuclide | Fast, easy to use | Requires IDE plugins | 14 | | ssh + console ide (vim/emacs) | Needs no extra software | Lag when editing | 15 | | DropBox/syncthing | Replicates all files | Replicates all files | 16 | 17 | 18 | CodeFS brings the POSIX interface of sshfs with the speed that comes with a dedicated server process. 19 | 20 | # Current State 21 | 22 | CodeFS is in alpha testing. You should only use this on directories that are source controlled and you should git push often. 23 | 24 | # How to install 25 | 26 | ## OS/X 27 | 28 | Use homebrew: 29 | 30 | ``` 31 | brew cask install osxfuse 32 | brew install --HEAD MisterTea/codefs/codefs 33 | ``` 34 | 35 | ## Building from Source 36 | 37 | First install the dependencies (either from a package manager or source): 38 | 39 | 1. Boost 40 | 2. Protobuf 41 | 3. GFlags 42 | 4. ZeroMQ 43 | 5. fswatch 44 | 6. FUSE (or OSXFUSE for mac) for the client. Build with -DBUILD_CLIENT=OFF to skip the client if you cannot install FUSE on the server. 45 | 46 | Then: 47 | 48 | ``` 49 | git clone https://github.com/MisterTea/CodeFS.git --recurse-submodules 50 | cd CodeFS 51 | mkdir build 52 | cd build 53 | cmake ../ 54 | make -j4 55 | make install 56 | ``` 57 | 58 | # User Guide 59 | 60 | ## Starting the server 61 | 62 | Note that, as of now, there is **no** security or encryption. This means that port 2298 should **not** be exposed to the outside. Instead, codefs should be run over a secure layer, such as Eternal Terminal: https://github.com/MisterTea/EternalTerminal 63 | 64 | To connect to the server with port forwarding: 65 | 66 | ``` 67 | et -x -t="2298:2298" my_server.com 68 | ``` 69 | 70 | Then inside the et/ssh session, run: 71 | 72 | ``` 73 | codefsserver --path=/my/code/path --logtostdout 74 | ``` 75 | 76 | Where ```/my/code/path``` is the location of your code. For now, the server needs to be restarted every time the client (re)connects. 77 | 78 | ## Running the client 79 | 80 | On the client, run: 81 | 82 | ``` 83 | codefs --path=/tmp/my_development_path --logtostdout 84 | ``` 85 | 86 | Where ```/tmp/my_development_path``` is some empty folder that will act like a mirror to the folder on the server. 87 | 88 | # Troubleshooting 89 | 90 | ### Client doesn't connect to server 91 | 92 | In 0.0.1, the server needs to finish indexing the entire directory before the client will be able to connect to it. 93 | -------------------------------------------------------------------------------- /src/base/ZmqBiDirectionalRpc.cpp: -------------------------------------------------------------------------------- 1 | #include "ZmqBiDirectionalRpc.hpp" 2 | 3 | namespace codefs { 4 | ZmqBiDirectionalRpc::ZmqBiDirectionalRpc(const string& _address, bool _bind) 5 | : BiDirectionalRpc(), address(_address), bind(_bind) { 6 | context = shared_ptr(new zmq::context_t(8)); 7 | if (bind) { 8 | LOG(INFO) << "Binding on address: " << address; 9 | socket = shared_ptr( 10 | new zmq::socket_t(*(context.get()), ZMQ_ROUTER)); 11 | socket->bind(address); 12 | } else { 13 | LOG(INFO) << "Connecting to address: " << address; 14 | socket = shared_ptr( 15 | new zmq::socket_t(*(context.get()), ZMQ_DEALER)); 16 | socket->connect(address); 17 | } 18 | LOG(INFO) << "Done"; 19 | } 20 | 21 | ZmqBiDirectionalRpc::~ZmqBiDirectionalRpc() { 22 | if (context.get() || socket.get()) { 23 | LOGFATAL << "Tried to destroy an RPC instance without calling shutdown"; 24 | } 25 | } 26 | 27 | void ZmqBiDirectionalRpc::shutdown() { 28 | LOG(INFO) << "CLOSING SOCKET"; 29 | socket->close(); 30 | LOG(INFO) << "KILLING SOCKET"; 31 | socket.reset(); 32 | LOG(INFO) << "CLOSING CONTEXT"; 33 | context->close(); 34 | LOG(INFO) << "KILLING CONTEXT"; 35 | context.reset(); 36 | LOG(INFO) << "SHUTDOWN COMPLETE"; 37 | } 38 | 39 | void ZmqBiDirectionalRpc::update() { 40 | while (true) { 41 | zmq::message_t message; 42 | bool result = socket->recv(&message, ZMQ_DONTWAIT); 43 | FATAL_IF_FALSE_NOT_EAGAIN(result); 44 | if (!result) { 45 | // Nothing to recieve 46 | return; 47 | } 48 | // The identity 49 | if (bind) { 50 | if (clientIdentity != message) { 51 | LOG(INFO) << "Got a new client: " << clientIdentity.str() 52 | << " != " << message.str(); 53 | clientIdentity.rebuild(message.data(), message.size()); 54 | } 55 | } 56 | if (!message.more()) { 57 | LOGFATAL << "Expected more data!"; 58 | } 59 | 60 | // The data 61 | FATAL_IF_FALSE(socket->recv(&message)); 62 | if (message.more()) { 63 | LOGFATAL << "DID NOT GET ALL"; 64 | } 65 | 66 | VLOG(1) << "Got message with size " << message.size() << endl; 67 | string s(message.data(), message.size()); 68 | BiDirectionalRpc::receive(s); 69 | } 70 | } 71 | 72 | void ZmqBiDirectionalRpc::reconnect() { 73 | // Skip reconnect 74 | return; 75 | 76 | shutdown(); 77 | 78 | context = shared_ptr(new zmq::context_t(4)); 79 | socket.reset(new zmq::socket_t(*(context.get()), ZMQ_PAIR)); 80 | socket->setsockopt(ZMQ_LINGER, 3000); 81 | if (bind) { 82 | LOG(INFO) << "Binding on address: " << address; 83 | socket->bind(address); 84 | } else { 85 | socket->connect(address); 86 | } 87 | } 88 | 89 | void ZmqBiDirectionalRpc::send(const string& message) { 90 | VLOG(1) << "SENDING " << message.length(); 91 | if (message.length() == 0) { 92 | LOGFATAL << "Invalid message size"; 93 | } 94 | if (bind) { 95 | if (clientIdentity.size() == 0) { 96 | // no one to send to 97 | LOG(INFO) << "No one to send to!"; 98 | return; 99 | } 100 | FATAL_IF_FALSE(socket->send( 101 | zmq::message_t(clientIdentity.data(), clientIdentity.size()), 102 | ZMQ_SNDMORE)); 103 | FATAL_IF_FALSE(socket->send(zmq::message_t(), ZMQ_SNDMORE)); 104 | } 105 | zmq::message_t zmqMessage(message.c_str(), message.length()); 106 | FATAL_IF_FALSE(socket->send(zmqMessage)); 107 | } 108 | } // namespace codefs 109 | -------------------------------------------------------------------------------- /test/TestRpc.cpp: -------------------------------------------------------------------------------- 1 | #include "Headers.hpp" 2 | 3 | #include "ZmqBiDirectionalRpc.hpp" 4 | 5 | #include "Catch2/single_include/catch2/catch.hpp" 6 | 7 | namespace codefs { 8 | void runServer(const string& address, int* tasksLeft, bool bind, bool flaky, 9 | bool barrier, bool reconnect) { 10 | { 11 | ZmqBiDirectionalRpc server(address, bind); 12 | server.setFlaky(flaky); 13 | sleep(3); 14 | 15 | vector payloads = {"Hello", "World", "How", "Are", "You", "Today"}; 16 | map uidPayloadMap; 17 | 18 | for (int a = 0; a <= 100; a++) { 19 | server.update(); 20 | usleep(10 * 1000); 21 | if (reconnect && a % 50 == 3) { 22 | server.reconnect(); 23 | } 24 | if (a && a % 10 == 0) { 25 | server.heartbeat(); 26 | } 27 | if (a < payloads.size()) { 28 | if (barrier && a % 2 == 0) { 29 | server.barrier(); 30 | } 31 | uidPayloadMap.insert( 32 | make_pair(server.request(payloads[a]), payloads[a] + payloads[a])); 33 | } 34 | while (server.hasIncomingRequest()) { 35 | auto idPayload = server.getFirstIncomingRequest(); 36 | server.reply(idPayload.id, idPayload.payload + idPayload.payload); 37 | } 38 | } 39 | 40 | bool done = false; 41 | for (int a = 0; *tasksLeft; a++) { 42 | if (reconnect && rand() % 500 == 0) { 43 | server.reconnect(); 44 | } 45 | usleep(10 * 1000); 46 | server.update(); 47 | if (a % 100 == 0) { 48 | server.heartbeat(); 49 | } 50 | while (server.hasIncomingRequest()) { 51 | auto idPayload = server.getFirstIncomingRequest(); 52 | server.reply(idPayload.id, idPayload.payload + idPayload.payload); 53 | } 54 | while (server.hasIncomingReply()) { 55 | auto reply = server.getFirstIncomingReply(); 56 | auto it = uidPayloadMap.find(reply.id); 57 | REQUIRE(it != uidPayloadMap.end()); 58 | REQUIRE(it->second == reply.payload); 59 | uidPayloadMap.erase(it); 60 | } 61 | if (!done && uidPayloadMap.empty()) { 62 | done = true; 63 | (*tasksLeft)--; 64 | } 65 | } 66 | 67 | // TODO: We may still have work to do so check for other server to be done 68 | sleep(3); 69 | 70 | REQUIRE(uidPayloadMap.empty()); 71 | 72 | server.shutdown(); 73 | LOG(INFO) << "DESTROYING SERVER"; 74 | } 75 | 76 | LOG(INFO) << "SERVER EXITING"; 77 | } 78 | 79 | TEST_CASE("ReadWrite", "[RpcTest]") { 80 | char dirSchema[] = "/tmp/TestRpc.XXXXXX"; 81 | string dirName = mkdtemp(dirSchema); 82 | string address = string("ipc://") + dirName + "/ipc"; 83 | 84 | int tasksLeft = 2; 85 | thread serverThread(runServer, address, &tasksLeft, true, false, false, 86 | false); 87 | sleep(1); 88 | thread clientThread(runServer, address, &tasksLeft, false, false, false, 89 | false); 90 | serverThread.join(); 91 | clientThread.join(); 92 | 93 | boost::filesystem::remove_all(dirName); 94 | } 95 | 96 | TEST_CASE("FlakyReadWrite", "[RpcTest]") { 97 | char dirSchema[] = "/tmp/TestRpc.XXXXXX"; 98 | string dirName = mkdtemp(dirSchema); 99 | string address = string("ipc://") + dirName + "/ipc"; 100 | 101 | int tasksLeft = 2; 102 | thread serverThread(runServer, address, &tasksLeft, true, true, false, false); 103 | sleep(1); 104 | thread clientThread(runServer, address, &tasksLeft, false, true, false, 105 | false); 106 | serverThread.join(); 107 | clientThread.join(); 108 | 109 | boost::filesystem::remove_all(dirName); 110 | } 111 | 112 | TEST_CASE("FlakyReadWriteDisconnect", "[RpcTest]") { 113 | char dirSchema[] = "/tmp/TestRpc.XXXXXX"; 114 | string dirName = mkdtemp(dirSchema); 115 | string address = string("ipc://") + dirName + "/ipc"; 116 | 117 | int tasksLeft = 2; 118 | thread serverThread(runServer, address, &tasksLeft, true, true, false, true); 119 | sleep(1); 120 | thread clientThread(runServer, address, &tasksLeft, false, true, false, true); 121 | serverThread.join(); 122 | clientThread.join(); 123 | 124 | boost::filesystem::remove_all(dirName); 125 | } 126 | 127 | TEST_CASE("FlakyReadWriteBarrier", "[RpcTest]") { 128 | char dirSchema[] = "/tmp/TestRpc.XXXXXX"; 129 | string dirName = mkdtemp(dirSchema); 130 | string address = string("ipc://") + dirName + "/ipc"; 131 | 132 | int tasksLeft = 2; 133 | thread serverThread(runServer, address, &tasksLeft, true, true, true, false); 134 | sleep(1); 135 | thread clientThread(runServer, address, &tasksLeft, false, true, true, false); 136 | serverThread.join(); 137 | clientThread.join(); 138 | 139 | boost::filesystem::remove_all(dirName); 140 | } 141 | } // namespace codefs 142 | -------------------------------------------------------------------------------- /external/fswatch_config/libfswatch_config.h: -------------------------------------------------------------------------------- 1 | /* libfswatch_config.h.in. Generated from configure.ac by autoheader. */ 2 | 3 | /* Define to 1 if translation of program messages to the user's native 4 | language is requested. */ 5 | #undef ENABLE_NLS 6 | 7 | /* Define to 1 if you have the `atexit' function. */ 8 | #define HAVE_ATEXIT (1) 9 | 10 | #ifdef __APPLE__ 11 | /* Define to 1 if you have the Mac OS X function CFLocaleCopyCurrent in the 12 | CoreFoundation framework. */ 13 | #define HAVE_CFLOCALECOPYCURRENT (1) 14 | 15 | /* Define to 1 if you have the Mac OS X function CFPreferencesCopyAppValue in 16 | the CoreFoundation framework. */ 17 | #define HAVE_CFPREFERENCESCOPYAPPVALUE (1) 18 | 19 | /* Define to 1 if you have the header file. */ 20 | #define HAVE_CORESERVICES_CORESERVICES_H (1) 21 | #endif 22 | 23 | /* Define if is available. */ 24 | #define HAVE_CXX_ATOMIC (1) 25 | 26 | /* Define if is available. */ 27 | #define HAVE_CXX_MUTEX (1) 28 | 29 | /* Define if the thread_local storage specified is available. */ 30 | #define HAVE_CXX_THREAD_LOCAL (1) 31 | 32 | /* Define if std::unique_ptr in is available. */ 33 | #define HAVE_CXX_UNIQUE_PTR (1) 34 | 35 | /* CygWin API present. */ 36 | #undef HAVE_CYGWIN 37 | 38 | /* Define if the GNU dcgettext() function is already present or preinstalled. 39 | */ 40 | #undef HAVE_DCGETTEXT 41 | 42 | /* Define to 1 if you have the declaration of `cygwin_create_path', and to 0 43 | if you don't. */ 44 | #undef HAVE_DECL_CYGWIN_CREATE_PATH 45 | 46 | /* Define to 1 if you have the declaration of `FindCloseChangeNotification', 47 | and to 0 if you don't. */ 48 | #undef HAVE_DECL_FINDCLOSECHANGENOTIFICATION 49 | 50 | /* Define to 1 if you have the declaration of `FindFirstChangeNotification', 51 | and to 0 if you don't. */ 52 | #undef HAVE_DECL_FINDFIRSTCHANGENOTIFICATION 53 | 54 | /* Define to 1 if you have the declaration of `FindNextChangeNotification', 55 | and to 0 if you don't. */ 56 | #undef HAVE_DECL_FINDNEXTCHANGENOTIFICATION 57 | 58 | #if defined(__linux__) && !defined(__APPLE__) 59 | /* Define to 1 if you have the declaration of `inotify_add_watch', and to 0 if 60 | you don't. */ 61 | #define HAVE_DECL_INOTIFY_ADD_WATCH (1) 62 | 63 | /* Define to 1 if you have the declaration of `inotify_init', and to 0 if you 64 | don't. */ 65 | #define HAVE_DECL_INOTIFY_INIT (1) 66 | 67 | /* Define to 1 if you have the declaration of `inotify_rm_watch', and to 0 if 68 | you don't. */ 69 | #define HAVE_DECL_INOTIFY_RM_WATCH (1) 70 | #endif 71 | 72 | #if defined(__unix__) && !defined(__APPLE__) 73 | /* Define to 1 if you have the declaration of `kevent', and to 0 if you don't. 74 | */ 75 | #undef HAVE_DECL_KEVENT 76 | 77 | /* Define to 1 if you have the declaration of `kqueue', and to 0 if you don't. 78 | */ 79 | #undef HAVE_DECL_KQUEUE 80 | #endif 81 | 82 | #ifdef __APPLE__ 83 | /* Define if the file events are supported by OS X FSEvents API. */ 84 | #define HAVE_FSEVENTS_FILE_EVENTS (1) 85 | #endif 86 | 87 | /* Define to 1 if you have the header file. */ 88 | #undef HAVE_PORT_H 89 | 90 | /* Define to 1 if `st_mtime' is a member of `struct stat'. */ 91 | #define HAVE_STRUCT_STAT_ST_MTIME (1) 92 | 93 | /* Define to 1 if `st_mtimespec' is a member of `struct stat'. */ 94 | #define HAVE_STRUCT_STAT_ST_MTIMESPEC (1) 95 | 96 | #ifdef __APPLE__ 97 | /* Define to 1 if you have the header file. */ 98 | #undef HAVE_SYS_EVENT_H 99 | #endif 100 | 101 | #if defined(__unix__) && !defined(__APPLE__) 102 | /* Define to 1 if you have the header file. */ 103 | #define HAVE_SYS_INOTIFY_H (1) 104 | #endif 105 | 106 | /* Define to 1 if you have the header file. */ 107 | #define HAVE_UNORDERED_MAP (1) 108 | 109 | /* Define to 1 if you have the header file. */ 110 | #define HAVE_UNORDERED_SET (1) 111 | 112 | /* Windows API present. */ 113 | #undef HAVE_WINDOWS 114 | 115 | /* Define to 1 if you have the header file. */ 116 | #undef HAVE_WINDOWS_H 117 | 118 | /* Name of package */ 119 | #define PACKAGE "fswatch" 120 | 121 | /* Define to the address where bug reports for this package should be sent. */ 122 | #define PACKAGE_BUGREPORT "enrico.m.crisostomo@gmail.com" 123 | 124 | /* Define to the full name of this package. */ 125 | #define PACKAGE_NAME "fswatch" 126 | 127 | /* Define to the full name and version of this package. */ 128 | #define PACKAGE_STRING "fswatch 1.14.0" 129 | 130 | /* Define to the one symbol short name of this package. */ 131 | #define PACKAGE_TARNAME "fswatch" 132 | 133 | /* Define to the home page for this package. */ 134 | #define PACKAGE_URL "https://github.com/emcrisostomo/fswatch" 135 | 136 | /* Define to the version of this package. */ 137 | #define PACKAGE_VERSION "1.14.0" 138 | 139 | /* Version number of package */ 140 | #define VERSION "1.14.0" 141 | 142 | /* Define for Solaris 2.5.1 so the uint32_t typedef from , 143 | , or is not used. If the typedef were allowed, the 144 | #define below would cause a syntax error. */ 145 | #undef _UINT32_T 146 | 147 | /* Define to `int' if does not define. */ 148 | #undef mode_t 149 | 150 | /* Define to `unsigned int' if does not define. */ 151 | #undef size_t 152 | 153 | /* Define to `int' if does not define. */ 154 | #undef ssize_t 155 | 156 | /* Define to the type of an unsigned integer type of width exactly 32 bits if 157 | such a type exists and the standard includes do not define it. */ 158 | #undef uint32_t 159 | -------------------------------------------------------------------------------- /src/base/BiDirectionalRpc.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __BIDIRECTIONAL_RPC_H__ 2 | #define __BIDIRECTIONAL_RPC_H__ 3 | 4 | #include "Headers.hpp" 5 | #include "MessageReader.hpp" 6 | #include "MessageWriter.hpp" 7 | #include "PidController.hpp" 8 | #include "RpcId.hpp" 9 | 10 | namespace codefs { 11 | class IdPayload { 12 | public: 13 | IdPayload() {} 14 | IdPayload(const RpcId& _id, const string& _payload) 15 | : id(_id), payload(_payload) {} 16 | 17 | RpcId id; 18 | string payload; 19 | }; 20 | } // namespace codefs 21 | 22 | namespace std { 23 | template <> 24 | struct hash : public std::unary_function { 25 | public: 26 | // hash functor: hash uuid to size_t value by pseudorandomizing transform 27 | size_t operator()(const codefs::RpcId& rpcId) const { 28 | if (sizeof(size_t) > 4) { 29 | return size_t(rpcId.barrier ^ rpcId.id); 30 | } else { 31 | uint64_t hash64 = rpcId.barrier ^ rpcId.id; 32 | return size_t(uint32_t(hash64 >> 32) ^ uint32_t(hash64)); 33 | } 34 | } 35 | }; 36 | } // namespace std 37 | 38 | namespace codefs { 39 | enum RpcHeader { HEARTBEAT = 1, REQUEST = 2, REPLY = 3, ACKNOWLEDGE = 4 }; 40 | 41 | class BiDirectionalRpc { 42 | public: 43 | BiDirectionalRpc(); 44 | virtual ~BiDirectionalRpc(); 45 | void shutdown(); 46 | void heartbeat(); 47 | void barrier() { 48 | lock_guard guard(mutex); 49 | onBarrier++; 50 | } 51 | 52 | RpcId request(const string& payload); 53 | void requestNoReply(const string& payload); 54 | virtual void requestWithId(const IdPayload& idPayload); 55 | virtual void reply(const RpcId& rpcId, const string& payload); 56 | inline void replyOneWay(const RpcId& rpcId) { reply(rpcId, "OK"); } 57 | 58 | bool hasIncomingRequest() { 59 | lock_guard guard(mutex); 60 | return !incomingRequests.empty(); 61 | } 62 | bool hasIncomingRequestWithId(const RpcId& rpcId) { 63 | lock_guard guard(mutex); 64 | return incomingRequests.find(rpcId) != incomingRequests.end(); 65 | } 66 | IdPayload getFirstIncomingRequest() { 67 | lock_guard guard(mutex); 68 | if (!hasIncomingRequest()) { 69 | LOGFATAL << "Tried to get a request when one doesn't exist"; 70 | } 71 | return IdPayload(incomingRequests.begin()->first, 72 | incomingRequests.begin()->second); 73 | } 74 | 75 | bool hasIncomingReply() { 76 | lock_guard guard(mutex); 77 | return !incomingReplies.empty(); 78 | } 79 | 80 | IdPayload getFirstIncomingReply() { 81 | lock_guard guard(mutex); 82 | if (incomingReplies.empty()) { 83 | LOGFATAL << "Tried to get reply when there was none"; 84 | } 85 | IdPayload idPayload = IdPayload(incomingReplies.begin()->first, 86 | incomingReplies.begin()->second); 87 | incomingReplies.erase(incomingReplies.begin()); 88 | return idPayload; 89 | } 90 | 91 | bool hasIncomingReplyWithId(const RpcId& rpcId) { 92 | lock_guard guard(mutex); 93 | return incomingReplies.find(rpcId) != incomingReplies.end(); 94 | } 95 | string consumeIncomingReplyWithId(const RpcId& rpcId) { 96 | lock_guard guard(mutex); 97 | auto it = incomingReplies.find(rpcId); 98 | if (it == incomingReplies.end()) { 99 | LOGFATAL << "Tried to get a reply that didn't exist!"; 100 | } 101 | string payload = it->second; 102 | incomingReplies.erase(it); 103 | return payload; 104 | } 105 | 106 | void setFlaky(bool _flaky) { flaky = _flaky; } 107 | 108 | virtual void receive(const string& message); 109 | 110 | bool hasWork() { 111 | lock_guard guard(mutex); 112 | return !delayedRequests.empty() || !outgoingRequests.empty() || 113 | !incomingRequests.empty() || !outgoingReplies.empty() || 114 | !incomingReplies.empty(); 115 | } 116 | 117 | protected: 118 | unordered_map delayedRequests; 119 | unordered_map outgoingRequests; 120 | unordered_map incomingRequests; 121 | unordered_set oneWayRequests; 122 | 123 | unordered_map requestSendTimeMap; 124 | unordered_map requestRecieveTimeMap; 125 | 126 | unordered_map outgoingReplies; 127 | unordered_map incomingReplies; 128 | 129 | int64_t onBarrier; 130 | uint64_t onId; 131 | bool flaky; 132 | recursive_mutex mutex; 133 | 134 | struct NetworkStats { 135 | int64_t offset; 136 | int64_t ping; 137 | }; 138 | deque networkStatsQueue; 139 | PidController timeOffsetController; 140 | 141 | void handleRequest(const RpcId& rpcId, const string& payload); 142 | virtual void handleReply(const RpcId& rpcId, const string& payload); 143 | void resendRandomOutgoingMessage(); 144 | void tryToSendBarrier(); 145 | void sendRequest(const RpcId& id, const string& payload); 146 | void sendReply(const RpcId& id, const string& payload); 147 | void sendAcknowledge(const RpcId& uid); 148 | virtual void addIncomingRequest(const IdPayload& idPayload); 149 | virtual void addIncomingReply(const RpcId& uid, const string& payload) { 150 | incomingReplies.emplace(uid, payload); 151 | } 152 | void updateDrift(int64_t requestSendTime, int64_t requestReceiptTime, 153 | int64_t replySendTime, int64_t replyRecieveTime); 154 | 155 | virtual void send(const string& message) = 0; 156 | }; 157 | } // namespace codefs 158 | 159 | #endif // __BIDIRECTIONAL_RPC_H__ -------------------------------------------------------------------------------- /src/server/ServerFileSystem.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CODEFS_SERVER_FILE_SYSTEM_H__ 2 | #define __CODEFS_SERVER_FILE_SYSTEM_H__ 3 | 4 | #include "FileSystem.hpp" 5 | 6 | namespace codefs { 7 | class ServerFileSystem : public FileSystem { 8 | public: 9 | class Handler { 10 | public: 11 | virtual void metadataUpdated(const string &path, 12 | const FileData &fileData) = 0; 13 | }; 14 | 15 | explicit ServerFileSystem(const string &_rootPath, 16 | const set &_excludes); 17 | virtual ~ServerFileSystem() {} 18 | 19 | void init(); 20 | inline bool isInitialized() { return initialized; } 21 | void setHandler(Handler *_handler) { handler = _handler; } 22 | 23 | void rescanPath(const string &absolutePath); 24 | 25 | inline void rescanPathAndParent(const string &absolutePath) { 26 | std::lock_guard lock(mutex); 27 | rescanPath(absolutePath); 28 | if (absoluteToRelative(absolutePath) != string("/")) { 29 | LOG(INFO) << "RESCANNING PARENT"; 30 | rescanPath(boost::filesystem::path(absolutePath).parent_path().string()); 31 | } 32 | } 33 | 34 | inline void rescanPathAndParentAndChildren(const string &absolutePath) { 35 | std::lock_guard lock(mutex); 36 | if (absoluteToRelative(absolutePath) != string("/")) { 37 | rescanPath(boost::filesystem::path(absolutePath).parent_path().string()); 38 | } 39 | rescanPathAndChildren(absolutePath); 40 | } 41 | 42 | inline void rescanPathAndChildren(const string &absolutePath) { 43 | std::lock_guard lock(mutex); 44 | auto node = getNode(absolutePath); 45 | if (node) { 46 | // scan node and known children envelope for deletion/update 47 | string relativePath = absoluteToRelative(absolutePath); 48 | vector subPaths; 49 | for (auto &it : allFileData) { 50 | if (it.first.find(relativePath) == 0) { 51 | // This is the node or a child 52 | subPaths.push_back(it.first); 53 | } 54 | } 55 | for (auto &it : subPaths) { 56 | rescanPath(relativeToAbsolute(it)); 57 | } 58 | } 59 | 60 | // Begin recursive scan to pick up new children 61 | scanRecursively(absolutePath); 62 | } 63 | 64 | string readFile(const string &path); 65 | int writeFile(const string &path, const string &fileContents); 66 | 67 | int mkdir(const string &path, mode_t mode) { 68 | std::lock_guard lock(mutex); 69 | return ::mkdir(relativeToAbsolute(path).c_str(), mode); 70 | } 71 | 72 | int unlink(const string &path) { 73 | std::lock_guard lock(mutex); 74 | return ::unlink(relativeToAbsolute(path).c_str()); 75 | } 76 | 77 | int rmdir(const string &path) { 78 | std::lock_guard lock(mutex); 79 | return ::rmdir(relativeToAbsolute(path).c_str()); 80 | } 81 | 82 | int symlink(const string &from, const string &to) { 83 | std::lock_guard lock(mutex); 84 | return ::symlink(relativeToAbsolute(from).c_str(), 85 | relativeToAbsolute(to).c_str()); 86 | } 87 | 88 | int link(const string &from, const string &to) { 89 | std::lock_guard lock(mutex); 90 | return ::link(relativeToAbsolute(from).c_str(), 91 | relativeToAbsolute(to).c_str()); 92 | } 93 | 94 | int rename(const string &from, const string &to) { 95 | std::lock_guard lock(mutex); 96 | return ::rename(relativeToAbsolute(from).c_str(), 97 | relativeToAbsolute(to).c_str()); 98 | } 99 | 100 | int chmod(const string &path, mode_t mode) { 101 | std::lock_guard lock(mutex); 102 | int res = ::chmod(relativeToAbsolute(path).c_str(), mode); 103 | rescanPath(relativeToAbsolute(path)); 104 | return res; 105 | } 106 | 107 | int lchown(const string &path, int64_t uid, int64_t gid) { 108 | std::lock_guard lock(mutex); 109 | int res = ::lchown(relativeToAbsolute(path).c_str(), uid, gid); 110 | rescanPath(relativeToAbsolute(path)); 111 | return res; 112 | } 113 | 114 | int truncate(const string &path, int64_t size) { 115 | std::lock_guard lock(mutex); 116 | int res = ::truncate(relativeToAbsolute(path).c_str(), size); 117 | rescanPath(relativeToAbsolute(path)); 118 | return res; 119 | } 120 | 121 | int utimensat(const string &path, struct timespec ts[2]) { 122 | std::lock_guard lock(mutex); 123 | int res = ::utimensat(0, relativeToAbsolute(path).c_str(), ts, 124 | AT_SYMLINK_NOFOLLOW); 125 | rescanPath(relativeToAbsolute(path)); 126 | return res; 127 | } 128 | 129 | int lremovexattr(const string &path, const string &name) { 130 | std::lock_guard lock(mutex); 131 | int res = ::lremovexattr(relativeToAbsolute(path).c_str(), name.c_str()); 132 | rescanPath(relativeToAbsolute(path)); 133 | return res; 134 | } 135 | 136 | int lsetxattr(const string &path, const string &name, const string &value, 137 | int64_t size, int flags) { 138 | std::lock_guard lock(mutex); 139 | int res = ::lsetxattr(relativeToAbsolute(path).c_str(), name.c_str(), 140 | value.c_str(), size, flags); 141 | rescanPath(relativeToAbsolute(path)); 142 | return res; 143 | } 144 | 145 | void scanRecursively(const string &path, 146 | shared_ptr scanThreadPool = 147 | shared_ptr()); 148 | void scanNode(const string &path); 149 | 150 | protected: 151 | bool initialized; 152 | Handler *handler; 153 | set excludes; 154 | }; 155 | } // namespace codefs 156 | 157 | #endif // __CODEFS_SERVER_FILE_SYSTEM_H__ -------------------------------------------------------------------------------- /src/client/Main.cpp: -------------------------------------------------------------------------------- 1 | #include "Client.hpp" 2 | 3 | #include "ClientFileSystem.hpp" 4 | #include "ClientFuseAdapter.hpp" 5 | #include "LogHandler.hpp" 6 | 7 | namespace codefs { 8 | struct loopback {}; 9 | 10 | static struct loopback loopback; 11 | 12 | static const struct fuse_opt codefs_opts[] = { 13 | // { "case_insensitive", offsetof(struct loopback, case_insensitive), 1 }, 14 | FUSE_OPT_END}; 15 | 16 | void runFuse(char *binaryLocation, shared_ptr client, 17 | shared_ptr fileSystem, 18 | const std::string &mountPoint, bool logToStdout) { 19 | vector fuseFlags = {binaryLocation, mountPoint.c_str()}; //, "-s"}; 20 | #if __APPLE__ 21 | // OSXFUSE has a timeout in the kernel. Because we can block on network 22 | // failure, we disable this timeout 23 | fuseFlags.push_back("-odaemon_timeout=2592000"); 24 | fuseFlags.push_back("-ojail_symlinks"); 25 | #endif 26 | if (logToStdout) { 27 | fuseFlags.push_back("-d"); 28 | } else { 29 | fuseFlags.push_back("-f"); 30 | } 31 | 32 | int argc = int(fuseFlags.size()); 33 | char **argv = new char *[argc]; 34 | for (int a = 0; a < argc; a++) { 35 | argv[a] = &(fuseFlags[a][0]); 36 | } 37 | struct fuse_args args = FUSE_ARGS_INIT(argc, argv); 38 | 39 | if (fuse_opt_parse(&args, &loopback, codefs_opts, NULL) == -1) { 40 | LOGFATAL << "Error parsing fuse options"; 41 | } 42 | 43 | umask(0); 44 | fuse_operations codefs_oper; 45 | memset(&codefs_oper, 0, sizeof(fuse_operations)); 46 | 47 | ClientFuseAdapter adapter; 48 | adapter.assignCallbacks(fileSystem, client, &codefs_oper); 49 | 50 | int res = fuse_main(argc, argv, &codefs_oper, NULL); 51 | fuse_opt_free_args(&args); 52 | if (res) { 53 | LOGFATAL << "Unclean exit from fuse thread: " << res << " (errno: " << errno 54 | << ")"; 55 | } else { 56 | LOG(INFO) << "FUSE THREAD EXIT"; 57 | } 58 | } // namespace codefs 59 | 60 | int main(int argc, char *argv[]) { 61 | // Setup easylogging configurations 62 | el::Configurations defaultConf = LogHandler::setupLogHandler(&argc, &argv); 63 | 64 | // Parse command line arguments 65 | cxxopts::Options options("et", "Remote shell for the busy and impatient"); 66 | 67 | try { 68 | options.allow_unrecognised_options(); 69 | 70 | options.add_options() // 71 | ("h,help", "Print help") // 72 | ("port", "Port to connect to", 73 | cxxopts::value()->default_value("2298")) // 74 | ("hostname", "Hostname to connect to", 75 | cxxopts::value()) // 76 | ("mountpoint", "Where to mount the FS for server access", 77 | cxxopts::value()->default_value("/tmp/clientmount")) // 78 | ("v,verbose", "Enable verbose logging", 79 | cxxopts::value()->default_value("0")) // 80 | ("logtostdout", "Write log to stdout") // 81 | ; 82 | 83 | auto result = options.parse(argc, argv); 84 | if (result.count("help")) { 85 | cout << options.help({}) << endl; 86 | exit(0); 87 | } 88 | 89 | if (result.count("logtostdout")) { 90 | defaultConf.setGlobally(el::ConfigurationType::ToStandardOutput, "true"); 91 | } else { 92 | defaultConf.setGlobally(el::ConfigurationType::ToStandardOutput, "false"); 93 | } 94 | 95 | if (result.count("verbose")) { 96 | el::Loggers::setVerboseLevel(result["verbose"].as()); 97 | } 98 | // default max log file size is 20MB for etserver 99 | string maxlogsize = "20971520"; 100 | codefs::LogHandler::setupLogFile(&defaultConf, "/tmp/codefs.log", 101 | maxlogsize); 102 | 103 | // Reconfigure default logger to apply settings above 104 | el::Loggers::reconfigureLogger("default", defaultConf); 105 | 106 | // Create mountpoint if it doesn't exist 107 | boost::filesystem::path pt(result["mountpoint"].as()); 108 | if (!exists(pt)) { 109 | boost::filesystem::create_directory(pt); 110 | } else { 111 | if (!boost::filesystem::is_directory(pt)) { 112 | cout << "Error: The mountpoint is not a directory" << endl; 113 | LOGFATAL << "The mountpoint is not a directory"; 114 | } 115 | } 116 | 117 | int port = result["port"].as(); 118 | 119 | shared_ptr fileSystem( 120 | new ClientFileSystem(result["mountpoint"].as())); 121 | shared_ptr client(new Client(string("tcp://") + 122 | result["hostname"].as() + 123 | ":" + to_string(port), 124 | fileSystem)); 125 | sleep(1); 126 | 127 | auto future = std::async(std::launch::async, [client] { 128 | auto lastHeartbeatTime = std::chrono::high_resolution_clock::now(); 129 | while (true) { 130 | int retval = client->update(); 131 | if (retval) { 132 | return retval; 133 | } 134 | auto msSinceLastHeartbeat = 135 | std::chrono::duration_cast( 136 | std::chrono::high_resolution_clock::now() - lastHeartbeatTime) 137 | .count(); 138 | if (msSinceLastHeartbeat >= 3000) { 139 | client->heartbeat(); 140 | lastHeartbeatTime = std::chrono::high_resolution_clock::now(); 141 | } 142 | usleep(1); 143 | } 144 | }); 145 | 146 | string mountPoint = result["mountpoint"].as(); 147 | 148 | runFuse(argv[0], client, fileSystem, mountPoint, 149 | result.count("logtostdout") > 0); 150 | 151 | LOG(INFO) << "Client finished"; 152 | cout << "Client finished" << endl; 153 | return 0; 154 | } catch (cxxopts::OptionException &oe) { 155 | cout << "Exception: " << oe.what() << "\n" << endl; 156 | cout << options.help({}) << endl; 157 | exit(1); 158 | } 159 | } 160 | } // namespace codefs 161 | 162 | int main(int argc, char *argv[]) { codefs::main(argc, argv); } 163 | -------------------------------------------------------------------------------- /src/base/FileSystem.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FILESYSTEM_H__ 2 | #define __FILESYSTEM_H__ 3 | 4 | #include "Headers.hpp" 5 | 6 | namespace codefs { 7 | class FileSystem { 8 | public: 9 | explicit FileSystem(const string &_rootPath) : rootPath(_rootPath) { 10 | boost::trim_right_if(rootPath, boost::is_any_of("/")); 11 | } 12 | 13 | virtual optional getNode(const string &path) { 14 | std::lock_guard lock(mutex); 15 | auto it = allFileData.find(path); 16 | if (it == allFileData.end()) { 17 | return nullopt; 18 | } 19 | return it->second; 20 | } 21 | 22 | void setNode(const FileData &fileData) { 23 | std::lock_guard lock(mutex); 24 | allFileData.erase(fileData.path()); 25 | if (fileData.deleted()) { 26 | // The node is deleted, Don't add 27 | LOG(INFO) << fileData.path() << " was deleted!"; 28 | } else { 29 | if (fileData.invalid()) { 30 | LOG(INFO) << "INVALIDAING " << fileData.path(); 31 | } 32 | LOG(INFO) << "UPDATING " << fileData.path(); 33 | allFileData.insert(make_pair(fileData.path(), fileData)); 34 | } 35 | } 36 | 37 | void createStub(const string &path) { 38 | std::lock_guard lock(mutex); 39 | FileData stub; 40 | stub.set_path(path); 41 | stub.set_invalid(true); 42 | setNode(stub); 43 | } 44 | 45 | void deleteNode(const string &path) { 46 | std::lock_guard lock(mutex); 47 | auto it = allFileData.find(path); 48 | if (it != allFileData.end()) { 49 | allFileData.erase(it); 50 | } 51 | } 52 | 53 | virtual string absoluteToRelative(const string &absolutePath) { 54 | if (absolutePath.find(rootPath) != 0) { 55 | LOGFATAL << "Tried to convert absolute path to fuse that wasn't inside " 56 | "the absolute FS: " 57 | << absolutePath << " " << rootPath; 58 | } 59 | string relative = absolutePath.substr(rootPath.size()); 60 | if (relative.length() == 0) { 61 | return "/"; 62 | } else { 63 | return relative; 64 | } 65 | } 66 | virtual string relativeToAbsolute(const string &relativePath) { 67 | return (boost::filesystem::path(rootPath) / relativePath).string(); 68 | } 69 | 70 | static inline void statToProto(const struct stat &fileStat, StatData *fStat) { 71 | fStat->set_dev(fileStat.st_dev); 72 | fStat->set_ino(fileStat.st_ino); 73 | fStat->set_mode(fileStat.st_mode); 74 | fStat->set_nlink(fileStat.st_nlink); 75 | fStat->set_uid(fileStat.st_uid); 76 | fStat->set_gid(fileStat.st_gid); 77 | fStat->set_rdev(fileStat.st_rdev); 78 | fStat->set_size(fileStat.st_size); 79 | fStat->set_blksize(fileStat.st_blksize); 80 | fStat->set_blocks(fileStat.st_blocks); 81 | fStat->set_atime(fileStat.st_atime); 82 | fStat->set_mtime(fileStat.st_mtime); 83 | fStat->set_ctime(fileStat.st_ctime); 84 | } 85 | 86 | static inline void protoToStat(const StatData &fStat, struct stat *fileStat) { 87 | fileStat->st_dev = fStat.dev(); 88 | fileStat->st_ino = fStat.ino(); 89 | fileStat->st_mode = fStat.mode(); 90 | fileStat->st_nlink = fStat.nlink(); 91 | fileStat->st_uid = fStat.uid(); 92 | fileStat->st_gid = fStat.gid(); 93 | fileStat->st_rdev = fStat.rdev(); 94 | fileStat->st_size = fStat.size(); 95 | fileStat->st_blksize = fStat.blksize(); 96 | fileStat->st_blocks = fStat.blocks(); 97 | fileStat->st_atime = fStat.atime(); 98 | fileStat->st_mtime = fStat.mtime(); 99 | fileStat->st_ctime = fStat.ctime(); 100 | } 101 | 102 | string serializeFileDataCompressed(const string &path); 103 | void deserializeFileDataCompressed(const string &path, const string &s); 104 | 105 | unordered_map allFileData; 106 | 107 | protected: 108 | string rootPath; 109 | shared_ptr fuseThread; 110 | std::recursive_mutex mutex; 111 | }; 112 | } // namespace codefs 113 | 114 | #ifdef __APPLE__ 115 | // Add missing xattr functions for OS/X 116 | 117 | #define G_PREFIX "org" 118 | #define G_KAUTH_FILESEC_XATTR G_PREFIX ".apple.system.Security" 119 | inline ssize_t llistxattr(const char *path, char *list, size_t size) { 120 | ssize_t res = listxattr(path, list, size, XATTR_NOFOLLOW); 121 | if (res > 0) { 122 | if (list) { 123 | size_t len = 0; 124 | char *curr = list; 125 | do { 126 | size_t thislen = strlen(curr) + 1; 127 | if (strcmp(curr, G_KAUTH_FILESEC_XATTR) == 0) { 128 | memmove(curr, curr + thislen, res - len - thislen); 129 | res -= thislen; 130 | break; 131 | } 132 | curr += thislen; 133 | len += thislen; 134 | } while (len < res); 135 | } 136 | } 137 | 138 | return res; 139 | } 140 | 141 | #define A_PREFIX "com" 142 | #define A_KAUTH_FILESEC_XATTR A_PREFIX ".apple.system.Security" 143 | #define XATTR_APPLE_PREFIX "com.apple." 144 | inline ssize_t lgetxattr(const char *path, const char *name, void *value, 145 | size_t size) { 146 | int res; 147 | 148 | if (strcmp(name, A_KAUTH_FILESEC_XATTR) == 0) { 149 | char new_name[MAXPATHLEN]; 150 | 151 | memcpy(new_name, A_KAUTH_FILESEC_XATTR, sizeof(A_KAUTH_FILESEC_XATTR)); 152 | memcpy(new_name, G_PREFIX, sizeof(G_PREFIX) - 1); 153 | 154 | res = getxattr(path, new_name, value, size, 0, XATTR_NOFOLLOW); 155 | 156 | } else { 157 | res = getxattr(path, name, value, size, 0, XATTR_NOFOLLOW); 158 | } 159 | 160 | return res; 161 | } 162 | 163 | inline int lsetxattr(const char *path, const char *name, const void *value, 164 | size_t size, int flags) { 165 | int res; 166 | 167 | if (!strncmp(name, XATTR_APPLE_PREFIX, sizeof(XATTR_APPLE_PREFIX) - 1)) { 168 | flags &= ~(XATTR_NOSECURITY); 169 | } 170 | 171 | if (!strcmp(name, A_KAUTH_FILESEC_XATTR)) { 172 | char new_name[MAXPATHLEN]; 173 | 174 | memcpy(new_name, A_KAUTH_FILESEC_XATTR, sizeof(A_KAUTH_FILESEC_XATTR)); 175 | memcpy(new_name, G_PREFIX, sizeof(G_PREFIX) - 1); 176 | 177 | res = setxattr(path, new_name, value, size, 0, XATTR_NOFOLLOW); 178 | 179 | } else { 180 | res = setxattr(path, name, value, size, 0, XATTR_NOFOLLOW); 181 | } 182 | 183 | if (res == -1) { 184 | return -errno; 185 | } 186 | 187 | return 0; 188 | } 189 | 190 | inline int lremovexattr(const char *path, const char *name) { 191 | int res; 192 | 193 | if (strcmp(name, A_KAUTH_FILESEC_XATTR) == 0) { 194 | char new_name[MAXPATHLEN]; 195 | 196 | memcpy(new_name, A_KAUTH_FILESEC_XATTR, sizeof(A_KAUTH_FILESEC_XATTR)); 197 | memcpy(new_name, G_PREFIX, sizeof(G_PREFIX) - 1); 198 | 199 | res = removexattr(path, new_name, XATTR_NOFOLLOW); 200 | 201 | } else { 202 | res = removexattr(path, name, XATTR_NOFOLLOW); 203 | } 204 | 205 | return res; 206 | } 207 | 208 | #endif 209 | 210 | #endif // __FILESYSTEM_H__ 211 | -------------------------------------------------------------------------------- /src/server/Main.cpp: -------------------------------------------------------------------------------- 1 | #include "Server.hpp" 2 | 3 | #include "LogHandler.hpp" 4 | #include "ServerFileSystem.hpp" 5 | 6 | namespace { 7 | boost::filesystem::path ROOT_PATH; 8 | shared_ptr globalFileSystem; 9 | } // namespace 10 | 11 | namespace codefs { 12 | 13 | void runFsWatch() { 14 | fsw::FSW_EVENT_CALLBACK *cb = [](const std::vector &events, 15 | void *context) { 16 | for (const auto &it : events) { 17 | if (it.get_path().find(ROOT_PATH.string()) != 0) { 18 | LOG(ERROR) << "FSWatch event on invalid path: " << it.get_path(); 19 | continue; 20 | } 21 | for (const auto &it2 : it.get_flags()) { 22 | switch (it2) { 23 | case NoOp: 24 | break; 25 | case Updated: 26 | case Link: 27 | case OwnerModified: 28 | case AttributeModified: 29 | LOG(INFO) << it.get_path() << " " << it.get_time() 30 | << fsw_get_event_flag_name(it2); 31 | globalFileSystem->rescanPathAndParent(it.get_path()); 32 | break; 33 | case Removed: 34 | case Renamed: 35 | case MovedFrom: 36 | case MovedTo: 37 | case Created: 38 | LOG(INFO) << it.get_path() << " " << it.get_time() 39 | << fsw_get_event_flag_name(it2); 40 | globalFileSystem->rescanPathAndParentAndChildren(it.get_path()); 41 | break; 42 | case IsFile: 43 | case IsDir: 44 | case IsSymLink: 45 | case PlatformSpecific: 46 | break; 47 | case Overflow: 48 | LOGFATAL << "Overflow"; 49 | default: 50 | LOGFATAL << "Unhandled flag " << it2; 51 | } 52 | } 53 | } 54 | }; 55 | 56 | // Create the default platform monitor 57 | fsw::monitor *active_monitor = fsw::monitor_factory::create_monitor( 58 | fsw_monitor_type::system_default_monitor_type, {ROOT_PATH.string()}, cb); 59 | 60 | // Configure the monitor 61 | // active_monitor->set_properties(monitor_properties); 62 | active_monitor->set_allow_overflow(true); 63 | // active_monitor->set_latency(latency); 64 | active_monitor->set_recursive(true); 65 | // active_monitor->set_directory_only(directory_only); 66 | // active_monitor->set_event_type_filters(event_filters); 67 | // active_monitor->set_filters(filters); 68 | // active_monitor->set_follow_symlinks(true); 69 | // active_monitor->set_watch_access(watch_access); 70 | 71 | // Start the monitor 72 | active_monitor->start(); 73 | } 74 | 75 | int main(int argc, char *argv[]) { 76 | // Setup easylogging configurations 77 | el::Configurations defaultConf = LogHandler::setupLogHandler(&argc, &argv); 78 | 79 | // Parse command line arguments 80 | cxxopts::Options options("et", "Remote shell for the busy and impatient"); 81 | 82 | try { 83 | options.allow_unrecognised_options(); 84 | 85 | options.add_options() // 86 | ("h,help", "Print help") // 87 | ("port", "Port to listen on", 88 | cxxopts::value()->default_value("2298")) // 89 | ("path", "Absolute path containing code for codefs to monitor", 90 | cxxopts::value()->default_value("")) // 91 | ("v,verbose", "Enable verbose logging", 92 | cxxopts::value()->default_value("0")) // 93 | ("logtostdout", "Write log to stdout") // 94 | ; 95 | 96 | auto result = options.parse(argc, argv); 97 | if (result.count("help")) { 98 | cout << options.help({}) << endl; 99 | exit(0); 100 | } 101 | 102 | if (result.count("logtostdout")) { 103 | defaultConf.setGlobally(el::ConfigurationType::ToStandardOutput, "true"); 104 | } else { 105 | defaultConf.setGlobally(el::ConfigurationType::ToStandardOutput, "false"); 106 | } 107 | 108 | if (result.count("verbose")) { 109 | el::Loggers::setVerboseLevel(result["verbose"].as()); 110 | } 111 | string maxlogsize = "20971520"; 112 | codefs::LogHandler::setupLogFile(&defaultConf, "/tmp/codefs_server.log", 113 | maxlogsize); 114 | 115 | // Reconfigure default logger to apply settings above 116 | el::Loggers::reconfigureLogger("default", defaultConf); 117 | 118 | if (!result.count("path")) { 119 | LOGFATAL << "Please specify a --path flag containing the code path"; 120 | } 121 | 122 | ROOT_PATH = boost::filesystem::canonical( 123 | boost::filesystem::path(result["path"].as())) 124 | .string(); 125 | cout << "CANONICAL PATH: " << ROOT_PATH << endl; 126 | 127 | // Check for .codefs config 128 | auto cfgPath = ROOT_PATH / boost::filesystem::path(".codefs"); 129 | set excludes; 130 | if (boost::filesystem::exists(cfgPath)) { 131 | CSimpleIniA ini(true, true, true); 132 | SI_Error rc = ini.LoadFile(cfgPath.string().c_str()); 133 | if (rc == 0) { 134 | auto relativeExcludes = 135 | split(ini.GetValue("Scanner", "Excludes", NULL), ','); 136 | for (auto exclude : relativeExcludes) { 137 | excludes.insert(boost::filesystem::path(ROOT_PATH) / exclude); 138 | } 139 | } else { 140 | LOGFATAL << "Invalid ini file: " << cfgPath; 141 | } 142 | } 143 | 144 | // Check for .watchmanconfig and update excludes 145 | auto pathToCheck = boost::filesystem::path(ROOT_PATH); 146 | while (true) { 147 | auto cfgPath = pathToCheck / boost::filesystem::path(".watchmanconfig"); 148 | if (boost::filesystem::exists(cfgPath)) { 149 | LOG(INFO) << "Found watchman config: " << cfgPath; 150 | auto configJson = json::parse(fileToStr(cfgPath.string())); 151 | for (auto ignoreDir : configJson["ignore_dirs"]) { 152 | auto absoluteIgnoreDir = pathToCheck / ignoreDir.get(); 153 | LOG(INFO) << "Adding exclude: " << absoluteIgnoreDir; 154 | excludes.insert(absoluteIgnoreDir); 155 | } 156 | break; 157 | } 158 | if (pathToCheck == "/") { 159 | break; 160 | } 161 | pathToCheck = pathToCheck.parent_path(); 162 | } 163 | 164 | shared_ptr fileSystem( 165 | new ServerFileSystem(ROOT_PATH.string(), excludes)); 166 | shared_ptr server( 167 | new Server(string("tcp://") + "0.0.0.0" + ":" + 168 | to_string(result["port"].as()), 169 | fileSystem)); 170 | 171 | globalFileSystem = fileSystem; 172 | shared_ptr watchThread(new thread(runFsWatch)); 173 | usleep(100 * 1000); 174 | 175 | fileSystem->init(); 176 | LOG(INFO) << "Server filesystem initialized"; 177 | 178 | fileSystem->setHandler(server.get()); 179 | server->init(); 180 | usleep(100 * 1000); 181 | 182 | auto lastHeartbeatTime = std::chrono::high_resolution_clock::now(); 183 | while (true) { 184 | int retval = server->update(); 185 | if (retval) { 186 | return retval; 187 | } 188 | auto msSinceLastHeartbeat = 189 | std::chrono::duration_cast( 190 | std::chrono::high_resolution_clock::now() - lastHeartbeatTime) 191 | .count(); 192 | if (msSinceLastHeartbeat >= 3000) { 193 | server->heartbeat(); 194 | lastHeartbeatTime = std::chrono::high_resolution_clock::now(); 195 | } 196 | usleep(1); 197 | } 198 | } catch (cxxopts::OptionException &oe) { 199 | cout << "Exception: " << oe.what() << "\n" << endl; 200 | cout << options.help({}) << endl; 201 | exit(1); 202 | } 203 | } 204 | } // namespace codefs 205 | 206 | int main(int argc, char *argv[]) { codefs::main(argc, argv); } 207 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.0.2) 2 | project (CodeFS VERSION 0.0.1) 3 | 4 | SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/external/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH}) 5 | FIND_PACKAGE(Sanitizers) 6 | 7 | MACRO(DECORATE_TARGET TARGET_NAME) 8 | add_sanitizers(${TARGET_NAME}) 9 | set_target_properties(${TARGET_NAME} PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "src/base/Headers.hpp") 10 | cotire(${TARGET_NAME}) 11 | ENDMACRO() 12 | 13 | SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/external/cotire/CMake" ${CMAKE_MODULE_PATH}) 14 | INCLUDE(cotire) 15 | if(POLICY CMP0058) 16 | cmake_policy(SET CMP0058 NEW) # Needed for cotire 17 | endif() 18 | 19 | option(CODE_COVERAGE "Enable code coverage" OFF) 20 | option(BUILD_CLIENT "Build the client (depends on fuse)" ON) 21 | 22 | 23 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DCODEFS_VERSION='\"${PROJECT_VERSION}\"'") 24 | 25 | # Link whole archives 26 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") 27 | # using Clang 28 | SET(WHOLE_ARCHIVE_ON "-Wl,-force_load,") 29 | SET(WHOLE_ARCHIVE_OFF "") 30 | elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 31 | # using GCC 32 | SET(WHOLE_ARCHIVE_ON "-Wl,--whole-archive") 33 | SET(WHOLE_ARCHIVE_OFF "-Wl,--no-whole-archive") 34 | else() 35 | message( FATAL_ERROR "Unsupported compiler ${CMAKE_CXX_COMPILER_ID}" ) 36 | endif() 37 | 38 | # Needed for FUSE 39 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FILE_OFFSET_BITS=64") 40 | 41 | # Debug info for getting line numbers 42 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") 43 | 44 | IF(APPLE) 45 | # Turn off address randomizing to get debug prints 46 | SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-pie") 47 | ENDIF() 48 | 49 | IF(CODE_COVERAGE) 50 | if(UNIX) 51 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") 52 | endif() 53 | ENDIF(CODE_COVERAGE) 54 | 55 | if(UNIX) 56 | # Enable C++-11 57 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11") 58 | 59 | # Enable debug info 60 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") 61 | endif() 62 | 63 | #Using FreeBSD? 64 | if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") 65 | set(FREEBSD TRUE) 66 | endif (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") 67 | 68 | # For fsevent 69 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAVE_CONFIG_H") 70 | SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_CONFIG_H") 71 | 72 | # Add cmake script directory. 73 | LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") 74 | 75 | # Required packages 76 | find_package(Threads REQUIRED) 77 | find_package(GFlags REQUIRED) 78 | find_package(ZeroMQ REQUIRED) 79 | find_package(Protobuf REQUIRED) 80 | find_package(Boost REQUIRED COMPONENTS filesystem) 81 | find_package(ZLIB REQUIRED) 82 | 83 | IF(BUILD_CLIENT) 84 | IF(APPLE) 85 | find_package(OSXFuse REQUIRED) 86 | SET(FUSE_INCLUDE_DIR ${OSXFUSE_INCLUDE_DIR}) 87 | SET(FUSE_LIBRARIES ${OSXFUSE_LIBRARIES}) 88 | ELSE(APPLE) 89 | find_package(LibFUSE REQUIRED) 90 | SET(FUSE_INCLUDE_DIR ${LIBFUSE_INCLUDE_DIRS}) 91 | SET(FUSE_LIBRARIES ${LIBFUSE_LIBRARIES}) 92 | ENDIF(APPLE) 93 | ENDIF(BUILD_CLIENT) 94 | 95 | IF(APPLE) 96 | set(CORE_LIBRARIES "-framework CoreServices" "-framework CoreFoundation" util resolv) 97 | ELSEIF(FREEBSD) 98 | set(CORE_LIBRARIES util) 99 | ELSE() 100 | set(CORE_LIBRARIES util resolv) 101 | ENDIF() 102 | 103 | PROTOBUF_GENERATE_CPP( 104 | CODEFS_SRCS 105 | CODEFS_HDRS 106 | 107 | proto/CodeFS.proto 108 | ) 109 | add_custom_target( 110 | generated-code 111 | DEPENDS 112 | 113 | ${CODEFS_SRCS} ${CODEFS_HDRS} 114 | ) 115 | 116 | include_directories( 117 | src/base 118 | external 119 | external/asio/asio/include 120 | external/json/include 121 | external/msgpack-c/include 122 | external/fswatch_config 123 | external/fswatch/libfswatch/src 124 | external/fswatch/libfswatch/src/libfswatch 125 | external/cxxopts/include 126 | ${CMAKE_CURRENT_BINARY_DIR} 127 | ${CMAKE_CURRENT_SOURCE_DIR} 128 | ${GFLAGS_INCLUDE_DIRS} 129 | ${FUSE_INCLUDE_DIR} 130 | ${Boost_INCLUDE_DIRS} 131 | ${ZMQ_INCLUDE_DIRS} 132 | ${ZLIB_INCLUDE_DIRS} 133 | ) 134 | 135 | if(APPLE) 136 | SET( 137 | OS_FSWATCH_FILES 138 | 139 | external/fswatch/libfswatch/src/libfswatch/c++/fsevents_monitor.cpp 140 | ) 141 | elseif(UNIX) 142 | SET( 143 | OS_FSWATCH_FILES 144 | 145 | external/fswatch/libfswatch/src/libfswatch/c++/inotify_monitor.cpp 146 | ) 147 | else() 148 | SET( 149 | OS_FSWATCH_FILES 150 | 151 | external/fswatch/libfswatch/src/libfswatch/c++/windows_monitor.cpp 152 | ) 153 | endif() 154 | add_library( 155 | codefs-lib 156 | STATIC 157 | 158 | external/simpleini/ConvertUTF.c 159 | 160 | external/fswatch/libfswatch/src/libfswatch/c/cevent.cpp 161 | external/fswatch/libfswatch/src/libfswatch/c/libfswatch_log.cpp 162 | external/fswatch/libfswatch/src/libfswatch/c/libfswatch.cpp 163 | external/fswatch/libfswatch/src/libfswatch/c++/event.cpp 164 | external/fswatch/libfswatch/src/libfswatch/c++/fen_monitor.cpp 165 | external/fswatch/libfswatch/src/libfswatch/c++/event.cpp 166 | external/fswatch/libfswatch/src/libfswatch/c++/libfswatch_exception.cpp 167 | external/fswatch/libfswatch/src/libfswatch/c++/monitor.cpp 168 | external/fswatch/libfswatch/src/libfswatch/c++/monitor_factory.cpp 169 | external/fswatch/libfswatch/src/libfswatch/c++/path_utils.cpp 170 | external/fswatch/libfswatch/src/libfswatch/c++/poll_monitor.cpp 171 | external/fswatch/libfswatch/src/libfswatch/c++/string/string_utils.cpp 172 | ${OS_FSWATCH_FILES} 173 | 174 | src/base/EasyLoggingWrapper.cpp 175 | 176 | src/base/LogHandler.hpp 177 | src/base/LogHandler.cpp 178 | 179 | src/base/DaemonCreator.hpp 180 | src/base/DaemonCreator.cpp 181 | 182 | src/base/PidController.hpp 183 | src/base/PidController.cpp 184 | 185 | src/base/FileSystem.hpp 186 | src/base/FileSystem.cpp 187 | 188 | src/base/FileUtils.hpp 189 | src/base/FileUtils.cpp 190 | 191 | src/base/BiDirectionalRpc.hpp 192 | src/base/BiDirectionalRpc.cpp 193 | 194 | src/base/ZmqBiDirectionalRpc.hpp 195 | src/base/ZmqBiDirectionalRpc.cpp 196 | 197 | src/base/TimeHandler.hpp 198 | src/base/TimeHandler.cpp 199 | 200 | ${CODEFS_SRCS} 201 | ) 202 | add_dependencies( 203 | codefs-lib 204 | generated-code 205 | ) 206 | DECORATE_TARGET(codefs-lib) 207 | 208 | IF(BUILD_CLIENT) 209 | add_executable ( 210 | codefs 211 | 212 | src/client/Client.hpp 213 | src/client/Client.cpp 214 | 215 | src/client/ClientFuseAdapter.hpp 216 | src/client/ClientFuseAdapter.cpp 217 | 218 | src/client/Main.cpp 219 | ) 220 | 221 | target_link_libraries ( 222 | codefs 223 | LINK_PUBLIC 224 | ${WHOLE_ARCHIVE_ON} codefs-lib 225 | ${WHOLE_ARCHIVE_OFF} ${ZMQ_LIBRARIES} 226 | ${CMAKE_THREAD_LIBS_INIT} 227 | ${PROTOBUF_LIBRARIES} 228 | ${GFLAGS_LIBRARIES} 229 | ${Boost_LIBRARIES} 230 | ${FUSE_LIBRARIES} 231 | ${ZLIB_LIBRARY_RELEASE} 232 | ${CORE_LIBRARIES} 233 | ) 234 | DECORATE_TARGET(codefs) 235 | ENDIF(BUILD_CLIENT) 236 | 237 | add_executable ( 238 | codefsserver 239 | 240 | src/server/ServerFileSystem.cpp 241 | src/server/Server.cpp 242 | src/server/fswatchexample.cpp 243 | 244 | src/server/Main.cpp 245 | ) 246 | 247 | target_link_libraries ( 248 | codefsserver 249 | LINK_PUBLIC 250 | ${WHOLE_ARCHIVE_ON} codefs-lib 251 | ${WHOLE_ARCHIVE_OFF} ${ZMQ_LIBRARIES} 252 | ${CMAKE_THREAD_LIBS_INIT} 253 | ${PROTOBUF_LIBRARIES} 254 | ${GFLAGS_LIBRARIES} 255 | ${Boost_LIBRARIES} 256 | ${ZLIB_LIBRARY_RELEASE} 257 | ${CORE_LIBRARIES} 258 | ) 259 | DECORATE_TARGET(codefsserver) 260 | 261 | file(GLOB TEST_SRCS test/Test*.cpp ) 262 | add_executable( 263 | codefs-test 264 | 265 | ${TEST_SRCS} 266 | ) 267 | add_dependencies( 268 | codefs-test 269 | 270 | codefs-lib) 271 | 272 | target_link_libraries( 273 | codefs-test 274 | ${WHOLE_ARCHIVE_ON} codefs-lib 275 | ${WHOLE_ARCHIVE_OFF} ${ZMQ_LIBRARIES} 276 | ${CMAKE_THREAD_LIBS_INIT} 277 | ${PROTOBUF_LIBRARIES} 278 | ${GFLAGS_LIBRARIES} 279 | ${sodium_LIBRARY_RELEASE} 280 | ${Boost_LIBRARIES} 281 | ${ZLIB_LIBRARIES} 282 | ${ZLIB_LIBRARY_RELEASE} 283 | ${CORE_LIBRARIES} 284 | ) 285 | add_test( 286 | codefs-test 287 | codefs-test 288 | ) 289 | DECORATE_TARGET(codefs-test) 290 | 291 | install(TARGETS codefs codefsserver 292 | PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ 293 | DESTINATION "bin" 294 | ) 295 | -------------------------------------------------------------------------------- /src/base/Headers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CODEFS_HEADERS__ 2 | #define __CODEFS_HEADERS__ 3 | 4 | #define ELPP_THREAD_SAFE (1) 5 | 6 | // Enable standalone asio 7 | #ifndef ASIO_STANDALONE 8 | #define ASIO_STANDALONE (1) 9 | #endif 10 | #ifndef USE_STANDALONE_ASIO 11 | #define USE_STANDALONE_ASIO (1) 12 | #endif 13 | 14 | #include 15 | 16 | #if __APPLE__ 17 | #include 18 | #include 19 | #include 20 | #include 21 | #elif __FreeBSD__ 22 | #define _WITH_GETLINE 23 | #include 24 | #elif __NetBSD__ // do not need pty.h on NetBSD 25 | #else 26 | #include 27 | #endif 28 | 29 | #if __NetBSD__ 30 | #include 31 | #endif 32 | 33 | #ifndef __APPLE__ 34 | extern "C" { 35 | #include 36 | } 37 | #endif 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | 80 | #define FUSE_USE_VERSION 26 81 | #include "fuse.h" 82 | 83 | #include 84 | 85 | #include "CTPL/ctpl_stl.h" 86 | 87 | #include "simpleini/SimpleIni.h" 88 | 89 | #include "UniversalStacktrace/ust/ust.hpp" 90 | #include "easyloggingpp/src/easylogging++.h" 91 | 92 | #define LOGFATAL (LOG(ERROR) << "\n" << ust::generate(), LOG(FATAL)) 93 | 94 | #include "zlib.h" 95 | 96 | #include "cppcodec/cppcodec/base64_default_rfc4648.hpp" 97 | using base64 = cppcodec::base64_rfc4648; 98 | 99 | #include "CTPL/ctpl_stl.h" 100 | #include "msgpack.hpp" 101 | #include "nlohmann/json.hpp" 102 | #include "sole/sole.hpp" 103 | #include "zmq.hpp" 104 | #include "zmq_addon.hpp" 105 | 106 | #include "Optional/optional.hpp" 107 | using namespace std::experimental; 108 | 109 | #include "CodeFS.pb.h" 110 | 111 | #include 112 | #include 113 | 114 | #define HAVE_CXX_MUTEX (1) 115 | #include "libfswatch/c++/monitor.hpp" 116 | #include "libfswatch/c++/monitor_factory.hpp" 117 | 118 | using namespace std; 119 | 120 | namespace google {} 121 | namespace gflags {} 122 | using namespace google; 123 | using namespace gflags; 124 | 125 | using json = nlohmann::json; 126 | 127 | // The protocol version supported by this binary 128 | static const int PROTOCOL_VERSION = 1; 129 | 130 | #define FATAL_IF_FALSE(X) \ 131 | if (((X) == false)) \ 132 | LOGFATAL << "Error: (" << errno << "): " << strerror(errno); 133 | 134 | #define FATAL_IF_FALSE_NOT_EAGAIN(X) \ 135 | if (((X) == false)) { \ 136 | if (errno == EAGAIN) { \ 137 | VLOG(10) << "Could not complete: (" << errno \ 138 | << "): " << strerror(errno); \ 139 | } else { \ 140 | LOGFATAL << "Error: (" << errno << "): " << strerror(errno); \ 141 | } \ 142 | } 143 | 144 | #define FATAL_FAIL(X) \ 145 | if (((X) == -1)) LOGFATAL << "Error: (" << errno << "): " << strerror(errno); 146 | 147 | #define DRAW_FROM_UNORDERED(ITERATOR, COLLECTION) \ 148 | auto ITERATOR = COLLECTION.begin(); \ 149 | std::advance(ITERATOR, rand() % COLLECTION.size()); 150 | 151 | template 152 | inline void split(const std::string& s, char delim, Out result) { 153 | std::stringstream ss; 154 | ss.str(s); 155 | std::string item; 156 | while (std::getline(ss, item, delim)) { 157 | *(result++) = item; 158 | } 159 | } 160 | 161 | inline std::vector split(const std::string& s, char delim) { 162 | std::vector elems; 163 | split(s, delim, std::back_inserter(elems)); 164 | return elems; 165 | } 166 | 167 | inline std::string SystemToStr(const char* cmd) { 168 | std::array buffer; 169 | std::string result; 170 | std::shared_ptr pipe(popen(cmd, "r"), pclose); 171 | if (!pipe) throw std::runtime_error("popen() failed!"); 172 | while (!feof(pipe.get())) { 173 | if (fgets(buffer.data(), 128, pipe.get()) != nullptr) 174 | result += buffer.data(); 175 | } 176 | return result; 177 | } 178 | 179 | inline std::string fileToStr(const string& path) { 180 | std::ifstream t(path); 181 | std::stringstream buffer; 182 | buffer << t.rdbuf(); 183 | return buffer.str(); 184 | } 185 | 186 | inline bool replace(std::string& str, const std::string& from, 187 | const std::string& to) { 188 | size_t start_pos = str.find(from); 189 | if (start_pos == std::string::npos) return false; 190 | str.replace(start_pos, from.length(), to); 191 | return true; 192 | } 193 | 194 | inline int replaceAll(std::string& str, const std::string& from, 195 | const std::string& to) { 196 | if (from.empty()) return 0; 197 | int retval = 0; 198 | size_t start_pos = 0; 199 | while ((start_pos = str.find(from, start_pos)) != std::string::npos) { 200 | retval++; 201 | str.replace(start_pos, from.length(), to); 202 | start_pos += to.length(); // In case 'to' contains 'from', like replacing 203 | // 'x' with 'yx' 204 | } 205 | return retval; 206 | } 207 | 208 | /** Compress a STL string using zlib with given compression level and return 209 | * the binary data. */ 210 | inline std::string compressString(const std::string& str, 211 | int compressionlevel = Z_BEST_COMPRESSION) { 212 | z_stream zs; // z_stream is zlib's control structure 213 | memset(&zs, 0, sizeof(zs)); 214 | 215 | if (deflateInit(&zs, compressionlevel) != Z_OK) 216 | throw(std::runtime_error("deflateInit failed while compressing.")); 217 | 218 | zs.next_in = (Bytef*)str.data(); 219 | zs.avail_in = str.size(); // set the z_stream's input 220 | 221 | int ret; 222 | char outbuffer[32768]; 223 | std::string outstring; 224 | 225 | // retrieve the compressed bytes blockwise 226 | do { 227 | zs.next_out = reinterpret_cast(outbuffer); 228 | zs.avail_out = sizeof(outbuffer); 229 | 230 | ret = deflate(&zs, Z_FINISH); 231 | 232 | if (outstring.size() < zs.total_out) { 233 | // append the block to the output string 234 | outstring.append(outbuffer, zs.total_out - outstring.size()); 235 | } 236 | } while (ret == Z_OK); 237 | 238 | deflateEnd(&zs); 239 | 240 | if (ret != Z_STREAM_END) { // an error occurred that was not EOF 241 | std::ostringstream oss; 242 | oss << "Exception during zlib compression: (" << ret << ") " << zs.msg; 243 | throw(std::runtime_error(oss.str())); 244 | } 245 | 246 | return outstring; 247 | } 248 | 249 | /** Decompress an STL string using zlib and return the original data. */ 250 | inline std::string decompressString(const std::string& str) { 251 | z_stream zs; // z_stream is zlib's control structure 252 | memset(&zs, 0, sizeof(zs)); 253 | 254 | if (inflateInit(&zs) != Z_OK) 255 | throw(std::runtime_error("inflateInit failed while decompressing.")); 256 | 257 | zs.next_in = (Bytef*)str.data(); 258 | zs.avail_in = str.size(); 259 | 260 | int ret; 261 | char outbuffer[32768]; 262 | std::string outstring; 263 | 264 | // get the decompressed bytes blockwise using repeated calls to inflate 265 | do { 266 | zs.next_out = reinterpret_cast(outbuffer); 267 | zs.avail_out = sizeof(outbuffer); 268 | 269 | ret = inflate(&zs, 0); 270 | 271 | if (outstring.size() < zs.total_out) { 272 | outstring.append(outbuffer, zs.total_out - outstring.size()); 273 | } 274 | 275 | } while (ret == Z_OK); 276 | 277 | inflateEnd(&zs); 278 | 279 | if (ret != Z_STREAM_END) { // an error occurred that was not EOF 280 | std::ostringstream oss; 281 | oss << "Exception during zlib decompression: (" << ret << ") " << zs.msg; 282 | throw(std::runtime_error(oss.str())); 283 | } 284 | 285 | return outstring; 286 | } 287 | 288 | #endif 289 | -------------------------------------------------------------------------------- /src/client/ClientFileSystem.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CODEFS_CLIENT_FILE_SYSTEM_H__ 2 | #define __CODEFS_CLIENT_FILE_SYSTEM_H__ 3 | 4 | #include "FileSystem.hpp" 5 | 6 | namespace codefs { 7 | class OwnedFileInfo { 8 | public: 9 | unordered_set fds; 10 | string content; 11 | bool readOnly; 12 | 13 | OwnedFileInfo() : readOnly(false) {} 14 | 15 | OwnedFileInfo(int fd, string _content, bool _readOnly) 16 | : content(_content), readOnly(_readOnly) { 17 | fds.insert(fd); 18 | } 19 | 20 | explicit OwnedFileInfo(const OwnedFileInfo& other) { 21 | fds = other.fds; 22 | content = other.content; 23 | readOnly = other.readOnly; 24 | } 25 | }; 26 | 27 | class ClientFileSystem : public FileSystem { 28 | public: 29 | explicit ClientFileSystem(const string& _rootPath) 30 | : FileSystem(_rootPath), fdCounter(1) {} 31 | 32 | virtual ~ClientFileSystem() {} 33 | 34 | inline void invalidatePath(const string& path) { 35 | std::lock_guard lock(mutex); 36 | invalidateVfsCache(); 37 | LOG(INFO) << "INVALIDATING PATH: " << path; 38 | { 39 | auto it = fileCache.find(path); 40 | if (it != fileCache.end()) { 41 | fileCache.erase(it); 42 | } 43 | } 44 | auto it = allFileData.find(path); 45 | if (it == allFileData.end()) { 46 | // Create empty invalid node 47 | FileData fd; 48 | fd.set_invalid(true); 49 | allFileData[path] = fd; 50 | return; 51 | } 52 | it->second.set_invalid(true); 53 | } 54 | 55 | inline void invalidatePathAndParent(const string& path) { 56 | std::lock_guard lock(mutex); 57 | invalidateVfsCache(); 58 | invalidatePath(boost::filesystem::path(path).parent_path().string()); 59 | invalidatePath(path); 60 | } 61 | 62 | inline void invalidatePathAndParentAndChildren(const string& path) { 63 | std::lock_guard lock(mutex); 64 | invalidateVfsCache(); 65 | invalidatePathAndParent(path); 66 | for (auto& it : allFileData) { 67 | if (it.first.find(path + string("/")) == 0) { 68 | LOG(INFO) << "INVALIDATING " << it.first; 69 | it.second.set_invalid(true); 70 | } 71 | } 72 | } 73 | 74 | inline vector getPathsToDownload(const string& path) { 75 | std::lock_guard lock(mutex); 76 | auto it = allFileData.find(path); 77 | if (it != allFileData.end()) { 78 | // We already have this path, let's make sure we also have all the 79 | // children 80 | const auto& fd = it->second; 81 | if (fd.child_node_size() == 0) { 82 | return {}; 83 | } 84 | bool haveAllChildren = true; 85 | for (const auto& childName : fd.child_node()) { 86 | auto childPath = (boost::filesystem::path(path) / childName).string(); 87 | if (allFileData.find(childPath) == allFileData.end()) { 88 | haveAllChildren = false; 89 | break; 90 | } 91 | } 92 | if (haveAllChildren) { 93 | return {}; 94 | } else { 95 | return {path}; 96 | } 97 | } 98 | if (path == string("/")) { 99 | LOGFATAL << "Somehow we don't have the root path???"; 100 | } 101 | auto parentPath = boost::filesystem::path(path).parent_path().string(); 102 | vector retval = getPathsToDownload(parentPath); 103 | if (retval.empty()) { 104 | // If we know the parent directory, then we know all children of the 105 | // parent directory, so this file doesn't exist and doesn't need to be 106 | // scanned 107 | } else { 108 | retval.push_back(path); 109 | } 110 | return retval; 111 | } 112 | 113 | inline optional getCachedFile(const string& path) { 114 | std::lock_guard lock(mutex); 115 | auto it = fileCache.find(path); 116 | if (it == fileCache.end()) { 117 | return optional(); 118 | } else { 119 | return it->second; 120 | } 121 | } 122 | 123 | inline void setCachedFile(const string& path, const string& data) { 124 | std::lock_guard lock(mutex); 125 | fileCache[path] = data; 126 | } 127 | 128 | inline void invalidateVfsCache() { 129 | std::lock_guard lock(mutex); 130 | cachedStatVfsProto.reset(); 131 | } 132 | 133 | inline int getNewFd() { 134 | std::lock_guard lock(mutex); 135 | fdCounter++; 136 | return fdCounter; 137 | } 138 | 139 | inline bool ownsPathContents(const string& path) { 140 | std::lock_guard lock(mutex); 141 | return ownedFileContents.find(path) != ownedFileContents.end(); 142 | } 143 | 144 | inline void addOwnedFileContents(const string& path, int fd, 145 | const string& cachedData, bool readOnly) { 146 | std::lock_guard lock(mutex); 147 | ownedFileContents[path] = OwnedFileInfo(fd, cachedData, readOnly); 148 | } 149 | 150 | inline void addHandleToOwnedFile(const string& path, int fd, bool readOnly) { 151 | std::lock_guard lock(mutex); 152 | ownedFileContents.at(path).fds.insert(fd); 153 | if (!readOnly) { 154 | ownedFileContents.at(path).readOnly = false; 155 | } 156 | } 157 | 158 | inline int readOwnedFile(const string& path, char* buf, int64_t size, 159 | int64_t offset) { 160 | std::lock_guard lock(mutex); 161 | auto it = ownedFileContents.find(path); 162 | if (it == ownedFileContents.end()) { 163 | LOGFATAL << "TRIED TO READ AN INVALID PATH"; 164 | } 165 | const auto& content = it->second.content; 166 | if (offset >= int64_t(content.size())) { 167 | return 0; 168 | } 169 | auto start = content.c_str() + offset; 170 | int64_t actualSize = min(int64_t(content.size()), offset + size) - offset; 171 | LOG(INFO) << content.size() << " " << size << " " << offset << " " 172 | << actualSize << endl; 173 | memcpy(buf, start, actualSize); 174 | return actualSize; 175 | } 176 | 177 | inline int writeOwnedFile(const string& path, const char* buf, int64_t size, 178 | int64_t offset) { 179 | std::lock_guard lock(mutex); 180 | auto it = ownedFileContents.find(path); 181 | if (it == ownedFileContents.end()) { 182 | LOGFATAL << "TRIED TO READ AN INVALID PATH: " << path; 183 | } 184 | if (it->second.readOnly) { 185 | LOGFATAL << "Tried to write to a read-only file: " << path; 186 | } 187 | auto& content = it->second.content; 188 | LOG(INFO) << "WRITING " << size << " TO " << path << " AT " << offset; 189 | if (int64_t(content.size()) < offset + size) { 190 | content.resize(offset + size, '\0'); 191 | } 192 | memcpy(&(content[offset]), buf, size); 193 | return size; 194 | } 195 | 196 | inline void closeOwnedFile(const string& path, int fd, bool* readOnly, 197 | string* content) { 198 | std::lock_guard lock(mutex); 199 | auto& ownedFile = ownedFileContents.at(path); 200 | if (ownedFile.fds.find(fd) == ownedFile.fds.end()) { 201 | LOGFATAL << "Tried to close a file handle that is not owned"; 202 | } 203 | if (!ownedFile.readOnly) { 204 | LOG(INFO) << "Invalidating path"; 205 | invalidatePathAndParent(path); 206 | } 207 | *readOnly = ownedFile.readOnly; 208 | *content = ownedFile.content; 209 | ownedFile.fds.erase(ownedFile.fds.find(fd)); 210 | if (ownedFile.fds.empty()) { 211 | ownedFileContents.erase(path); 212 | } 213 | } 214 | 215 | optional getSizeOverride(const string& path) { 216 | std::lock_guard lock(mutex); 217 | auto it = ownedFileContents.find(path); 218 | if (it == ownedFileContents.end()) { 219 | return optional(); 220 | } 221 | return int64_t(it->second.content.size()); 222 | } 223 | 224 | bool truncateOwnedFileIfExists(const string& path, int64_t size) { 225 | std::lock_guard lock(mutex); 226 | if (ownedFileContents.find(path) != ownedFileContents.end()) { 227 | ownedFileContents.at(path).content.resize(size, '\0'); 228 | return true; 229 | } 230 | return false; 231 | } 232 | 233 | optional getVfsCache() { 234 | std::lock_guard lock(mutex); 235 | return cachedStatVfsProto; 236 | } 237 | 238 | void setVfsCache(const StatVfsData& vfs) { 239 | std::lock_guard lock(mutex); 240 | cachedStatVfsProto = vfs; 241 | } 242 | 243 | void renameOwnedFileIfItExists(const string& from, const string& to) { 244 | std::lock_guard lock(mutex); 245 | if (ownedFileContents.find(to) != ownedFileContents.end()) { 246 | LOGFATAL << "I don't handle renaming from one open file to another yet"; 247 | } 248 | if (ownedFileContents.find(from) != ownedFileContents.end()) { 249 | ownedFileContents[to] = ownedFileContents.at(from); 250 | ownedFileContents.erase(ownedFileContents.find(from)); 251 | } 252 | } 253 | 254 | protected: 255 | unordered_map fileCache; 256 | unordered_map ownedFileContents; 257 | optional cachedStatVfsProto; 258 | int fdCounter; 259 | }; 260 | } // namespace codefs 261 | 262 | #endif // __CODEFS_CLIENT_FILE_SYSTEM_H__ -------------------------------------------------------------------------------- /src/server/ServerFileSystem.cpp: -------------------------------------------------------------------------------- 1 | #include "ServerFileSystem.hpp" 2 | 3 | namespace codefs { 4 | ServerFileSystem::ServerFileSystem( 5 | const string& _rootPath, const set& _excludes) 6 | : FileSystem(_rootPath), 7 | initialized(false), 8 | handler(NULL), 9 | excludes(_excludes) {} 10 | 11 | void ServerFileSystem::init() { 12 | scanRecursively(rootPath); 13 | initialized = true; 14 | } 15 | 16 | void ServerFileSystem::rescanPath(const string& absolutePath) { 17 | std::lock_guard lock(mutex); 18 | scanNode(absolutePath); 19 | } 20 | 21 | string ServerFileSystem::readFile(const string& path) { 22 | std::lock_guard lock(mutex); 23 | return fileToStr(relativeToAbsolute(path)); 24 | } 25 | 26 | int ServerFileSystem::writeFile(const string& path, 27 | const string& fileContents) { 28 | std::lock_guard lock(mutex); 29 | FILE* fp = ::fopen(relativeToAbsolute(path).c_str(), "wb"); 30 | if (fp == NULL) { 31 | return -1; 32 | } 33 | size_t bytesWritten = 0; 34 | while (bytesWritten < fileContents.length()) { 35 | size_t written = ::fwrite(fileContents.c_str() + bytesWritten, 1, 36 | fileContents.length() - bytesWritten, fp); 37 | bytesWritten += written; 38 | } 39 | ::fclose(fp); 40 | rescanPath(relativeToAbsolute(path)); 41 | return 0; 42 | } 43 | 44 | const int MAX_XATTR_SIZE = 64 * 1024; 45 | 46 | void ServerFileSystem::scanRecursively( 47 | const string& path_string, shared_ptr scanThreadPool) { 48 | bool waitUntilFinished = false; 49 | if (scanThreadPool.get() == NULL) { 50 | // scanThreadPool.reset(new ctpl::thread_pool(8)); 51 | waitUntilFinished = true; 52 | } 53 | 54 | boost::filesystem::path pt(path_string); 55 | if (!exists(pt)) { 56 | return; 57 | } 58 | auto path = 59 | boost::filesystem::canonical(boost::filesystem::path(path_string)); 60 | if (excludes.find(path) != excludes.end()) { 61 | LOG(INFO) << "Ignoring " << path; 62 | return; 63 | } 64 | 65 | std::lock_guard lock(mutex); 66 | VLOG(1) << "SCANNING DIRECTORY " << path_string; 67 | scanNode(path_string); 68 | 69 | if (boost::filesystem::is_directory(pt)) { 70 | // path exists 71 | for (auto& p : boost::make_iterator_range( 72 | boost::filesystem::directory_iterator(pt), {})) { 73 | string p_str = p.path().string(); 74 | if (boost::filesystem::is_symlink(p.path()) || 75 | boost::filesystem::is_regular_file(p.path())) { 76 | // LOG(INFO) << "SCANNING FILE " << p_str; 77 | this->scanNode(p_str); 78 | } else if (boost::filesystem::is_directory(p.path())) { 79 | scanRecursively(p_str, scanThreadPool); 80 | } else { 81 | LOG(INFO) << p 82 | << " exists, but is neither a regular file nor a directory"; 83 | } 84 | } 85 | } else { 86 | LOG(INFO) << "path " << path_string << "isn't a directory!"; 87 | } 88 | 89 | VLOG(1) << "RECURSIVE SCAN FINISHED"; 90 | if (waitUntilFinished) { 91 | scanThreadPool.reset(); 92 | } 93 | } 94 | 95 | void ServerFileSystem::scanNode(const string& path) { 96 | auto pathObj = boost::filesystem::path(path); 97 | try { 98 | if (exists(pathObj)) { 99 | pathObj = boost::filesystem::canonical(pathObj); 100 | } 101 | } catch (const boost::filesystem::filesystem_error& ex) { 102 | // Can happen for self-referencing symbolic links 103 | LOG(ERROR) << "Error resolving file: " << ex.what(); 104 | } 105 | if (excludes.find(pathObj) != excludes.end()) { 106 | LOG(INFO) << "Ignoring " << pathObj; 107 | return; 108 | } 109 | 110 | FileData fd; 111 | { 112 | std::lock_guard lock(mutex); 113 | VLOG(1) << "SCANNING NODE : " << path; 114 | 115 | fd.set_path(absoluteToRelative(path)); 116 | fd.set_deleted(false); 117 | fd.set_invalid(false); 118 | 119 | #if __APPLE__ 120 | // faccessat doesn't have AT_SYMLINK_NOFOLLOW 121 | bool symlinkToDeadFile = false; 122 | if (::faccessat(0, path.c_str(), F_OK, 0) != 0) { 123 | // The file is gone, but this could be a symlink and the symlink could 124 | // still be alive. 125 | 126 | if (boost::filesystem::symbolic_link_exists(path)) { 127 | symlinkToDeadFile = true; 128 | } else { 129 | VLOG(1) << "FILE IS GONE: " << path << " " << errno; 130 | { 131 | std::lock_guard lock(mutex); 132 | allFileData.erase(absoluteToRelative(path)); 133 | } 134 | fd.set_deleted(true); 135 | if (handler != NULL) { 136 | VLOG(1) << "UPDATING METADATA: " << path; 137 | handler->metadataUpdated(absoluteToRelative(path), fd); 138 | } 139 | 140 | return; 141 | } 142 | } 143 | 144 | if (symlinkToDeadFile) { 145 | // TODO: Re-implement access(). Until then, clients will think they can 146 | // edit symlinks when they cant. 147 | fd.set_can_read(true); 148 | fd.set_can_write(true); 149 | fd.set_can_execute(true); 150 | } else { 151 | if (::faccessat(0, path.c_str(), R_OK, 0) == 0) { 152 | fd.set_can_read(true); 153 | } else { 154 | fd.set_can_read(false); 155 | } 156 | if (::faccessat(0, path.c_str(), W_OK, 0) == 0) { 157 | fd.set_can_write(true); 158 | } else { 159 | fd.set_can_write(false); 160 | } 161 | if (::faccessat(0, path.c_str(), X_OK, 0) == 0) { 162 | fd.set_can_execute(true); 163 | } else { 164 | fd.set_can_execute(false); 165 | } 166 | } 167 | #else 168 | if (::faccessat(0, path.c_str(), F_OK, AT_SYMLINK_NOFOLLOW) != 0) { 169 | VLOG(1) << "FILE IS GONE: " << path << " " << errno; 170 | // The file is gone 171 | { 172 | std::lock_guard lock(mutex); 173 | allFileData.erase(absoluteToRelative(path)); 174 | } 175 | fd.set_deleted(true); 176 | if (handler != NULL) { 177 | VLOG(1) << "UPDATING METADATA: " << path; 178 | handler->metadataUpdated(absoluteToRelative(path), fd); 179 | } 180 | 181 | return; 182 | } 183 | 184 | if (::faccessat(0, path.c_str(), R_OK, AT_SYMLINK_NOFOLLOW) == 0) { 185 | fd.set_can_read(true); 186 | } else { 187 | fd.set_can_read(false); 188 | } 189 | if (::faccessat(0, path.c_str(), W_OK, AT_SYMLINK_NOFOLLOW) == 0) { 190 | fd.set_can_write(true); 191 | } else { 192 | fd.set_can_write(false); 193 | } 194 | if (::faccessat(0, path.c_str(), X_OK, AT_SYMLINK_NOFOLLOW) == 0) { 195 | fd.set_can_execute(true); 196 | } else { 197 | fd.set_can_execute(false); 198 | } 199 | #endif 200 | 201 | struct stat fileStat; 202 | FATAL_FAIL(lstat(path.c_str(), &fileStat)); 203 | 204 | StatData fStat; 205 | FileSystem::statToProto(fileStat, &fStat); 206 | *(fd.mutable_stat_data()) = fStat; 207 | if (S_ISLNK(fileStat.st_mode)) { 208 | int bufsiz = fileStat.st_size + 1; 209 | 210 | /* Some magic symlinks under (for example) /proc and /sys 211 | report 'st_size' as zero. In that case, take PATH_MAX as 212 | a "good enough" estimate. */ 213 | 214 | if (fileStat.st_size == 0) { 215 | bufsiz = PATH_MAX; 216 | } 217 | 218 | string s(bufsiz, '\0'); 219 | int nbytes = readlink(path.c_str(), &s[0], bufsiz); 220 | FATAL_FAIL(nbytes); 221 | s = s.substr(0, nbytes + 1); 222 | if (s[0] == '/') { 223 | if (s.find(rootPath) == 0) { 224 | s = absoluteToRelative(s); 225 | } else { 226 | // This symlink goes outside the root directory. 227 | } 228 | } 229 | fd.set_symlink_contents(s); 230 | } 231 | 232 | if (S_ISDIR(fileStat.st_mode)) { 233 | // Populate children 234 | for (auto& it : boost::make_iterator_range( 235 | boost::filesystem::directory_iterator(path), {})) { 236 | if (boost::filesystem::is_symlink(it.path()) || 237 | boost::filesystem::is_regular_file(it.path()) || 238 | boost::filesystem::is_directory(it.path())) { 239 | auto childPath = it.path(); 240 | VLOG(1) << "GOT CHILD PATH: " << childPath; 241 | try { 242 | if (exists(childPath)) { 243 | childPath = boost::filesystem::canonical(childPath); 244 | if (excludes.find(childPath) == excludes.end()) { 245 | VLOG(1) << "ADDING CHILD NAME: " 246 | << it.path().filename().string(); 247 | fd.add_child_node(it.path().filename().string()); 248 | } 249 | } 250 | } catch (const boost::filesystem::filesystem_error& ex) { 251 | // Can happen for self-referencing symbolic links 252 | LOG(ERROR) << "Error resolving file: " << ex.what(); 253 | } 254 | } 255 | } 256 | } 257 | 258 | // Load extended attributes 259 | { 260 | string xattrBuffer = string(MAX_XATTR_SIZE, '\0'); 261 | memset(&xattrBuffer[0], '\0', MAX_XATTR_SIZE); 262 | auto listSize = llistxattr(path.c_str(), &xattrBuffer[0], MAX_XATTR_SIZE); 263 | FATAL_FAIL(listSize); 264 | string s(&xattrBuffer[0], listSize); 265 | vector keys = split(s, '\0'); 266 | for (string key : keys) { 267 | auto xattrSize = lgetxattr(path.c_str(), key.c_str(), &xattrBuffer[0], 268 | MAX_XATTR_SIZE); 269 | FATAL_FAIL(xattrSize); 270 | string value(&xattrBuffer[0], xattrSize); 271 | fd.add_xattr_key(key); 272 | fd.add_xattr_value(value); 273 | } 274 | } 275 | 276 | VLOG(1) << "SETTING: " << absoluteToRelative(path); 277 | { 278 | std::lock_guard lock(mutex); 279 | allFileData[absoluteToRelative(path)] = fd; 280 | } 281 | } 282 | 283 | if (handler != NULL) { 284 | VLOG(1) << "UPDATING METADATA: " << path; 285 | handler->metadataUpdated(absoluteToRelative(path), fd); 286 | } 287 | } 288 | 289 | } // namespace codefs 290 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/base/BiDirectionalRpc.cpp: -------------------------------------------------------------------------------- 1 | #include "BiDirectionalRpc.hpp" 2 | 3 | #include "TimeHandler.hpp" 4 | 5 | namespace codefs { 6 | BiDirectionalRpc::BiDirectionalRpc() 7 | : onBarrier(0), 8 | onId(0), 9 | flaky(false), 10 | timeOffsetController(1.0, 1000000, -1000000, 0.6, 1.2, 1.0) {} 11 | 12 | BiDirectionalRpc::~BiDirectionalRpc() {} 13 | 14 | void BiDirectionalRpc::shutdown() {} 15 | 16 | void BiDirectionalRpc::heartbeat() { 17 | lock_guard guard(mutex); 18 | // TODO: If the outgoingReplies/requests is high, and we have recently 19 | // received data, flush a lot of data out 20 | VLOG(1) << "BEAT: " << int64_t(this); 21 | if (!outgoingReplies.empty() || !outgoingRequests.empty()) { 22 | resendRandomOutgoingMessage(); 23 | } else { 24 | VLOG(1) << "SENDING HEARTBEAT"; 25 | string s = "0"; 26 | s[0] = HEARTBEAT; 27 | send(s); 28 | } 29 | } 30 | 31 | void BiDirectionalRpc::resendRandomOutgoingMessage() { 32 | if (!outgoingReplies.empty() && 33 | (outgoingRequests.empty() || rand() % 2 == 0)) { 34 | // Re-send a random reply 35 | DRAW_FROM_UNORDERED(it, outgoingReplies); 36 | sendReply(it->first, it->second); 37 | } else if (!outgoingRequests.empty()) { 38 | // Re-send a random request 39 | DRAW_FROM_UNORDERED(it, outgoingRequests); 40 | sendRequest(it->first, it->second); 41 | } else { 42 | } 43 | } 44 | 45 | void BiDirectionalRpc::receive(const string& message) { 46 | lock_guard guard(mutex); 47 | VLOG(1) << "Receiving message with length " << message.length(); 48 | MessageReader reader; 49 | reader.load(message); 50 | RpcHeader header = (RpcHeader)reader.readPrimitive(); 51 | if (flaky && rand() % 2 == 0) { 52 | // Pretend we never got the message 53 | VLOG(1) << "FLAKE"; 54 | } else { 55 | if (header != HEARTBEAT) { 56 | VLOG(1) << "GOT PACKET WITH HEADER " << header; 57 | } 58 | switch (header) { 59 | case HEARTBEAT: { 60 | // MultiEndpointHandler deals with keepalive 61 | } break; 62 | case REQUEST: { 63 | while (reader.sizeRemaining()) { 64 | RpcId rpcId = reader.readClass(); 65 | string payload = reader.readPrimitive(); 66 | handleRequest(rpcId, payload); 67 | } 68 | } break; 69 | case REPLY: { 70 | while (reader.sizeRemaining()) { 71 | RpcId uid = reader.readClass(); 72 | int64_t requestReceiptTime = reader.readPrimitive(); 73 | int64_t replySendTime = reader.readPrimitive(); 74 | auto requestSendTimeIt = requestSendTimeMap.find(uid); 75 | if (requestSendTimeIt != requestSendTimeMap.end()) { 76 | int64_t requestSendTime = requestSendTimeIt->second; 77 | requestSendTimeMap.erase(requestSendTimeIt); 78 | int64_t replyRecieveTime = TimeHandler::currentTimeMicros(); 79 | updateDrift(requestSendTime, requestReceiptTime, replySendTime, 80 | replyRecieveTime); 81 | } 82 | string payload = reader.readPrimitive(); 83 | handleReply(uid, payload); 84 | } 85 | } break; 86 | case ACKNOWLEDGE: { 87 | RpcId uid = reader.readClass(); 88 | VLOG(1) << "ACK UID " << uid.str(); 89 | for (auto it = outgoingReplies.begin(); it != outgoingReplies.end(); 90 | it++) { 91 | VLOG(1) << "REPLY UID " << it->first.str(); 92 | if (it->first == uid) { 93 | if (requestRecieveTimeMap.find(it->first) == 94 | requestRecieveTimeMap.end()) { 95 | LOG(INFO) << requestRecieveTimeMap.size(); 96 | for (const auto& it2 : requestRecieveTimeMap) { 97 | LOG(INFO) << "XXXX: " << it2.first.str(); 98 | } 99 | LOGFATAL << "Tried to remove a request receive time that we " 100 | "didn't have: " 101 | << it->first.str(); 102 | } 103 | requestRecieveTimeMap.erase(it->first); 104 | outgoingReplies.erase(it); 105 | break; 106 | } 107 | } 108 | } break; 109 | default: { 110 | LOGFATAL << "Got invalid header: " << header << " in message " 111 | << message; 112 | } 113 | } 114 | } 115 | } 116 | 117 | void BiDirectionalRpc::handleRequest(const RpcId& rpcId, 118 | const string& payload) { 119 | VLOG(1) << "GOT REQUEST: " << rpcId.str(); 120 | 121 | bool skip = (incomingRequests.find(rpcId) != incomingRequests.end()); 122 | if (!skip) { 123 | for (const auto& it : outgoingReplies) { 124 | if (it.first == rpcId) { 125 | // We already processed this request. Send the reply again 126 | skip = true; 127 | sendReply(it.first, it.second); 128 | break; 129 | } 130 | } 131 | } 132 | if (!skip) { 133 | addIncomingRequest(IdPayload(rpcId, payload)); 134 | } 135 | } 136 | 137 | void BiDirectionalRpc::handleReply(const RpcId& rpcId, const string& payload) { 138 | bool skip = false; 139 | if (incomingReplies.find(rpcId) != incomingReplies.end()) { 140 | // We already received this reply. Send acknowledge again and skip. 141 | sendAcknowledge(rpcId); 142 | skip = true; 143 | } 144 | if (!skip) { 145 | // Stop sending the request once you get the reply 146 | bool deletedRequest = false; 147 | for (auto it = outgoingRequests.begin(); it != outgoingRequests.end(); 148 | it++) { 149 | if (it->first == rpcId) { 150 | outgoingRequests.erase(it); 151 | deletedRequest = true; 152 | tryToSendBarrier(); 153 | break; 154 | } 155 | } 156 | if (deletedRequest) { 157 | auto it = oneWayRequests.find(rpcId); 158 | if (it != oneWayRequests.end()) { 159 | // Remove this from the set of one way requests and don't bother 160 | // adding a reply. 161 | oneWayRequests.erase(it); 162 | } else { 163 | // Add a reply to be processed 164 | addIncomingReply(rpcId, payload); 165 | } 166 | sendAcknowledge(rpcId); 167 | } else { 168 | // We must have processed both this request and reply. Send the 169 | // acknowledge again. 170 | sendAcknowledge(rpcId); 171 | } 172 | } 173 | } 174 | 175 | RpcId BiDirectionalRpc::request(const string& payload) { 176 | auto fullUuid = sole::uuid4(); 177 | auto uuid = RpcId(onBarrier, fullUuid.cd); 178 | auto idPayload = IdPayload(uuid, payload); 179 | requestWithId(idPayload); 180 | return uuid; 181 | } 182 | 183 | void BiDirectionalRpc::requestNoReply(const string& payload) { 184 | lock_guard guard(mutex); 185 | auto fullUuid = sole::uuid4(); 186 | auto uuid = RpcId(onBarrier, fullUuid.cd); 187 | oneWayRequests.insert(uuid); 188 | auto idPayload = IdPayload(uuid, payload); 189 | requestWithId(idPayload); 190 | } 191 | 192 | void BiDirectionalRpc::requestWithId(const IdPayload& idPayload) { 193 | lock_guard guard(mutex); 194 | if (outgoingRequests.empty() || 195 | outgoingRequests.begin()->first.barrier == onBarrier) { 196 | // We can send the request immediately 197 | outgoingRequests[idPayload.id] = idPayload.payload; 198 | requestSendTimeMap[idPayload.id] = TimeHandler::currentTimeMicros(); 199 | sendRequest(idPayload.id, idPayload.payload); 200 | } else { 201 | // We have to wait for existing requests from an older barrier 202 | delayedRequests[idPayload.id] = idPayload.payload; 203 | } 204 | } 205 | 206 | void BiDirectionalRpc::reply(const RpcId& rpcId, const string& payload) { 207 | lock_guard guard(mutex); 208 | incomingRequests.erase(incomingRequests.find(rpcId)); 209 | outgoingReplies[rpcId] = payload; 210 | sendReply(rpcId, payload); 211 | } 212 | 213 | void BiDirectionalRpc::tryToSendBarrier() { 214 | if (delayedRequests.empty()) { 215 | // Nothing to send 216 | return; 217 | } 218 | if (outgoingRequests.empty()) { 219 | // There are no outgoing requests, we can send the next barrier 220 | int64_t lowestBarrier = delayedRequests.begin()->first.barrier; 221 | for (const auto& it : delayedRequests) { 222 | lowestBarrier = min(lowestBarrier, it.first.barrier); 223 | } 224 | 225 | for (auto it = delayedRequests.begin(); it != delayedRequests.end();) { 226 | if (it->first.barrier == lowestBarrier) { 227 | outgoingRequests[it->first] = it->second; 228 | requestSendTimeMap[it->first] = TimeHandler::currentTimeMicros(); 229 | sendRequest(it->first, it->second); 230 | it = delayedRequests.erase(it); 231 | } else { 232 | it++; 233 | } 234 | } 235 | } 236 | } 237 | 238 | void BiDirectionalRpc::sendRequest(const RpcId& id, const string& payload) { 239 | VLOG(1) << "SENDING REQUEST: " << id.str(); 240 | MessageWriter writer; 241 | writer.start(); 242 | set rpcsSent; 243 | 244 | rpcsSent.insert(id); 245 | writer.writePrimitive(REQUEST); 246 | writer.writeClass(id); 247 | writer.writePrimitive(payload); 248 | // Try to attach more requests to this packet 249 | int i = 0; 250 | while (!outgoingRequests.empty() && 251 | rpcsSent.size() < outgoingRequests.size()) { 252 | DRAW_FROM_UNORDERED(it, outgoingRequests); 253 | if (rpcsSent.find(it->first) != rpcsSent.end()) { 254 | // Drew an rpc that's already in the packet. Just bail for now, maybe in 255 | // the future do something more clever. 256 | break; 257 | } 258 | int size = sizeof(RpcId) + it->second.length(); 259 | if (size + writer.size() > 400) { 260 | // Too big 261 | break; 262 | } 263 | i++; 264 | rpcsSent.insert(it->first); 265 | writer.writeClass(it->first); 266 | writer.writePrimitive(it->second); 267 | } 268 | VLOG(1) << "Attached " << i << " extra packets"; 269 | send(writer.finish()); 270 | } 271 | 272 | void BiDirectionalRpc::sendReply(const RpcId& id, const string& payload) { 273 | lock_guard guard(mutex); 274 | VLOG(1) << "SENDING REPLY: " << id.str(); 275 | set rpcsSent; 276 | 277 | rpcsSent.insert(id); 278 | MessageWriter writer; 279 | writer.start(); 280 | writer.writePrimitive(REPLY); 281 | writer.writeClass(id); 282 | auto receiveTimeIt = requestRecieveTimeMap.find(id); 283 | if (receiveTimeIt == requestRecieveTimeMap.end()) { 284 | LOGFATAL << "Got a request with no receive time: " << id.str() << " " 285 | << requestRecieveTimeMap.size(); 286 | } 287 | writer.writePrimitive(receiveTimeIt->second); 288 | writer.writePrimitive(TimeHandler::currentTimeMicros()); 289 | writer.writePrimitive(payload); 290 | // Try to attach more replies to this packet 291 | int i = 0; 292 | while (!outgoingReplies.empty() && rpcsSent.size() < outgoingReplies.size()) { 293 | DRAW_FROM_UNORDERED(it, outgoingReplies); 294 | if (rpcsSent.find(it->first) != rpcsSent.end()) { 295 | // Drew an rpc that's already in the packet. Just bail for now, maybe in 296 | // the future do something more clever. 297 | break; 298 | } 299 | int size = sizeof(RpcId) + it->second.length(); 300 | if (size + writer.size() > 400) { 301 | // Too big 302 | break; 303 | } 304 | i++; 305 | rpcsSent.insert(it->first); 306 | writer.writeClass(it->first); 307 | receiveTimeIt = requestRecieveTimeMap.find(it->first); 308 | if (receiveTimeIt == requestRecieveTimeMap.end()) { 309 | LOGFATAL << "Got a request with no receive time"; 310 | } 311 | writer.writePrimitive(receiveTimeIt->second); 312 | writer.writePrimitive(TimeHandler::currentTimeMicros()); 313 | writer.writePrimitive(it->second); 314 | } 315 | VLOG(1) << "Attached " << i << " extra packets"; 316 | send(writer.finish()); 317 | } 318 | 319 | void BiDirectionalRpc::sendAcknowledge(const RpcId& uid) { 320 | MessageWriter writer; 321 | writer.start(); 322 | writer.writePrimitive(ACKNOWLEDGE); 323 | writer.writeClass(uid); 324 | send(writer.finish()); 325 | } 326 | 327 | void BiDirectionalRpc::addIncomingRequest(const IdPayload& idPayload) { 328 | lock_guard guard(mutex); 329 | if (requestRecieveTimeMap.find(idPayload.id) != requestRecieveTimeMap.end()) { 330 | LOGFATAL << "Already created receive time for id: " << idPayload.id.str(); 331 | } 332 | requestRecieveTimeMap[idPayload.id] = TimeHandler::currentTimeMicros(); 333 | incomingRequests.insert(make_pair(idPayload.id, idPayload.payload)); 334 | } 335 | 336 | void BiDirectionalRpc::updateDrift(int64_t requestSendTime, 337 | int64_t requestReceiptTime, 338 | int64_t replySendTime, 339 | int64_t replyRecieveTime) { 340 | int64_t timeOffset = ((requestReceiptTime - requestSendTime) + 341 | (replySendTime - replyRecieveTime)) / 342 | 2; 343 | int64_t ping = (replyRecieveTime - requestSendTime) - 344 | (replySendTime - requestReceiptTime); 345 | networkStatsQueue.push_back({timeOffset, ping}); 346 | VLOG(2) << "Time Sync Info: " << timeOffset << " " << ping << " " 347 | << (replyRecieveTime - requestSendTime) << " " 348 | << (replySendTime - requestReceiptTime); 349 | if (networkStatsQueue.size() >= 100) { 350 | LOG(INFO) << "Time Sync Info: " << timeOffset << " " << ping << " " 351 | << (replyRecieveTime - requestSendTime) << " " 352 | << (replySendTime - requestReceiptTime); 353 | int64_t sumShift = 0; 354 | int64_t shiftCount = 0; 355 | for (int i = 0; i < networkStatsQueue.size(); i++) { 356 | sumShift += networkStatsQueue.at(i).offset; 357 | shiftCount++; 358 | } 359 | if (shiftCount) { 360 | VLOG(2) << "New shift: " << (sumShift / shiftCount); 361 | auto shift = 362 | std::chrono::microseconds{sumShift / shiftCount / int64_t(5)}; 363 | VLOG(2) << "TIME CHANGE: " << TimeHandler::currentTimeMicros(); 364 | TimeHandler::initialTime -= shift; 365 | VLOG(2) << "TIME CHANGE: " << TimeHandler::currentTimeMicros(); 366 | } 367 | } 368 | // auto shift = std::chrono::microseconds{ 369 | // int64_t(timeOffsetController.calculate(0, double(timeOffset)))}; 370 | // TimeHandler::initialTime += shift; 371 | networkStatsQueue.clear(); 372 | } 373 | 374 | } // namespace codefs 375 | -------------------------------------------------------------------------------- /src/base/zmq_addon.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016-2017 ZeroMQ community 3 | Copyright (c) 2016 VOCA AS / Harald Nøkland 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to 7 | deal in the Software without restriction, including without limitation the 8 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | sell copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | IN THE SOFTWARE. 22 | */ 23 | 24 | #ifndef __ZMQ_ADDON_HPP_INCLUDED__ 25 | #define __ZMQ_ADDON_HPP_INCLUDED__ 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | namespace zmq 35 | { 36 | #ifdef ZMQ_HAS_RVALUE_REFS 37 | 38 | /* 39 | This class handles multipart messaging. It is the C++ equivalent of zmsg.h, 40 | which is part of CZMQ (the high-level C binding). Furthermore, it is a major 41 | improvement compared to zmsg.hpp, which is part of the examples in the ØMQ 42 | Guide. Unnecessary copying is avoided by using move semantics to efficiently 43 | add/remove parts. 44 | */ 45 | class multipart_t 46 | { 47 | private: 48 | std::deque m_parts; 49 | 50 | public: 51 | typedef std::deque::iterator iterator; 52 | typedef std::deque::const_iterator const_iterator; 53 | 54 | typedef std::deque::reverse_iterator reverse_iterator; 55 | typedef std::deque::const_reverse_iterator const_reverse_iterator; 56 | 57 | // Default constructor 58 | multipart_t() {} 59 | 60 | // Construct from socket receive 61 | multipart_t(socket_t &socket) { recv(socket); } 62 | 63 | // Construct from memory block 64 | multipart_t(const void *src, size_t size) { addmem(src, size); } 65 | 66 | // Construct from string 67 | multipart_t(const std::string &string) { addstr(string); } 68 | 69 | // Construct from message part 70 | multipart_t(message_t &&message) { add(std::move(message)); } 71 | 72 | // Move constructor 73 | multipart_t(multipart_t &&other) { m_parts = std::move(other.m_parts); } 74 | 75 | // Move assignment operator 76 | multipart_t &operator=(multipart_t &&other) 77 | { 78 | m_parts = std::move(other.m_parts); 79 | return *this; 80 | } 81 | 82 | // Destructor 83 | virtual ~multipart_t() { clear(); } 84 | 85 | message_t &operator[](size_t n) { return m_parts[n]; } 86 | 87 | const message_t &operator[](size_t n) const { return m_parts[n]; } 88 | 89 | message_t &at(size_t n) { return m_parts.at(n); } 90 | 91 | const message_t &at(size_t n) const { return m_parts.at(n); } 92 | 93 | iterator begin() { return m_parts.begin(); } 94 | 95 | const_iterator begin() const { return m_parts.begin(); } 96 | 97 | const_iterator cbegin() const { return m_parts.cbegin(); } 98 | 99 | reverse_iterator rbegin() { return m_parts.rbegin(); } 100 | 101 | const_reverse_iterator rbegin() const { return m_parts.rbegin(); } 102 | 103 | iterator end() { return m_parts.end(); } 104 | 105 | const_iterator end() const { return m_parts.end(); } 106 | 107 | const_iterator cend() const { return m_parts.cend(); } 108 | 109 | reverse_iterator rend() { return m_parts.rend(); } 110 | 111 | const_reverse_iterator rend() const { return m_parts.rend(); } 112 | 113 | // Delete all parts 114 | void clear() { m_parts.clear(); } 115 | 116 | // Get number of parts 117 | size_t size() const { return m_parts.size(); } 118 | 119 | // Check if number of parts is zero 120 | bool empty() const { return m_parts.empty(); } 121 | 122 | // Receive multipart message from socket 123 | bool recv(socket_t &socket, int flags = 0) 124 | { 125 | clear(); 126 | bool more = true; 127 | while (more) { 128 | message_t message; 129 | if (!socket.recv(&message, flags)) 130 | return false; 131 | more = message.more(); 132 | add(std::move(message)); 133 | } 134 | return true; 135 | } 136 | 137 | // Send multipart message to socket 138 | bool send(socket_t &socket, int flags = 0) 139 | { 140 | flags &= ~(ZMQ_SNDMORE); 141 | bool more = size() > 0; 142 | while (more) { 143 | message_t message = pop(); 144 | more = size() > 0; 145 | if (!socket.send(message, (more ? ZMQ_SNDMORE : 0) | flags)) 146 | return false; 147 | } 148 | clear(); 149 | return true; 150 | } 151 | 152 | // Concatenate other multipart to front 153 | void prepend(multipart_t &&other) 154 | { 155 | while (!other.empty()) 156 | push(other.remove()); 157 | } 158 | 159 | // Concatenate other multipart to back 160 | void append(multipart_t &&other) 161 | { 162 | while (!other.empty()) 163 | add(other.pop()); 164 | } 165 | 166 | // Push memory block to front 167 | void pushmem(const void *src, size_t size) 168 | { 169 | m_parts.push_front(message_t(src, size)); 170 | } 171 | 172 | // Push memory block to back 173 | void addmem(const void *src, size_t size) 174 | { 175 | m_parts.push_back(message_t(src, size)); 176 | } 177 | 178 | // Push string to front 179 | void pushstr(const std::string &string) 180 | { 181 | m_parts.push_front(message_t(string.data(), string.size())); 182 | } 183 | 184 | // Push string to back 185 | void addstr(const std::string &string) 186 | { 187 | m_parts.push_back(message_t(string.data(), string.size())); 188 | } 189 | 190 | // Push type (fixed-size) to front 191 | template void pushtyp(const T &type) 192 | { 193 | static_assert(!std::is_same::value, 194 | "Use pushstr() instead of pushtyp()"); 195 | m_parts.push_front(message_t(&type, sizeof(type))); 196 | } 197 | 198 | // Push type (fixed-size) to back 199 | template void addtyp(const T &type) 200 | { 201 | static_assert(!std::is_same::value, 202 | "Use addstr() instead of addtyp()"); 203 | m_parts.push_back(message_t(&type, sizeof(type))); 204 | } 205 | 206 | // Push message part to front 207 | void push(message_t &&message) { m_parts.push_front(std::move(message)); } 208 | 209 | // Push message part to back 210 | void add(message_t &&message) { m_parts.push_back(std::move(message)); } 211 | 212 | // Pop string from front 213 | std::string popstr() 214 | { 215 | std::string string(m_parts.front().data(), m_parts.front().size()); 216 | m_parts.pop_front(); 217 | return string; 218 | } 219 | 220 | // Pop type (fixed-size) from front 221 | template T poptyp() 222 | { 223 | static_assert(!std::is_same::value, 224 | "Use popstr() instead of poptyp()"); 225 | if (sizeof(T) != m_parts.front().size()) 226 | throw std::runtime_error( 227 | "Invalid type, size does not match the message size"); 228 | T type = *m_parts.front().data(); 229 | m_parts.pop_front(); 230 | return type; 231 | } 232 | 233 | // Pop message part from front 234 | message_t pop() 235 | { 236 | message_t message = std::move(m_parts.front()); 237 | m_parts.pop_front(); 238 | return message; 239 | } 240 | 241 | // Pop message part from back 242 | message_t remove() 243 | { 244 | message_t message = std::move(m_parts.back()); 245 | m_parts.pop_back(); 246 | return message; 247 | } 248 | 249 | // Get pointer to a specific message part 250 | const message_t *peek(size_t index) const { return &m_parts[index]; } 251 | 252 | // Get a string copy of a specific message part 253 | std::string peekstr(size_t index) const 254 | { 255 | std::string string(m_parts[index].data(), m_parts[index].size()); 256 | return string; 257 | } 258 | 259 | // Peek type (fixed-size) from front 260 | template T peektyp(size_t index) const 261 | { 262 | static_assert(!std::is_same::value, 263 | "Use peekstr() instead of peektyp()"); 264 | if (sizeof(T) != m_parts[index].size()) 265 | throw std::runtime_error( 266 | "Invalid type, size does not match the message size"); 267 | T type = *m_parts[index].data(); 268 | return type; 269 | } 270 | 271 | // Create multipart from type (fixed-size) 272 | template static multipart_t create(const T &type) 273 | { 274 | multipart_t multipart; 275 | multipart.addtyp(type); 276 | return multipart; 277 | } 278 | 279 | // Copy multipart 280 | multipart_t clone() const 281 | { 282 | multipart_t multipart; 283 | for (size_t i = 0; i < size(); i++) 284 | multipart.addmem(m_parts[i].data(), m_parts[i].size()); 285 | return multipart; 286 | } 287 | 288 | // Dump content to string 289 | std::string str() const 290 | { 291 | std::stringstream ss; 292 | for (size_t i = 0; i < m_parts.size(); i++) { 293 | const unsigned char *data = m_parts[i].data(); 294 | size_t size = m_parts[i].size(); 295 | 296 | // Dump the message as text or binary 297 | bool isText = true; 298 | for (size_t j = 0; j < size; j++) { 299 | if (data[j] < 32 || data[j] > 127) { 300 | isText = false; 301 | break; 302 | } 303 | } 304 | ss << "\n[" << std::dec << std::setw(3) << std::setfill('0') << size 305 | << "] "; 306 | if (size >= 1000) { 307 | ss << "... (to big to print)"; 308 | continue; 309 | } 310 | for (size_t j = 0; j < size; j++) { 311 | if (isText) 312 | ss << static_cast(data[j]); 313 | else 314 | ss << std::hex << std::setw(2) << std::setfill('0') 315 | << static_cast(data[j]); 316 | } 317 | } 318 | return ss.str(); 319 | } 320 | 321 | // Check if equal to other multipart 322 | bool equal(const multipart_t *other) const 323 | { 324 | if (size() != other->size()) 325 | return false; 326 | for (size_t i = 0; i < size(); i++) 327 | if (*peek(i) != *other->peek(i)) 328 | return false; 329 | return true; 330 | } 331 | 332 | private: 333 | // Disable implicit copying (moving is more efficient) 334 | multipart_t(const multipart_t &other) ZMQ_DELETED_FUNCTION; 335 | void operator=(const multipart_t &other) ZMQ_DELETED_FUNCTION; 336 | }; // class multipart_t 337 | 338 | inline std::ostream &operator<<(std::ostream &os, const multipart_t &msg) 339 | { 340 | return os << msg.str(); 341 | } 342 | 343 | #endif // ZMQ_HAS_RVALUE_REFS 344 | 345 | #if defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER) 346 | class active_poller_t 347 | { 348 | public: 349 | active_poller_t() = default; 350 | ~active_poller_t() = default; 351 | 352 | active_poller_t(const active_poller_t &) = delete; 353 | active_poller_t &operator=(const active_poller_t &) = delete; 354 | 355 | active_poller_t(active_poller_t &&src) = default; 356 | active_poller_t &operator=(active_poller_t &&src) = default; 357 | 358 | using handler_t = std::function; 359 | 360 | void add(zmq::socket_t &socket, short events, handler_t handler) 361 | { 362 | auto it = decltype(handlers)::iterator{}; 363 | auto inserted = bool{}; 364 | std::tie(it, inserted) = 365 | handlers.emplace(static_cast(socket), 366 | std::make_shared(std::move(handler))); 367 | try { 368 | base_poller.add(socket, events, 369 | inserted && *(it->second) ? it->second.get() : nullptr); 370 | need_rebuild |= inserted; 371 | } 372 | catch (const zmq::error_t &) { 373 | // rollback 374 | if (inserted) { 375 | handlers.erase(static_cast(socket)); 376 | } 377 | throw; 378 | } 379 | } 380 | 381 | void remove(zmq::socket_t &socket) 382 | { 383 | base_poller.remove(socket); 384 | handlers.erase(static_cast(socket)); 385 | need_rebuild = true; 386 | } 387 | 388 | void modify(zmq::socket_t &socket, short events) 389 | { 390 | base_poller.modify(socket, events); 391 | } 392 | 393 | size_t wait(std::chrono::milliseconds timeout) 394 | { 395 | if (need_rebuild) { 396 | poller_events.resize(handlers.size()); 397 | poller_handlers.clear(); 398 | poller_handlers.reserve(handlers.size()); 399 | for (const auto &handler : handlers) { 400 | poller_handlers.push_back(handler.second); 401 | } 402 | need_rebuild = false; 403 | } 404 | const auto count = base_poller.wait_all(poller_events, timeout); 405 | std::for_each(poller_events.begin(), poller_events.begin() + count, 406 | [](zmq_poller_event_t &event) { 407 | if (event.user_data != NULL) 408 | (*reinterpret_cast(event.user_data))( 409 | event.events); 410 | }); 411 | return count; 412 | } 413 | 414 | bool empty() const { return handlers.empty(); } 415 | 416 | size_t size() const { return handlers.size(); } 417 | 418 | private: 419 | bool need_rebuild{false}; 420 | 421 | poller_t base_poller{}; 422 | std::unordered_map> handlers{}; 423 | std::vector poller_events{}; 424 | std::vector> poller_handlers{}; 425 | }; // class active_poller_t 426 | #endif // defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER) 427 | 428 | 429 | } // namespace zmq 430 | 431 | #endif // __ZMQ_ADDON_HPP_INCLUDED__ -------------------------------------------------------------------------------- /src/server/Server.cpp: -------------------------------------------------------------------------------- 1 | #include "Server.hpp" 2 | 3 | #include "ZmqBiDirectionalRpc.hpp" 4 | 5 | namespace codefs { 6 | Server::Server(const string &_address, shared_ptr _fileSystem) 7 | : address(_address), fileSystem(_fileSystem), clientFd(-1) {} 8 | 9 | void Server::init() { 10 | lock_guard lock(rpcMutex); 11 | rpc = shared_ptr(new ZmqBiDirectionalRpc(address, true)); 12 | } 13 | 14 | int Server::update() { 15 | { 16 | lock_guard lock(rpcMutex); 17 | rpc->update(); 18 | } 19 | 20 | MessageWriter writer; 21 | MessageReader reader; 22 | while (true) { 23 | RpcId id; 24 | string payload; 25 | { 26 | lock_guard lock(rpcMutex); 27 | if (!rpc->hasIncomingRequest()) { 28 | break; 29 | } 30 | auto idPayload = rpc->getFirstIncomingRequest(); 31 | id = idPayload.id; 32 | payload = idPayload.payload; 33 | } 34 | reader.load(payload); 35 | unsigned char header = reader.readPrimitive(); 36 | VLOG(1) << "CONSUMING REQUEST: " << id.str() << ": " << int(header) << " " 37 | << payload.size(); 38 | switch (header) { 39 | case CLIENT_SERVER_CREATE_FILE: { 40 | string path = reader.readPrimitive(); 41 | int flags = reader.readPrimitive(); 42 | int mode = reader.readPrimitive(); 43 | int readWriteMode = (flags & O_ACCMODE); 44 | LOG(INFO) << "REQUESTING FILE: " << path << " FLAGS: " << flags << " " 45 | << readWriteMode << " " << mode; 46 | optional fileData = fileSystem->getNode(path); 47 | 48 | writer.start(); 49 | 50 | bool access = true; 51 | if (readWriteMode == O_RDONLY) { 52 | if (!fileData) { 53 | writer.writePrimitive(ENOENT); 54 | access = false; 55 | } else if (!fileData->can_read()) { 56 | writer.writePrimitive(EACCES); 57 | access = false; 58 | } 59 | } else { 60 | if (!fileData) { 61 | LOG(INFO) << "FILE DOES NOT EXIST YET"; 62 | 63 | // Get the parent path and make sure we can write there 64 | string parentPath = 65 | boost::filesystem::path(path).parent_path().string(); 66 | LOG(INFO) << "PARENT PATH: " << parentPath; 67 | if (parentPath != string("/")) { 68 | optional parentFileData = 69 | fileSystem->getNode(parentPath); 70 | if (!parentFileData || !parentFileData->can_execute()) { 71 | writer.writePrimitive(EACCES); 72 | access = false; 73 | } 74 | } 75 | 76 | if (access) { 77 | LOG(INFO) << "Creating empty file"; 78 | fileSystem->writeFile(path, ""); 79 | fileSystem->chmod(path, mode_t(mode)); 80 | } 81 | } else if (!fileData->can_write()) { 82 | writer.writePrimitive(EACCES); 83 | access = false; 84 | } 85 | } 86 | 87 | if (access) { 88 | writer.writePrimitive(0); 89 | } 90 | reply(id, writer.finish()); 91 | fileSystem->rescanPathAndParent(fileSystem->relativeToAbsolute(path)); 92 | 93 | } break; 94 | case CLIENT_SERVER_REQUEST_FILE: { 95 | string path = reader.readPrimitive(); 96 | int flags = reader.readPrimitive(); 97 | int readWriteMode = (flags & O_ACCMODE); 98 | LOG(INFO) << "REQUESTING FILE: " << path << " FLAGS: " << flags << " " 99 | << readWriteMode; 100 | optional fileData = fileSystem->getNode(path); 101 | 102 | writer.start(); 103 | 104 | bool access = true; 105 | if (readWriteMode == O_RDONLY) { 106 | if (!fileData) { 107 | writer.writePrimitive(ENOENT); 108 | writer.writePrimitive(""); 109 | access = false; 110 | } else if (!fileData->can_read()) { 111 | writer.writePrimitive(EACCES); 112 | writer.writePrimitive(""); 113 | access = false; 114 | } 115 | } else { 116 | if (!fileData) { 117 | LOG(INFO) << "FILE DOES NOT EXIST YET"; 118 | writer.writePrimitive(ENOENT); 119 | writer.writePrimitive(""); 120 | access = false; 121 | } else if (!fileData->can_write()) { 122 | writer.writePrimitive(EACCES); 123 | writer.writePrimitive(""); 124 | access = false; 125 | } 126 | } 127 | 128 | if (access) { 129 | bool skipLoadingFile = false; // O_TRUNC never occurs in FUSE 130 | string fileContents = ""; 131 | if (!skipLoadingFile) { 132 | fileContents = fileSystem->readFile(path); 133 | LOG(INFO) << "READ FILE: " << path << " " << fileContents.size(); 134 | } 135 | 136 | writer.writePrimitive(0); 137 | writer.writePrimitive(compressString(fileContents)); 138 | } 139 | reply(id, writer.finish()); 140 | if (readWriteMode != O_RDONLY) { 141 | fileSystem->rescanPathAndParent(fileSystem->relativeToAbsolute(path)); 142 | } 143 | } break; 144 | case CLIENT_SERVER_RETURN_FILE: { 145 | string path = reader.readPrimitive(); 146 | bool readOnly = reader.readPrimitive(); 147 | int res = 0; 148 | if (readOnly) { 149 | LOG(INFO) << "RETURNED READ-ONLY FILE"; 150 | } else { 151 | string fileContents = 152 | decompressString(reader.readPrimitive()); 153 | LOG(INFO) << "WRITING FILE " << path << " " << fileContents.size(); 154 | 155 | res = fileSystem->writeFile(path, fileContents); 156 | } 157 | 158 | writer.start(); 159 | writer.writePrimitive(res); 160 | if (res) { 161 | writer.writePrimitive(errno); 162 | } else { 163 | writer.writePrimitive(0); 164 | } 165 | reply(id, writer.finish()); 166 | if (!readOnly) { 167 | fileSystem->rescanPathAndParent(fileSystem->relativeToAbsolute(path)); 168 | } 169 | } break; 170 | case CLIENT_SERVER_FETCH_METADATA: { 171 | int numPaths = reader.readPrimitive(); 172 | writer.start(); 173 | for (int a = 0; a < numPaths; a++) { 174 | string path = reader.readPrimitive(); 175 | VLOG(1) << "Fetching Metadata for " << path; 176 | auto s = fileSystem->serializeFileDataCompressed(path); 177 | writer.writePrimitive(path); 178 | writer.writePrimitive(s); 179 | } 180 | reply(id, writer.finish()); 181 | } break; 182 | case CLIENT_SERVER_MKDIR: { 183 | string path = reader.readPrimitive(); 184 | mode_t mode = reader.readPrimitive(); 185 | int res = fileSystem->mkdir(path, mode); 186 | writer.writePrimitive(res); 187 | if (res) { 188 | writer.writePrimitive(errno); 189 | } else { 190 | writer.writePrimitive(0); 191 | } 192 | reply(id, writer.finish()); 193 | fileSystem->rescanPathAndParent(fileSystem->relativeToAbsolute(path)); 194 | } break; 195 | case CLIENT_SERVER_UNLINK: { 196 | string path = reader.readPrimitive(); 197 | LOG(INFO) << "UNLINKING: " << path << " " 198 | << fileSystem->relativeToAbsolute(path); 199 | int res = fileSystem->unlink(path); 200 | writer.writePrimitive(res); 201 | if (res) { 202 | writer.writePrimitive(errno); 203 | } else { 204 | writer.writePrimitive(0); 205 | } 206 | reply(id, writer.finish()); 207 | fileSystem->rescanPathAndParent(fileSystem->relativeToAbsolute(path)); 208 | } break; 209 | case CLIENT_SERVER_RMDIR: { 210 | string path = reader.readPrimitive(); 211 | int res = fileSystem->rmdir(path); 212 | writer.writePrimitive(res); 213 | if (res) { 214 | writer.writePrimitive(errno); 215 | } else { 216 | writer.writePrimitive(0); 217 | } 218 | reply(id, writer.finish()); 219 | fileSystem->rescanPathAndParent(fileSystem->relativeToAbsolute(path)); 220 | } break; 221 | case CLIENT_SERVER_SYMLINK: { 222 | string from = reader.readPrimitive(); 223 | string to = reader.readPrimitive(); 224 | int res = fileSystem->symlink(from, to); 225 | writer.writePrimitive(res); 226 | if (res) { 227 | writer.writePrimitive(errno); 228 | } else { 229 | writer.writePrimitive(0); 230 | } 231 | reply(id, writer.finish()); 232 | fileSystem->rescanPathAndParent(fileSystem->relativeToAbsolute(from)); 233 | fileSystem->rescanPathAndParent(fileSystem->relativeToAbsolute(to)); 234 | } break; 235 | case CLIENT_SERVER_RENAME: { 236 | string from = reader.readPrimitive(); 237 | string to = reader.readPrimitive(); 238 | int res = fileSystem->rename(from, to); 239 | writer.writePrimitive(res); 240 | if (res) { 241 | writer.writePrimitive(errno); 242 | } else { 243 | writer.writePrimitive(0); 244 | } 245 | reply(id, writer.finish()); 246 | fileSystem->rescanPathAndParentAndChildren( 247 | fileSystem->relativeToAbsolute(from)); 248 | fileSystem->rescanPathAndParentAndChildren( 249 | fileSystem->relativeToAbsolute(to)); 250 | } break; 251 | case CLIENT_SERVER_LINK: { 252 | string from = reader.readPrimitive(); 253 | if (from[0] == '/') { 254 | from = fileSystem->relativeToAbsolute(from); 255 | } 256 | string to = reader.readPrimitive(); 257 | int res = fileSystem->link(from, to); 258 | writer.writePrimitive(res); 259 | if (res) { 260 | writer.writePrimitive(errno); 261 | } else { 262 | writer.writePrimitive(0); 263 | } 264 | reply(id, writer.finish()); 265 | fileSystem->rescanPathAndParent(fileSystem->relativeToAbsolute(from)); 266 | fileSystem->rescanPathAndParent(fileSystem->relativeToAbsolute(to)); 267 | } break; 268 | case CLIENT_SERVER_CHMOD: { 269 | string path = reader.readPrimitive(); 270 | int mode = reader.readPrimitive(); 271 | int res = fileSystem->chmod(path, mode); 272 | writer.writePrimitive(res); 273 | if (res) { 274 | writer.writePrimitive(errno); 275 | } else { 276 | writer.writePrimitive(0); 277 | } 278 | reply(id, writer.finish()); 279 | fileSystem->rescanPath(fileSystem->relativeToAbsolute(path)); 280 | } break; 281 | case CLIENT_SERVER_LCHOWN: { 282 | string path = reader.readPrimitive(); 283 | int64_t uid = reader.readPrimitive(); 284 | int64_t gid = reader.readPrimitive(); 285 | int res = fileSystem->lchown(path, uid, gid); 286 | writer.writePrimitive(res); 287 | if (res) { 288 | writer.writePrimitive(errno); 289 | } else { 290 | writer.writePrimitive(0); 291 | } 292 | reply(id, writer.finish()); 293 | fileSystem->rescanPath(fileSystem->relativeToAbsolute(path)); 294 | } break; 295 | case CLIENT_SERVER_TRUNCATE: { 296 | string path = reader.readPrimitive(); 297 | int64_t size = reader.readPrimitive(); 298 | int res = fileSystem->truncate(path, size); 299 | writer.writePrimitive(res); 300 | if (res) { 301 | writer.writePrimitive(errno); 302 | } else { 303 | writer.writePrimitive(0); 304 | } 305 | reply(id, writer.finish()); 306 | fileSystem->rescanPath(fileSystem->relativeToAbsolute(path)); 307 | } break; 308 | case CLIENT_SERVER_STATVFS: { 309 | struct statvfs stbuf; 310 | int res = 311 | ::statvfs(fileSystem->relativeToAbsolute("/").c_str(), &stbuf); 312 | StatVfsData statVfsProto; 313 | writer.writePrimitive(res); 314 | if (res) { 315 | writer.writePrimitive(errno); 316 | } else { 317 | writer.writePrimitive(0); 318 | 319 | statVfsProto.set_bsize(stbuf.f_bsize); 320 | statVfsProto.set_frsize(stbuf.f_frsize); 321 | statVfsProto.set_blocks(stbuf.f_blocks); 322 | statVfsProto.set_bfree(stbuf.f_bfree); 323 | statVfsProto.set_bavail(stbuf.f_bavail); 324 | statVfsProto.set_files(stbuf.f_files); 325 | statVfsProto.set_ffree(stbuf.f_ffree); 326 | statVfsProto.set_favail(stbuf.f_favail); 327 | statVfsProto.set_fsid(stbuf.f_fsid); 328 | statVfsProto.set_flag(stbuf.f_flag); 329 | statVfsProto.set_namemax(stbuf.f_namemax); 330 | } 331 | writer.writeProto(statVfsProto); 332 | reply(id, writer.finish()); 333 | } break; 334 | case CLIENT_SERVER_UTIMENSAT: { 335 | string path = reader.readPrimitive(); 336 | struct timespec ts[2]; 337 | ts[0].tv_sec = reader.readPrimitive(); 338 | ts[0].tv_nsec = reader.readPrimitive(); 339 | ts[1].tv_sec = reader.readPrimitive(); 340 | ts[1].tv_nsec = reader.readPrimitive(); 341 | int res = fileSystem->utimensat(path, ts); 342 | writer.writePrimitive(res); 343 | if (res) { 344 | writer.writePrimitive(errno); 345 | } else { 346 | writer.writePrimitive(0); 347 | } 348 | reply(id, writer.finish()); 349 | fileSystem->rescanPath(fileSystem->relativeToAbsolute(path)); 350 | } break; 351 | case CLIENT_SERVER_LREMOVEXATTR: { 352 | string path = reader.readPrimitive(); 353 | string name = reader.readPrimitive(); 354 | int res = fileSystem->lremovexattr(path, name); 355 | if (res) { 356 | writer.writePrimitive(errno); 357 | } else { 358 | writer.writePrimitive(0); 359 | } 360 | reply(id, writer.finish()); 361 | fileSystem->rescanPath(fileSystem->relativeToAbsolute(path)); 362 | } break; 363 | case CLIENT_SERVER_LSETXATTR: { 364 | string path = reader.readPrimitive(); 365 | string name = reader.readPrimitive(); 366 | string value = reader.readPrimitive(); 367 | int64_t size = reader.readPrimitive(); 368 | int flags = reader.readPrimitive(); 369 | int res = fileSystem->lsetxattr(path, name, value, size, flags); 370 | if (res) { 371 | writer.writePrimitive(errno); 372 | } else { 373 | writer.writePrimitive(0); 374 | } 375 | reply(id, writer.finish()); 376 | fileSystem->rescanPath(fileSystem->relativeToAbsolute(path)); 377 | } break; 378 | default: 379 | LOGFATAL << "Invalid packet header: " << int(header); 380 | } 381 | } 382 | 383 | while (true) { 384 | RpcId id; 385 | string payload; 386 | { 387 | lock_guard lock(rpcMutex); 388 | if (!rpc->hasIncomingReply()) { 389 | break; 390 | } 391 | auto idPayload = rpc->getFirstIncomingReply(); 392 | id = idPayload.id; 393 | payload = idPayload.payload; 394 | } 395 | reader.load(payload); 396 | unsigned char header = reader.readPrimitive(); 397 | 398 | switch (header) { 399 | case SERVER_CLIENT_METADATA_UPDATE: { 400 | } break; 401 | 402 | default: 403 | LOGFATAL << "Invalid packet header: " << int(header); 404 | } 405 | } 406 | 407 | return 0; 408 | } 409 | 410 | void Server::metadataUpdated(const string &path, const FileData &fileData) { 411 | MessageWriter writer; 412 | writer.start(); 413 | writer.writePrimitive(SERVER_CLIENT_METADATA_UPDATE); 414 | writer.writePrimitive(path); 415 | writer.writeProto(fileData); 416 | request(writer.finish()); 417 | } 418 | 419 | } // namespace codefs -------------------------------------------------------------------------------- /src/client/ClientFuseAdapter.cpp: -------------------------------------------------------------------------------- 1 | #include "Headers.hpp" 2 | 3 | #include "Client.hpp" 4 | #include "ClientFuseAdapter.hpp" 5 | 6 | namespace codefs { 7 | shared_ptr client; 8 | shared_ptr fileSystem; 9 | 10 | class DirectoryPointer { 11 | public: 12 | string directory; 13 | int offset; 14 | 15 | DirectoryPointer(const string &_directory) 16 | : directory(_directory), offset(0) {} 17 | }; 18 | 19 | class FdInfo { 20 | public: 21 | string path; 22 | 23 | FdInfo(const string &_path) : path(_path) {} 24 | }; 25 | 26 | recursive_mutex fdMapMutex; 27 | unordered_map fdMap; 28 | unordered_map dirpMap; 29 | 30 | static void *codefs_init(struct fuse_conn_info *conn) { return NULL; } 31 | 32 | static int codefs_access(const char *path, int mask) { 33 | VLOG(1) << "CHECKING ACCESS FOR " << path << " " << mask; 34 | optional fileData = client->getNode(path); 35 | if (!fileData) { 36 | LOG(INFO) << "FILE DOESN'T EXIST"; 37 | return -1 * ENOENT; 38 | } 39 | if (mask == 0) { 40 | return 0; 41 | } 42 | 43 | if (mask & R_OK) { 44 | if (fileData->can_read()) { 45 | } else { 46 | return -1 * EACCES; 47 | } 48 | } 49 | if (mask & W_OK) { 50 | if (fileData->can_write()) { 51 | } else { 52 | return -1 * EACCES; 53 | } 54 | } 55 | if (mask & X_OK) { 56 | if (fileData->can_execute()) { 57 | } else { 58 | return -1 * EACCES; 59 | } 60 | } 61 | 62 | return 0; 63 | } 64 | 65 | static int codefs_readlink(const char *path, char *buf, size_t size) { 66 | optional fileData = client->getNode(path); 67 | if (!fileData) { 68 | return -ENOENT; 69 | } 70 | 71 | string absoluteTo = fileData->symlink_contents(); 72 | if (absoluteTo[0] == '/') { 73 | absoluteTo = fileSystem->relativeToAbsolute(absoluteTo); 74 | } 75 | auto contentsSize = absoluteTo.length(); 76 | if (contentsSize == 0) { 77 | return -EINVAL; 78 | } 79 | if (contentsSize >= size) { 80 | memcpy(buf, absoluteTo.c_str(), size); 81 | } else { 82 | memcpy(buf, absoluteTo.c_str(), contentsSize); 83 | } 84 | // NOTE: This is different than POSIX readlink which returns the size 85 | return 0; 86 | } 87 | 88 | static int codefs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, 89 | off_t offset, struct fuse_file_info *fi) { 90 | VLOG(1) << "READING DIRECTORY AT PATH: " << path; 91 | if (client->hasDirectory(path) == false) { 92 | return -ENOENT; 93 | } 94 | optional node; 95 | vector children; 96 | node = client->getNodeAndChildren(path, &children); 97 | for (const auto &child : children) { 98 | string fileName = boost::filesystem::path(child.path()).filename().string(); 99 | struct stat st; 100 | memset(&st, 0, sizeof(struct stat)); 101 | FileSystem::protoToStat(child.stat_data(), &st); 102 | if (filler(buf, fileName.c_str(), &st, 0)) { 103 | LOGFATAL << "Filler returned non-zero value"; 104 | break; 105 | } 106 | } 107 | return 0; 108 | } 109 | 110 | static int codefs_listxattr(const char *path, char *list, size_t size) { 111 | optional fileData = client->getNode(path); 112 | if (!fileData) { 113 | return -1 * ENOENT; 114 | } 115 | string s; 116 | for (int a = 0; a < fileData->xattr_key().size(); a++) { 117 | s.append(fileData->xattr_key(a)); 118 | s.append("\0"); 119 | } 120 | if (s.length() > size) { 121 | return -ERANGE; 122 | } 123 | memcpy(list, s.c_str(), s.length()); 124 | return s.length(); 125 | } 126 | 127 | static int codefs_getxattr(const char *path, const char *name, char *value, 128 | size_t size) { 129 | optional fileData = client->getNode(path); 130 | if (!fileData) { 131 | return -1 * ENOENT; 132 | } 133 | for (int a = 0; a < fileData->xattr_key().size(); a++) { 134 | if (fileData->xattr_key(a) == name) { 135 | if (fileData->xattr_value(a).length() < size) { 136 | return -ERANGE; 137 | } 138 | memcpy(value, &(fileData->xattr_value(a)[0]), 139 | fileData->xattr_value(a).length()); 140 | return fileData->xattr_value(a).length(); 141 | } 142 | } 143 | #ifndef ENOATTR 144 | #define ENOATTR (0) 145 | #endif 146 | return -ENOATTR; 147 | } 148 | 149 | static int codefs_fsync(const char *, int, struct fuse_file_info *) { 150 | return 0; 151 | } 152 | 153 | static int codefs_fsyncdir(const char *, int, struct fuse_file_info *) { 154 | return 0; 155 | } 156 | 157 | map readLocks; 158 | map writeLocks; 159 | static int codefs_lock(const char *path, struct fuse_file_info *fi, int cmd, 160 | struct flock *lockp) { 161 | LOG(INFO) << "LOCK CALLED FOR PATH " << string(path) << " and fh " << fi->fh; 162 | struct flock lockCopy = *lockp; 163 | string pathString = string(path); 164 | switch (cmd) { 165 | case F_GETLK: 166 | switch (lockCopy.l_type) { 167 | case F_WRLCK: 168 | // write-locks also check for read locks 169 | 170 | // TODO: Check offset and length instead of just file 171 | if (writeLocks.find(pathString) != writeLocks.end()) { 172 | *lockp = writeLocks.at(pathString); 173 | } else if (readLocks.find(pathString) != readLocks.end()) { 174 | *lockp = readLocks.at(pathString); 175 | } else { 176 | lockp->l_type = F_UNLCK; 177 | } 178 | break; 179 | case F_RDLCK: 180 | // TODO: Check offset and length instead of just file 181 | if (readLocks.find(pathString) != readLocks.end()) { 182 | *lockp = readLocks.at(pathString); 183 | } else { 184 | lockp->l_type = F_UNLCK; 185 | } 186 | break; 187 | } 188 | break; 189 | case F_SETLK: 190 | switch (lockCopy.l_type) { 191 | case F_WRLCK: 192 | if (writeLocks.find(pathString) != writeLocks.end()) { 193 | auto &lock = writeLocks.at(pathString); 194 | if (lock.l_pid == lockCopy.l_pid) { 195 | lock = lockCopy; 196 | } else { 197 | return -1 * EAGAIN; 198 | } 199 | } 200 | if (readLocks.find(pathString) != readLocks.end()) { 201 | auto &lock = readLocks.at(pathString); 202 | if (lock.l_pid == lockCopy.l_pid) { 203 | lock = lockCopy; 204 | } else { 205 | return -1 * EAGAIN; 206 | } 207 | } 208 | break; 209 | case F_RDLCK: 210 | if (readLocks.find(pathString) != readLocks.end()) { 211 | auto &lock = readLocks.at(pathString); 212 | if (lock.l_pid == lockCopy.l_pid) { 213 | lock = lockCopy; 214 | } else { 215 | return -1 * EAGAIN; 216 | } 217 | } 218 | break; 219 | case F_UNLCK: 220 | if (writeLocks.find(pathString) != writeLocks.end()) { 221 | auto &lock = writeLocks.at(pathString); 222 | if (lock.l_pid == lockCopy.l_pid) { 223 | writeLocks.erase(writeLocks.find(pathString)); 224 | } else { 225 | return -1 * EAGAIN; 226 | } 227 | } 228 | if (readLocks.find(pathString) != readLocks.end()) { 229 | auto &lock = readLocks.at(pathString); 230 | if (lock.l_pid == lockCopy.l_pid) { 231 | readLocks.erase(readLocks.find(pathString)); 232 | } else { 233 | return -1 * EAGAIN; 234 | } 235 | } 236 | break; 237 | } 238 | break; 239 | case F_SETLKW: 240 | LOGFATAL << "This can't happen because FUSE is single threaded."; 241 | break; 242 | default: 243 | LOGFATAL << "Invalid lock command"; 244 | } 245 | return 0; 246 | } 247 | 248 | static int codefs_getattr(const char *path, struct stat *stbuf) { 249 | if (stbuf == NULL) { 250 | LOGFATAL << "Tried to getattr with a NULL stat object"; 251 | } 252 | 253 | VLOG(2) << "GETTING ATTR FOR PATH: " << path; 254 | optional fileDataPtr = client->getNode(path); 255 | if (!fileDataPtr) { 256 | LOG(INFO) << "File doesn't exist"; 257 | return -1 * ENOENT; 258 | } 259 | FileData fileData = *fileDataPtr; 260 | optional fileSizeOverride = client->getSizeOverride(path); 261 | if (fileSizeOverride) { 262 | fileData.mutable_stat_data()->set_size(*fileSizeOverride); 263 | } 264 | FileSystem::protoToStat(fileData.stat_data(), stbuf); 265 | return 0; 266 | } 267 | 268 | static int codefs_fgetattr(const char *path, struct stat *stbuf, 269 | struct fuse_file_info *fi) { 270 | { 271 | lock_guard guard(fdMapMutex); 272 | LOG(INFO) << "GETTING ATTR FOR FD " << fi->fh; 273 | auto it = fdMap.find(fi->fh); 274 | if (it == fdMap.end()) { 275 | LOG(INFO) << "MISSING FD"; 276 | errno = EBADF; 277 | return -errno; 278 | } 279 | LOG(INFO) << "PATHS: " << string(path) << " " << it->second.path; 280 | } 281 | return codefs_getattr(path, stbuf); 282 | } 283 | 284 | static int codefs_mkdir(const char *path, mode_t mode) { 285 | int res = client->mkdir(path, mode); 286 | if (res == -1) return -errno; 287 | return 0; 288 | } 289 | 290 | static int codefs_unlink(const char *path) { 291 | int res = client->unlink(path); 292 | if (res == -1) return -errno; 293 | return 0; 294 | } 295 | 296 | static int codefs_rmdir(const char *path) { 297 | int res = client->rmdir(path); 298 | if (res == -1) return -errno; 299 | return 0; 300 | } 301 | 302 | static int codefs_symlink(const char *from, const char *to) { 303 | int res = client->symlink(from, to); 304 | if (res == -1) return -errno; 305 | return 0; 306 | } 307 | 308 | static int codefs_rename(const char *from, const char *to) { 309 | int res = client->rename(from, to); 310 | if (res == -1) return -errno; 311 | return 0; 312 | } 313 | 314 | static int codefs_chmod(const char *path, mode_t mode) { 315 | int res = client->chmod(path, mode); 316 | if (res == -1) return -errno; 317 | return 0; 318 | } 319 | 320 | static int codefs_chown(const char *path, uid_t uid, gid_t gid) { 321 | int res = client->lchown(path, uid, gid); 322 | if (res == -1) return -errno; 323 | return 0; 324 | } 325 | 326 | static int codefs_truncate(const char *path, off_t size) { 327 | int res = client->truncate(path, size); 328 | if (res == -1) return -errno; 329 | return 0; 330 | } 331 | 332 | static int codefs_ftruncate(const char *path, off_t size, 333 | struct fuse_file_info *fi) { 334 | return codefs_truncate(path, size); 335 | } 336 | 337 | static int codefs_create(const char *path, mode_t mode, 338 | struct fuse_file_info *fi) { 339 | int fd = client->create(path, fi->flags, mode); 340 | if (fd == -1) return -errno; 341 | fi->fh = fd; 342 | LOG(INFO) << "CREATING FD " << fi->fh << " FOR PATH " << path; 343 | lock_guard guard(fdMapMutex); 344 | fdMap.insert(make_pair((int64_t)fd, FdInfo(string(path)))); 345 | return 0; 346 | } 347 | 348 | static int codefs_open(const char *path, struct fuse_file_info *fi) { 349 | if (fi->flags & O_CREAT) { 350 | LOGFATAL << "GOT O_CREAT BUT NO MODE"; 351 | } 352 | int openModes = 0; 353 | int readWriteMode = (fi->flags & O_ACCMODE); 354 | if (readWriteMode == O_RDONLY) { 355 | // The file is opened in read-only mode 356 | openModes++; 357 | } 358 | if (readWriteMode == O_WRONLY) { 359 | // The file is opened in write-only mode. 360 | openModes++; 361 | if (fi->flags & O_APPEND) { 362 | // We need to get the file from the server to append 363 | LOGFATAL << "APPEND NOT SUPPORTED YET"; 364 | } 365 | } 366 | if (readWriteMode == O_RDWR) { 367 | // The file is opened in read-write mode. 368 | openModes++; 369 | } 370 | if (openModes != 1) { 371 | LOGFATAL << "Invalid open openModes: " << fi->flags; 372 | } 373 | int fd = client->open(path, fi->flags); 374 | if (fd == -1) return -errno; 375 | 376 | fi->fh = fd; 377 | LOG(INFO) << "OPENING FD " << fi->fh << " WITH MODE " << readWriteMode; 378 | lock_guard guard(fdMapMutex); 379 | fdMap.insert(make_pair((int64_t)fd, FdInfo(string(path)))); 380 | return 0; 381 | } 382 | 383 | static int codefs_read(const char *path, char *buf, size_t size, off_t offset, 384 | struct fuse_file_info *fi) { 385 | int res; 386 | 387 | res = client->pread(path, buf, size, offset); 388 | if (res == -1) res = -errno; 389 | 390 | return res; 391 | } 392 | 393 | static int codefs_write(const char *path, const char *buf, size_t size, 394 | off_t offset, struct fuse_file_info *fi) { 395 | int res; 396 | 397 | LOG(INFO) << "IN WRITE: " << path << ":" << offset; 398 | res = client->pwrite(path, buf, size, offset); 399 | if (res == -1) res = -errno; 400 | 401 | return res; 402 | } 403 | 404 | static int codefs_statfs(const char *path, struct statvfs *stbuf) { 405 | int res; 406 | 407 | res = client->statvfs(stbuf); 408 | if (res == -1) return -errno; 409 | 410 | return 0; 411 | } 412 | 413 | static int codefs_release(const char *path, struct fuse_file_info *fi) { 414 | int fd = fi->fh; 415 | { 416 | lock_guard guard(fdMapMutex); 417 | LOG(INFO) << "RELEASING " << path << " FD " << fd; 418 | auto it = fdMap.find((int64_t)(fd)); 419 | if (it == fdMap.end()) { 420 | LOGFATAL << "Tried to close an fd that doesn't exist"; 421 | } 422 | string pathFromFd = it->second.path; 423 | if (pathFromFd != string(path)) { 424 | LOG(ERROR) << "PATHS DO NOT MATCH! " << pathFromFd << " " << path; 425 | } 426 | fdMap.erase(it); 427 | } 428 | client->close(path, fd); 429 | return 0; 430 | } 431 | 432 | static int codefs_utimens(const char *path, const struct timespec ts[2]) { 433 | int res; 434 | 435 | /* don't use utime/utimes since they follow symlinks */ 436 | res = client->utimensat(path, ts); 437 | if (res == -1) return -errno; 438 | return 0; 439 | } 440 | 441 | static int codefs_removexattr(const char *path, const char *name) { 442 | int res = client->lremovexattr(path, name); 443 | if (res == -1) return -errno; 444 | return 0; 445 | } 446 | 447 | static int codefs_setxattr(const char *path, const char *name, 448 | const char *value, size_t size, int flags) { 449 | int res = client->lsetxattr(path, name, value, size, flags); 450 | if (res == -1) return -errno; 451 | return 0; 452 | } 453 | 454 | #if __APPLE__ 455 | static int codefs_getxattr_osx(const char *path, const char *name, char *value, 456 | size_t size, uint32_t position) { 457 | if (position) { 458 | LOGFATAL << "Got a non-zero position: " << position; 459 | } 460 | return codefs_getxattr(path, name, value, size); 461 | } 462 | 463 | static int codefs_setxattr_osx(const char *path, const char *name, 464 | const char *value, size_t size, int flags, 465 | uint32_t position) { 466 | if (position) { 467 | LOGFATAL << "Got a non-zero position: " << position; 468 | } 469 | return codefs_setxattr(path, name, value, size, flags); 470 | } 471 | #endif 472 | 473 | void ClientFuseAdapter::assignCallbacks( 474 | shared_ptr _fileSystem, shared_ptr _client, 475 | fuse_operations *ops) { 476 | if (fileSystem.get()) { 477 | LOGFATAL << "Already initialized FUSE ops!"; 478 | } 479 | fileSystem = _fileSystem; 480 | client = _client; 481 | ops->init = codefs_init; 482 | ops->access = codefs_access; 483 | ops->readlink = codefs_readlink; 484 | // ops->opendir = codefs_opendir; 485 | ops->readdir = codefs_readdir; 486 | // ops->releasedir = codefs_releasedir; 487 | ops->fsync = codefs_fsync; 488 | ops->fsyncdir = codefs_fsyncdir; 489 | ops->lock = codefs_lock; 490 | // ops->flock = codefs_flock; 491 | 492 | ops->mkdir = codefs_mkdir; 493 | ops->symlink = codefs_symlink; 494 | ops->getattr = codefs_getattr; 495 | ops->fgetattr = codefs_fgetattr; 496 | ops->unlink = codefs_unlink; 497 | ops->rmdir = codefs_rmdir; 498 | ops->rename = codefs_rename; 499 | // ops->link = codefs_link; 500 | ops->chmod = codefs_chmod; 501 | ops->chown = codefs_chown; 502 | ops->truncate = codefs_truncate; 503 | ops->ftruncate = codefs_ftruncate; 504 | ops->utimens = codefs_utimens; 505 | ops->create = codefs_create; 506 | ops->open = codefs_open; 507 | ops->read = codefs_read; 508 | ops->write = codefs_write; 509 | ops->statfs = codefs_statfs; 510 | ops->release = codefs_release; 511 | ops->listxattr = codefs_listxattr; 512 | #if __APPLE__ 513 | ops->setxattr = codefs_setxattr_osx; 514 | ops->getxattr = codefs_getxattr_osx; 515 | #else 516 | ops->setxattr = codefs_setxattr; 517 | ops->getxattr = codefs_getxattr; 518 | #endif 519 | ops->removexattr = codefs_removexattr; 520 | } 521 | 522 | } // namespace codefs 523 | -------------------------------------------------------------------------------- /src/client/Client.cpp: -------------------------------------------------------------------------------- 1 | #include "Client.hpp" 2 | 3 | #include "FileUtils.hpp" 4 | 5 | namespace codefs { 6 | Client::Client(const string& _address, shared_ptr _fileSystem) 7 | : address(_address), fileSystem(_fileSystem) { 8 | MessageReader reader; 9 | MessageWriter writer; 10 | rpc = 11 | shared_ptr(new ZmqBiDirectionalRpc(address, false)); 12 | writer.start(); 13 | writer.writePrimitive(CLIENT_SERVER_FETCH_METADATA); 14 | writer.writePrimitive(1); 15 | writer.writePrimitive(string("/")); 16 | RpcId initId = rpc->request(writer.finish()); 17 | 18 | while (true) { 19 | LOG(INFO) << "Waiting for init..."; 20 | { 21 | lock_guard lock(mutex); 22 | rpc->update(); 23 | rpc->heartbeat(); 24 | if (rpc->hasIncomingReplyWithId(initId)) { 25 | string payload = rpc->consumeIncomingReplyWithId(initId); 26 | reader.load(payload); 27 | auto path = reader.readPrimitive(); 28 | auto data = reader.readPrimitive(); 29 | fileSystem->deserializeFileDataCompressed(path, data); 30 | break; 31 | } 32 | } 33 | sleep(1); 34 | } 35 | } 36 | 37 | int Client::update() { 38 | MessageReader reader; 39 | MessageWriter writer; 40 | lock_guard lock(mutex); 41 | rpc->update(); 42 | 43 | while (rpc->hasIncomingRequest()) { 44 | auto idPayload = rpc->getFirstIncomingRequest(); 45 | auto id = idPayload.id; 46 | string payload = idPayload.payload; 47 | reader.load(payload); 48 | unsigned char header = reader.readPrimitive(); 49 | switch (header) { 50 | case SERVER_CLIENT_METADATA_UPDATE: { 51 | string path = reader.readPrimitive(); 52 | LOG(INFO) << "UPDATING PATH: " << path; 53 | fileSystem->invalidateVfsCache(); 54 | FileData fileData = reader.readProto(); 55 | if (fileData.invalid()) { 56 | LOGFATAL << "Got filedata with invalid set"; 57 | } 58 | if (path != fileData.path()) { 59 | LOGFATAL << "PATH MISMATCH: " << path << " != " << fileData.path(); 60 | } 61 | vector pathsToDownload = fileSystem->getPathsToDownload(path); 62 | if (pathsToDownload.empty()) { 63 | // Only update this node if we already have it in cache 64 | fileSystem->setNode(fileData); 65 | } 66 | writer.start(); 67 | writer.writePrimitive(header); 68 | rpc->reply(id, writer.finish()); 69 | } break; 70 | default: 71 | LOGFATAL << "Invalid packet header: " << int(header); 72 | } 73 | } 74 | 75 | return 0; 76 | } 77 | 78 | vector> Client::getNodes(const vector& paths) { 79 | vector rpcIds; 80 | string payload; 81 | vector metadataToFetch; 82 | for (auto path : paths) { 83 | vector pathsToDownload = fileSystem->getPathsToDownload(path); 84 | VLOG(1) << "Number of paths: " << pathsToDownload.size(); 85 | for (auto it : pathsToDownload) { 86 | LOG(INFO) << "GETTING SCAN FOR PATH: " << it; 87 | metadataToFetch.push_back(it); 88 | } 89 | } 90 | 91 | if (!metadataToFetch.empty()) { 92 | RpcId id; 93 | { 94 | lock_guard lock(mutex); 95 | MessageWriter writer; 96 | writer.start(); 97 | writer.writePrimitive(CLIENT_SERVER_FETCH_METADATA); 98 | writer.writePrimitive(metadataToFetch.size()); 99 | for (auto s : metadataToFetch) { 100 | writer.writePrimitive(s); 101 | } 102 | payload = writer.finish(); 103 | id = rpc->request(payload); 104 | } 105 | rpcIds.push_back(id); 106 | string result; 107 | while (true) { 108 | usleep(1); 109 | { 110 | lock_guard lock(mutex); 111 | if (rpc->hasIncomingReplyWithId(id)) { 112 | result = rpc->consumeIncomingReplyWithId(id); 113 | break; 114 | } 115 | } 116 | } 117 | { 118 | lock_guard lock(mutex); 119 | MessageReader reader; 120 | reader.load(result); 121 | while (reader.sizeRemaining()) { 122 | auto path = reader.readPrimitive(); 123 | auto data = reader.readPrimitive(); 124 | fileSystem->deserializeFileDataCompressed(path, data); 125 | } 126 | } 127 | } 128 | 129 | vector> retval; 130 | for (const auto& path : paths) { 131 | for (int waitTicks = 0;; waitTicks++) { 132 | auto node = fileSystem->getNode(path); 133 | if (node) { 134 | bool invalid = node->invalid(); 135 | if (!invalid) { 136 | retval.push_back(node); 137 | break; 138 | } else { 139 | LOG(INFO) << path 140 | << " is invalid, waiting for new version: " << waitTicks; 141 | if (((waitTicks + 1) % 10) == 0) { 142 | LOG(ERROR) << path 143 | << " is invalid for too long, demanding new version " 144 | "from server"; 145 | string payload; 146 | { 147 | lock_guard lock(mutex); 148 | MessageWriter writer; 149 | writer.start(); 150 | writer.writePrimitive( 151 | CLIENT_SERVER_FETCH_METADATA); 152 | writer.writePrimitive(1); 153 | writer.writePrimitive(path); 154 | payload = writer.finish(); 155 | } 156 | string result = fileRpc(payload); 157 | { 158 | lock_guard lock(mutex); 159 | MessageReader reader; 160 | reader.load(result); 161 | auto path = reader.readPrimitive(); 162 | auto data = reader.readPrimitive(); 163 | fileSystem->deserializeFileDataCompressed(path, data); 164 | } 165 | } else { 166 | usleep(100 * 1000); 167 | } 168 | } 169 | } else { 170 | retval.push_back(node); 171 | break; 172 | } 173 | } 174 | } 175 | return retval; 176 | } 177 | 178 | optional Client::getNodeAndChildren(const string& path, 179 | vector* children) { 180 | auto parentNode = getNode(path); 181 | vector childrenPaths; 182 | if (parentNode) { 183 | // Check the children 184 | VLOG(1) << "NUM CHILDREN: " << parentNode->child_node_size(); 185 | children->clear(); 186 | for (int a = 0; a < parentNode->child_node_size(); a++) { 187 | string fileName = parentNode->child_node(a); 188 | string childPath = (boost::filesystem::path(parentNode->path()) / 189 | boost::filesystem::path(fileName)) 190 | .string(); 191 | childrenPaths.push_back(childPath); 192 | } 193 | 194 | if (!childrenPaths.empty()) { 195 | auto childNodes = getNodes(childrenPaths); 196 | for (auto childNode : childNodes) { 197 | if (childNode) { 198 | children->push_back(*childNode); 199 | } 200 | } 201 | } 202 | } 203 | return parentNode; 204 | } 205 | 206 | int Client::open(const string& path, int flags) { 207 | MessageReader reader; 208 | MessageWriter writer; 209 | int readWriteMode = (flags & O_ACCMODE); 210 | bool readOnly = (readWriteMode == O_RDONLY); 211 | LOG(INFO) << "Reading file " << path << " (readonly? " << readOnly << ")"; 212 | int fd = fileSystem->getNewFd(); 213 | 214 | if (!fileSystem->ownsPathContents(path)) { 215 | if (!readOnly) { 216 | fileSystem->invalidatePathAndParent(path); 217 | } 218 | 219 | auto cachedData = fileSystem->getCachedFile(path); 220 | if (readOnly && cachedData) { 221 | LOG(INFO) << "FETCHING FROM FILE CACHE: " << cachedData->size(); 222 | fileSystem->addOwnedFileContents(path, fd, *cachedData, readOnly); 223 | } else { 224 | string payload; 225 | { 226 | lock_guard lock(mutex); 227 | fileSystem->invalidateVfsCache(); 228 | writer.start(); 229 | writer.writePrimitive(CLIENT_SERVER_REQUEST_FILE); 230 | writer.writePrimitive(path); 231 | writer.writePrimitive(flags); 232 | payload = writer.finish(); 233 | } 234 | string result = fileRpc(payload); 235 | { 236 | lock_guard lock(mutex); 237 | reader.load(result); 238 | int rpcErrno = reader.readPrimitive(); 239 | if (rpcErrno) { 240 | errno = rpcErrno; 241 | return -1; 242 | } 243 | string fileContents = decompressString(reader.readPrimitive()); 244 | LOG(INFO) << "READ FILE: " << path << " WITH CONTENTS SIZE " 245 | << fileContents.size(); 246 | fileSystem->addOwnedFileContents(path, fd, fileContents, readOnly); 247 | } 248 | } 249 | } else { 250 | LOG(INFO) << "FILE IS ALREADY IN LOCAL CACHE, SKIPPING READ"; 251 | fileSystem->addHandleToOwnedFile(path, fd, readOnly); 252 | } 253 | return fd; 254 | } 255 | 256 | int Client::create(const string& path, int flags, mode_t mode) { 257 | MessageReader reader; 258 | MessageWriter writer; 259 | int readWriteMode = (flags & O_ACCMODE); 260 | bool readOnly = (readWriteMode == O_RDONLY); 261 | LOG(INFO) << "Creating file " << path << " (readonly? " << readOnly << ")"; 262 | int fd = fileSystem->getNewFd(); 263 | 264 | if (!fileSystem->ownsPathContents(path)) { 265 | if (!readOnly) { 266 | fileSystem->invalidatePathAndParent(path); 267 | } 268 | 269 | auto cachedData = fileSystem->getCachedFile(path); 270 | if (cachedData) { 271 | LOGFATAL << "TRIED TO CREATE A FILE THAT IS CACHED"; 272 | } else { 273 | string payload; 274 | { 275 | lock_guard lock(mutex); 276 | fileSystem->invalidateVfsCache(); 277 | writer.start(); 278 | writer.writePrimitive(CLIENT_SERVER_CREATE_FILE); 279 | writer.writePrimitive(path); 280 | writer.writePrimitive(flags); 281 | writer.writePrimitive(mode); 282 | payload = writer.finish(); 283 | } 284 | // Create an invalid node until we get the real one 285 | fileSystem->createStub(path); 286 | string result = fileRpc(payload); 287 | { 288 | lock_guard lock(mutex); 289 | reader.load(result); 290 | int rpcErrno = reader.readPrimitive(); 291 | if (rpcErrno) { 292 | errno = rpcErrno; 293 | fileSystem->deleteNode(path); 294 | return -1; 295 | } 296 | LOG(INFO) << "CREATED FILE: " << path; 297 | fileSystem->addOwnedFileContents(path, fd, "", readOnly); 298 | } 299 | } 300 | } else { 301 | LOGFATAL << "Tried to create a file that is already owned!"; 302 | } 303 | 304 | return fd; 305 | } 306 | 307 | int Client::close(const string& path, int fd) { 308 | MessageReader reader; 309 | MessageWriter writer; 310 | 311 | bool readOnly; 312 | string content; 313 | fileSystem->closeOwnedFile(path, fd, &readOnly, &content); 314 | 315 | string payload; 316 | { 317 | lock_guard lock(mutex); 318 | fileSystem->invalidateVfsCache(); 319 | writer.start(); 320 | writer.writePrimitive(CLIENT_SERVER_RETURN_FILE); 321 | writer.writePrimitive(path); 322 | writer.writePrimitive(readOnly); 323 | fileSystem->setCachedFile(path, content); 324 | if (readOnly) { 325 | LOG(INFO) << "RETURNED FILE " << path << " TO SERVER READ-ONLY"; 326 | } else { 327 | writer.writePrimitive(compressString(content)); 328 | LOG(INFO) << "RETURNED FILE " << path << " TO SERVER WITH " 329 | << content.size() << " BYTES"; 330 | } 331 | payload = writer.finish(); 332 | } 333 | 334 | string result = fileRpc(payload); 335 | { 336 | lock_guard lock(mutex); 337 | reader.load(result); 338 | int res = reader.readPrimitive(); 339 | int rpcErrno = reader.readPrimitive(); 340 | if (res) { 341 | errno = rpcErrno; 342 | return -1; 343 | } 344 | return 0; 345 | } 346 | } 347 | 348 | int Client::pread(const string& path, char* buf, int64_t size, int64_t offset) { 349 | return fileSystem->readOwnedFile(path, buf, size, offset); 350 | } 351 | 352 | int Client::pwrite(const string& path, const char* buf, int64_t size, 353 | int64_t offset) { 354 | return fileSystem->writeOwnedFile(path, buf, size, offset); 355 | } 356 | 357 | int Client::mkdir(const string& path, mode_t mode) { 358 | MessageReader reader; 359 | MessageWriter writer; 360 | fileSystem->invalidatePathAndParent(path); 361 | string payload; 362 | { 363 | lock_guard lock(mutex); 364 | writer.start(); 365 | writer.writePrimitive(CLIENT_SERVER_MKDIR); 366 | writer.writePrimitive(path); 367 | writer.writePrimitive(mode); 368 | payload = writer.finish(); 369 | } 370 | string result = fileRpc(payload); 371 | { 372 | lock_guard lock(mutex); 373 | reader.load(result); 374 | int res = reader.readPrimitive(); 375 | int rpcErrno = reader.readPrimitive(); 376 | if (res) { 377 | errno = rpcErrno; 378 | } 379 | return res; 380 | } 381 | } 382 | 383 | int Client::unlink(const string& path) { 384 | fileSystem->invalidatePathAndParent(path); 385 | return singlePathNoReturn(CLIENT_SERVER_UNLINK, path); 386 | } 387 | 388 | int Client::rmdir(const string& path) { 389 | fileSystem->invalidatePathAndParent(path); 390 | return singlePathNoReturn(CLIENT_SERVER_RMDIR, path); 391 | } 392 | 393 | int Client::symlink(const string& from, const string& to) { 394 | fileSystem->invalidatePathAndParent(from); 395 | fileSystem->invalidatePathAndParent(to); 396 | return twoPathsNoReturn(CLIENT_SERVER_SYMLINK, from, to); 397 | } 398 | 399 | int Client::rename(const string& from, const string& to) { 400 | fileSystem->invalidateVfsCache(); 401 | LOG(INFO) << "RENAMING FROM " << from << " TO " << to; 402 | fileSystem->renameOwnedFileIfItExists(from, to); 403 | fileSystem->invalidatePathAndParentAndChildren(from); 404 | fileSystem->invalidatePathAndParentAndChildren(to); 405 | return twoPathsNoReturn(CLIENT_SERVER_RENAME, from, to); 406 | } 407 | 408 | int Client::link(const string& from, const string& to) { 409 | fileSystem->invalidatePathAndParent(from); 410 | fileSystem->invalidatePathAndParent(to); 411 | return twoPathsNoReturn(CLIENT_SERVER_LINK, from, to); 412 | } 413 | 414 | int Client::chmod(const string& path, int mode) { 415 | MessageReader reader; 416 | MessageWriter writer; 417 | fileSystem->invalidatePath(path); 418 | string payload; 419 | { 420 | lock_guard lock(mutex); 421 | writer.start(); 422 | writer.writePrimitive(CLIENT_SERVER_CHMOD); 423 | writer.writePrimitive(path); 424 | writer.writePrimitive(mode); 425 | payload = writer.finish(); 426 | } 427 | string result = fileRpc(payload); 428 | { 429 | lock_guard lock(mutex); 430 | reader.load(result); 431 | int res = reader.readPrimitive(); 432 | int rpcErrno = reader.readPrimitive(); 433 | if (res) { 434 | errno = rpcErrno; 435 | } 436 | return res; 437 | } 438 | } 439 | int Client::lchown(const string& path, int64_t uid, int64_t gid) { 440 | MessageReader reader; 441 | MessageWriter writer; 442 | fileSystem->invalidatePath(path); 443 | string payload; 444 | { 445 | lock_guard lock(mutex); 446 | writer.start(); 447 | writer.writePrimitive(CLIENT_SERVER_LCHOWN); 448 | writer.writePrimitive(path); 449 | writer.writePrimitive(uid); 450 | writer.writePrimitive(gid); 451 | payload = writer.finish(); 452 | } 453 | string result = fileRpc(payload); 454 | { 455 | lock_guard lock(mutex); 456 | reader.load(result); 457 | int res = reader.readPrimitive(); 458 | int rpcErrno = reader.readPrimitive(); 459 | if (res) { 460 | errno = rpcErrno; 461 | } 462 | return res; 463 | } 464 | } 465 | int Client::truncate(const string& path, int64_t size) { 466 | MessageReader reader; 467 | MessageWriter writer; 468 | fileSystem->invalidateVfsCache(); 469 | if (fileSystem->truncateOwnedFileIfExists(path, size)) { 470 | return 0; 471 | } 472 | 473 | fileSystem->invalidatePath(path); 474 | string payload; 475 | { 476 | lock_guard lock(mutex); 477 | writer.start(); 478 | writer.writePrimitive(CLIENT_SERVER_TRUNCATE); 479 | writer.writePrimitive(path); 480 | writer.writePrimitive(size); 481 | payload = writer.finish(); 482 | } 483 | string result = fileRpc(payload); 484 | { 485 | lock_guard lock(mutex); 486 | reader.load(result); 487 | int res = reader.readPrimitive(); 488 | int rpcErrno = reader.readPrimitive(); 489 | if (res) { 490 | errno = rpcErrno; 491 | } 492 | return res; 493 | } 494 | } 495 | int Client::statvfs(struct statvfs* stbuf) { 496 | StatVfsData statVfsProto; 497 | MessageReader reader; 498 | MessageWriter writer; 499 | 500 | auto cachedVfs = fileSystem->getVfsCache(); 501 | 502 | if (cachedVfs) { 503 | statVfsProto = *cachedVfs; 504 | } else { 505 | string payload; 506 | { 507 | lock_guard lock(mutex); 508 | writer.start(); 509 | writer.writePrimitive(CLIENT_SERVER_STATVFS); 510 | payload = writer.finish(); 511 | } 512 | string result = fileRpc(payload); 513 | { 514 | lock_guard lock(mutex); 515 | reader.load(result); 516 | int res = reader.readPrimitive(); 517 | int rpcErrno = reader.readPrimitive(); 518 | statVfsProto = reader.readProto(); 519 | if (res) { 520 | errno = rpcErrno; 521 | return res; 522 | } 523 | fileSystem->setVfsCache(statVfsProto); 524 | } 525 | } 526 | stbuf->f_bsize = statVfsProto.bsize(); 527 | stbuf->f_frsize = statVfsProto.frsize(); 528 | stbuf->f_blocks = statVfsProto.blocks(); 529 | stbuf->f_bfree = statVfsProto.bfree(); 530 | stbuf->f_bavail = statVfsProto.bavail(); 531 | stbuf->f_files = statVfsProto.files(); 532 | stbuf->f_ffree = statVfsProto.ffree(); 533 | stbuf->f_favail = statVfsProto.favail(); 534 | stbuf->f_fsid = statVfsProto.fsid(); 535 | stbuf->f_flag = statVfsProto.flag(); 536 | stbuf->f_namemax = statVfsProto.namemax(); 537 | return 0; 538 | } 539 | 540 | int Client::utimensat(const string& path, const struct timespec ts[2]) { 541 | MessageReader reader; 542 | MessageWriter writer; 543 | fileSystem->invalidatePath(path); 544 | string payload; 545 | { 546 | lock_guard lock(mutex); 547 | writer.start(); 548 | writer.writePrimitive(CLIENT_SERVER_UTIMENSAT); 549 | writer.writePrimitive(path); 550 | writer.writePrimitive(ts[0].tv_sec); 551 | writer.writePrimitive(ts[0].tv_nsec); 552 | writer.writePrimitive(ts[1].tv_sec); 553 | writer.writePrimitive(ts[1].tv_nsec); 554 | payload = writer.finish(); 555 | } 556 | string result = fileRpc(payload); 557 | { 558 | lock_guard lock(mutex); 559 | reader.load(result); 560 | int res = reader.readPrimitive(); 561 | int rpcErrno = reader.readPrimitive(); 562 | if (res) { 563 | errno = rpcErrno; 564 | } 565 | return res; 566 | } 567 | } 568 | int Client::lremovexattr(const string& path, const string& name) { 569 | MessageReader reader; 570 | MessageWriter writer; 571 | fileSystem->invalidatePath(path); 572 | string payload; 573 | { 574 | lock_guard lock(mutex); 575 | writer.start(); 576 | writer.writePrimitive(CLIENT_SERVER_LREMOVEXATTR); 577 | writer.writePrimitive(path); 578 | writer.writePrimitive(name); 579 | payload = writer.finish(); 580 | } 581 | string result = fileRpc(payload); 582 | { 583 | lock_guard lock(mutex); 584 | reader.load(result); 585 | int res = reader.readPrimitive(); 586 | int rpcErrno = reader.readPrimitive(); 587 | if (res) { 588 | errno = rpcErrno; 589 | } 590 | return res; 591 | } 592 | } 593 | int Client::lsetxattr(const string& path, const string& name, 594 | const string& value, int64_t size, int flags) { 595 | MessageReader reader; 596 | MessageWriter writer; 597 | fileSystem->invalidatePath(path); 598 | string payload; 599 | { 600 | lock_guard lock(mutex); 601 | writer.start(); 602 | writer.writePrimitive(CLIENT_SERVER_LSETXATTR); 603 | writer.writePrimitive(path); 604 | writer.writePrimitive(name); 605 | writer.writePrimitive(value); 606 | writer.writePrimitive(size); 607 | writer.writePrimitive(flags); 608 | payload = writer.finish(); 609 | } 610 | string result = fileRpc(payload); 611 | { 612 | lock_guard lock(mutex); 613 | reader.load(result); 614 | int res = reader.readPrimitive(); 615 | int rpcErrno = reader.readPrimitive(); 616 | if (res) { 617 | errno = rpcErrno; 618 | } 619 | return res; 620 | } 621 | } 622 | 623 | int Client::twoPathsNoReturn(unsigned char header, const string& from, 624 | const string& to) { 625 | MessageReader reader; 626 | MessageWriter writer; 627 | string payload; 628 | { 629 | lock_guard lock(mutex); 630 | writer.start(); 631 | writer.writePrimitive(header); 632 | writer.writePrimitive(from); 633 | writer.writePrimitive(to); 634 | payload = writer.finish(); 635 | } 636 | string result = fileRpc(payload); 637 | { 638 | lock_guard lock(mutex); 639 | reader.load(result); 640 | int res = reader.readPrimitive(); 641 | int rpcErrno = reader.readPrimitive(); 642 | if (res) { 643 | errno = rpcErrno; 644 | } 645 | return res; 646 | } 647 | } 648 | 649 | int Client::singlePathNoReturn(unsigned char header, const string& path) { 650 | MessageReader reader; 651 | MessageWriter writer; 652 | string payload; 653 | { 654 | lock_guard lock(mutex); 655 | writer.start(); 656 | writer.writePrimitive(header); 657 | writer.writePrimitive(path); 658 | payload = writer.finish(); 659 | } 660 | string result = fileRpc(payload); 661 | { 662 | lock_guard lock(mutex); 663 | reader.load(result); 664 | int res = reader.readPrimitive(); 665 | int rpcErrno = reader.readPrimitive(); 666 | if (res) { 667 | errno = rpcErrno; 668 | } 669 | return res; 670 | } 671 | } 672 | 673 | string Client::fileRpc(const string& payload) { 674 | RpcId id; 675 | { 676 | lock_guard lock(mutex); 677 | id = rpc->request(payload); 678 | } 679 | while (true) { 680 | usleep(1); 681 | { 682 | lock_guard lock(mutex); 683 | if (rpc->hasIncomingReplyWithId(id)) { 684 | return rpc->consumeIncomingReplyWithId(id); 685 | } 686 | } 687 | } 688 | } 689 | 690 | } // namespace codefs --------------------------------------------------------------------------------