From f792a53a7e51fd45ac9f983cf288eefc950f016d Mon Sep 17 00:00:00 2001 From: TheAssassin Date: Sat, 5 Jan 2019 15:10:02 +0100 Subject: [PATCH] Import AppImageLauncherFS into this repo --- .gitmodules | 3 - LICENSE.txt | 2 +- cmake/install.cmake | 12 + cmake/modules/FindFUSE.cmake | 18 + lib/AppImageLauncherFS | 1 - lib/CMakeLists.txt | 4 - resources/appimagelauncherfs.service.in | 10 + src/CMakeLists.txt | 3 + src/fusefs/CMakeLists.txt | 22 + src/fusefs/error.h | 28 ++ src/fusefs/fs.cpp | 579 ++++++++++++++++++++++++ src/fusefs/fs.h | 42 ++ src/fusefs/main.cpp | 69 +++ 13 files changed, 784 insertions(+), 9 deletions(-) create mode 100644 cmake/modules/FindFUSE.cmake delete mode 160000 lib/AppImageLauncherFS create mode 100644 resources/appimagelauncherfs.service.in create mode 100644 src/fusefs/CMakeLists.txt create mode 100644 src/fusefs/error.h create mode 100644 src/fusefs/fs.cpp create mode 100644 src/fusefs/fs.h create mode 100644 src/fusefs/main.cpp diff --git a/.gitmodules b/.gitmodules index 9e2f1f0e..b9dd0c7b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "lib/AppImageUpdate"] path = lib/AppImageUpdate url = https://github.com/AppImage/AppImageUpdate.git -[submodule "lib/AppImageLauncherFS"] - path = lib/AppImageLauncherFS - url = https://github.com/TheAssassin/AppImageLauncherFS [submodule "lib/libappimage"] path = lib/libappimage url = https://github.com/AppImage/libappimage.git diff --git a/LICENSE.txt b/LICENSE.txt index ed292512..5ba5f401 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2018 TheAssassin +Copyright (c) 2018-2019 TheAssassin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/cmake/install.cmake b/cmake/install.cmake index 7e8e6849..a2187eaa 100644 --- a/cmake/install.cmake +++ b/cmake/install.cmake @@ -50,3 +50,15 @@ install( FILES ${PROJECT_BINARY_DIR}/resources/appimagelauncherd.service DESTINATION lib/systemd/user/ COMPONENT APPIMAGELAUNCHER ) + +# install systemd service configuration for appimagelauncherfs +configure_file( + ${PROJECT_SOURCE_DIR}/resources/appimagelauncherfs.service.in + ${PROJECT_BINARY_DIR}/resources/appimagelauncherfs.service + @ONLY +) +# caution: don't use ${CMAKE_INSTALL_LIBDIR} here, it's really just lib/systemd/user +install( + FILES ${PROJECT_BINARY_DIR}/resources/appimagelauncherfs.service + DESTINATION lib/systemd/user/ COMPONENT APPIMAGELAUNCHERFS +) diff --git a/cmake/modules/FindFUSE.cmake b/cmake/modules/FindFUSE.cmake new file mode 100644 index 00000000..c0501c6f --- /dev/null +++ b/cmake/modules/FindFUSE.cmake @@ -0,0 +1,18 @@ +find_path(FUSE_INCLUDE_DIR fuse.h + /usr/include + /usr/include/x86_64-linux-gnu + /usr/include/i386-linux-gnu +) + +find_library(FUSE_LIBRARY fuse) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args("FUSE" DEFAULT_MSG FUSE_INCLUDE_DIR FUSE_LIBRARY) + +mark_as_advanced(FUSE_INCLUDE_DIR FUSE_LIBRARY) + +message(STATUS "Found FUSE: ${FUSE_LIBRARY} (include dirs: ${FUSE_INCLUDE_DIR})") + +add_library(libfuse IMPORTED SHARED) +set_property(TARGET libfuse PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${FUSE_INCLUDE_DIR}") +set_property(TARGET libfuse PROPERTY IMPORTED_LOCATION "${FUSE_LIBRARY}") diff --git a/lib/AppImageLauncherFS b/lib/AppImageLauncherFS deleted file mode 160000 index b92f2c7a..00000000 --- a/lib/AppImageLauncherFS +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b92f2c7abefe90eec9013ba559f1c5f7bf95a34e diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index c34812db..59171548 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -19,10 +19,6 @@ if(ENABLE_UPDATE_HELPER) add_subdirectory(AppImageUpdate EXCLUDE_FROM_ALL) endif() -if(NOT USE_SYSTEM_APPIMAGELAUNCHERFS) - add_subdirectory(AppImageLauncherFS) -endif() - if(TRAVIS_BUILD) # TODO: use latest version of CMake once it contains the following change: # https://gitlab.kitware.com/cmake/cmake/commit/00a9d133fb2838ebb756d684659c5d51f577ede3 diff --git a/resources/appimagelauncherfs.service.in b/resources/appimagelauncherfs.service.in new file mode 100644 index 00000000..e785ff93 --- /dev/null +++ b/resources/appimagelauncherfs.service.in @@ -0,0 +1,10 @@ +[Unit] +Description=AppImageLauncherFS daemon + +[Service] +ExecStart=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/appimagelauncherfs +Restart=always +RestartSec=2 + +[Install] +WantedBy=wm.target diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8b5f917c..93166cf2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,3 +42,6 @@ add_subdirectory(daemon) # main application and helper tools add_subdirectory(ui) + +# FUSE filesystem AppImageLauncherFS +add_subdirectory(fusefs) diff --git a/src/fusefs/CMakeLists.txt b/src/fusefs/CMakeLists.txt new file mode 100644 index 00000000..1db97f38 --- /dev/null +++ b/src/fusefs/CMakeLists.txt @@ -0,0 +1,22 @@ +# use latest FUSE API +add_definitions(-DFUSE_USE_VERSION=26) +# required by FUSE +add_definitions(-D_FILE_OFFSET_BITS=64) + +if(NOT TARGET libfuse) + find_package(FUSE REQUIRED) +endif() + +set(Boost_USE_STATIC_LIBS ON) +find_package(Boost REQUIRED COMPONENTS filesystem) + +add_executable(appimagelauncherfs main.cpp fs.cpp fs.h error.h) +target_link_libraries(appimagelauncherfs PUBLIC libfuse Boost::filesystem) + +# ISO C++ spawns annoying warnings about string literals +target_compile_options(appimagelauncherfs PRIVATE -Wno-write-strings) + +install( + TARGETS appimagelauncherfs COMPONENT APPIMAGELAUNCHERFS + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/src/fusefs/error.h b/src/fusefs/error.h new file mode 100644 index 00000000..f3c5a87d --- /dev/null +++ b/src/fusefs/error.h @@ -0,0 +1,28 @@ +#pragma once + +// system includes +#include + +// base class +class AppImageLauncherFSError : public std::runtime_error { +public: + explicit AppImageLauncherFSError(const std::string& msg = "") : runtime_error(msg) {} +}; + +class AlreadyRunningError : public AppImageLauncherFSError { using AppImageLauncherFSError::AppImageLauncherFSError; }; +class CouldNotOpenFileError : public AppImageLauncherFSError { using AppImageLauncherFSError::AppImageLauncherFSError; }; +class FileNotFoundError : public AppImageLauncherFSError { using AppImageLauncherFSError::AppImageLauncherFSError; }; +class InvalidPathError : public AppImageLauncherFSError { using AppImageLauncherFSError::AppImageLauncherFSError; }; + +class AppImageAlreadyRegisteredError : public AppImageLauncherFSError { +private: + int _id; + +public: + explicit AppImageAlreadyRegisteredError(int id) : _id(id) {}; + +public: + int id() { + return _id; + } +}; diff --git a/src/fusefs/fs.cpp b/src/fusefs/fs.cpp new file mode 100644 index 00000000..2c9a9505 --- /dev/null +++ b/src/fusefs/fs.cpp @@ -0,0 +1,579 @@ +// system includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// library includes +#include + +// local includes +#include "fs.h" +#include "error.h" + +namespace bf = boost::filesystem; + +class AppImageLauncherFS::PrivateData { +public: + // a.k.a. where the filesystem will be mounted + // this only needs to be calculated once, on initialization + std::string mountpoint; + + // registered AppImages are supposed to be executed only by the user and the group + // furthermore, they must be read-only, as we don't implement any sort of writing (we don't need it) + static constexpr int DEFAULT_MODE = 0550; + // mount point directory must be writable + static constexpr int MOUNTPOINT_MODE = 0750; + + static const char REGISTER_MSG[]; + + // used for internal data management + class RegisteredAppImage { + private: + // store copy of assigned ID + // not really useful now, but might make a few things easier + int _id; + bf::path _path; + // open a file descriptor on the file on instantiation to keep files alive until the file is not needed any more + FILE* _fp; + + private: + void openFile() { + _fp = fopen(_path.c_str(), "r"); + + if (_fp == nullptr) + throw CouldNotOpenFileError(""); + } + + public: + RegisteredAppImage() : _id(-1), _fp(nullptr) {}; + + RegisteredAppImage(const int id, bf::path path) : _id(id), _path(std::move(path)), _fp(nullptr) { + openFile(); + } + + ~RegisteredAppImage() { + if (_fp != nullptr) + fclose(_fp); + _fp = nullptr; + } + +// RegisteredAppImage(const RegisteredAppImage& r) = delete; + RegisteredAppImage(const RegisteredAppImage& r) : _id(r._id), _path(r._path), _fp(nullptr) { + openFile(); + }; + + RegisteredAppImage& operator=(const RegisteredAppImage& r) { + _id = r._id; + _path = r._path; + openFile(); + } + + bool operator==(const RegisteredAppImage& r) const { + return _id == r._id && _path == r._path; + } + + public: + bf::path path() const { + return _path; + } + + int id() const { + return _id; + } + + FILE* fp() const { + return _fp; + } + }; + + typedef std::map registered_appimages_t; + + // holds registered AppImages + // they're indexed by a monotonically increasing counter, IDs may be added or removed at any time, therefore using + // a map + // numerical IDs are surely less cryptic than any other sort of identifier + static registered_appimages_t registeredAppImages; + static int counter; + + // time of creation of the instance + // used to display atimes/mtimes of associated directories and the mountpoint + static const time_t timeOfCreation; + +public: + PrivateData() { + mountpoint = generateMountpointPath(); + + // make sure new instances free old resources (terminating an old instance) and recreate everything from scratch + // TODO: disables existing instance check + // TODO: simple string concatenation = super evil + system((std::string("fusermount -u ") + mountpoint).c_str()); + system((std::string("rmdir ") + mountpoint).c_str()); + + // create mappings for all AppImages in ~/Applications, which are most used + // TODO: allow "registration" of AppImages in any directory + auto applicationsDir = std::string(getenv("HOME")) + "/Applications"; + if (bf::is_directory(applicationsDir)) { + for (bf::directory_iterator it(applicationsDir); it != bf::directory_iterator(); ++it) { + auto path = it->path(); + + if (!bf::is_regular_file(path)) + continue; + + registerAppImage(path); + } + } + } + +private: + static std::string generateMountpointPath() { + return std::string("/run/user/") + std::to_string(getuid()) + "/appimagelauncherfs/"; + } + + static std::string generateFilenameForId(int id) { + std::ostringstream filename; + filename << std::setfill('0') << std::setw(4) << id << ".AppImage"; + return filename.str(); + } + + static std::string generateTextMap() { + std::vector map(1, '\0'); + + for (const auto& entry : registeredAppImages) { + auto filename = generateFilenameForId(entry.first); + + std::ostringstream line; + line << filename << " -> " << entry.second.path().string() << std::endl; + auto lineStr = line.str(); + + map.resize(map.size() + lineStr.size()); + strcat(map.data(), lineStr.c_str()); + } + + return map.data(); + } + + static int handleReadMap(void* buf, size_t bufsize, off_t offset) { + auto map = generateTextMap(); + + // cannot request more bytes than the file size + if (offset > map.size()) + return -EIO; + + size_t bytesToCopy = std::min(bufsize, map.size()) - offset; + + // prevent int wraparound (FUSE uses 32-bit ints for everything) + if (bytesToCopy > INT32_MAX) { + return -EIO; + } + + memcpy(buf, map.data() + offset, bytesToCopy); + + return static_cast(bytesToCopy); + } + + static int handleWriteRegister(char* buf, size_t bufsize, off_t offset) { + const size_t bytesToWrite = std::min(bufsize, strlen(REGISTER_MSG)) - offset; + + // prevent int wraparound (FUSE uses 32-bit ints for everything) + if (bytesToWrite > INT32_MAX) { + return -EIO; + } + + memcpy(buf + offset, REGISTER_MSG, bytesToWrite); + + return static_cast(bytesToWrite); + } + + static int handleReadRegisteredAppImage(char* buf, size_t bufsize, off_t offset, struct fuse_file_info* fi) { + // convert our file handle into a FILE* object once to save extra conversions in the rest of the code + FILE* fh = reinterpret_cast(fi->fh); + + // seek to requested offset + ::fseek(fh, offset, SEEK_SET); + + // read into buffer + size_t bytesRead = ::fread(buf, sizeof(char), bufsize, fh); + + // prevent int wraparound (FUSE uses 32-bit ints for everything) + if (bytesRead > INT32_MAX) { + return -EIO; + } + + // patch out (a.k.a. null) magic bytes (if necessary) + constexpr auto magicBytesBegin = 8; + constexpr auto magicBytesEnd = 10; + if (offset <= magicBytesEnd) { + auto beg = magicBytesBegin - offset; + auto count = std::min(std::min((size_t) (magicBytesEnd - offset), (size_t) 2), bufsize) + 1; + memset(buf + beg, '\x00', count); + } + + return static_cast(bytesRead); + } + +public: + class CouldNotFindRegisteredAppImageError : public std::runtime_error { + public: + CouldNotFindRegisteredAppImageError() : runtime_error("") {}; + }; + + bool otherInstanceRunning() const { + // TODO: implement properly (as in, check for stale mountpoint) + return bf::is_directory(mountpoint); + } + + static int registerAppImage(const bf::path& path) { + if (!bf::exists(path)) + throw FileNotFoundError(); + + // TODO: implement check whether file is an AppImage (i.e., if it is a regular file and contains the AppImage magic bytes) + + // check whether file is registered already + + for (const auto& r : registeredAppImages) { + if (path == r.second.path()) { + throw AppImageAlreadyRegisteredError(r.first); + } + } + + const auto id = counter++; + registeredAppImages.emplace(id, RegisteredAppImage(id, path)); + + std::cout << "Registered new AppImage: " << path << " (ID: " << std::setfill('0') << std::setw(4) << id << ")" << std::endl; + return id; + } + + /** + * Maps a FUSE filesystem path to a registered AppImage's path + * @param path path provided by FUSE (must begin with leading slash) + * @return path of AppImage mapping to this path + * @throws CouldNotFindRegisteredAppImageError if path doesn't refer to registered AppImage + * @throws std::invalid_argument if path is invalid + */ + static RegisteredAppImage& mapPathToRegisteredAppImage(const std::string& path) { + std::vector mutablePath(path.size() + 1); + strcpy(mutablePath.data(), path.c_str()); + auto mutablePathPtr = mutablePath.data(); + + char* firstPart = strsep(&mutablePathPtr, "."); + + // skip leading slash + if (firstPart[0] != '/') + throw InvalidPathError("Path doesn't begin with leading /"); + + firstPart++; + + // try to convert string ID to int + int id; + try { + id = std::stoi(firstPart); + } catch (const std::invalid_argument&) { + throw CouldNotFindRegisteredAppImageError(); + } + + // check if filename matches the one we'd generate for parsed id + // that'll make sure only the listed files in the used scheme are covered by this function + if (path != ("/" + generateFilenameForId(id))) { + throw CouldNotFindRegisteredAppImageError(); + } + + return registeredAppImages[id]; + } + + static int getattr(const char* path, struct stat* st) { + std::string strpath(path); + + // there must be exactly one slash in every path only + if (std::count(strpath.begin(), strpath.end(), '/') != 1) + throw InvalidPathError("Path must contain exactly one /"); + + // path must start with said slash, it may not be in any other position + if (strpath.find('/') != 0) + throw InvalidPathError("Path does not start with /"); + + // root directory entry + if (strpath == "/") { + st->st_atim = timespec{timeOfCreation, 0}; + st->st_mtim = timespec{timeOfCreation, 0}; + + st->st_gid = getgid(); + st->st_uid = getuid(); + + st->st_mode = S_IFDIR | DEFAULT_MODE; + st->st_nlink = 2; + + return 0; + } + + // virtual read-only file generated on demand from the data stored by the fs + if (strpath == "/map") { + st->st_atim = timespec{timeOfCreation, 0}; + st->st_mtim = timespec{timeOfCreation, 0}; + + st->st_gid = getgid(); + st->st_uid = getuid(); + + st->st_mode = S_IFREG | 0444; + st->st_nlink = 1; + + auto map = generateTextMap(); + st->st_size = map.size(); + + return 0; + } + + // virtual readable and writable file + // on read, the file will return a static help message (hardcoded in the codebase) + // on write, it will interpret every line as a path of an AppImage that shall be registered + if (strpath == "/register") { + st->st_atim = timespec{timeOfCreation, 0}; + st->st_mtim = timespec{timeOfCreation, 0}; + + st->st_gid = getgid(); + st->st_uid = getuid(); + + st->st_mode = S_IFREG | 0660; + st->st_nlink = 1; + + st->st_size = strlen(REGISTER_MSG); + + return 0; + } + + // the only remaining entries might be registered AppImages, so let's try to find such an entry + // on success, we read the original file's metadata, alter it a bit (e.g., change permissions to read-only) + // if an entry cannot be found, we return an appropriate error code + try { + auto& registeredAppImage = mapPathToRegisteredAppImage(path); + + if (!bf::is_regular_file(registeredAppImage.path())) { + return -EIO; + } + + if (stat(registeredAppImage.path().c_str(), st) != 0) + throw std::runtime_error("stat() failed"); + + // overwrite permissions: read-only executable + st->st_mode = S_IFREG | 0555; + st->st_nlink = 1; + + return 0; + } catch (const CouldNotFindRegisteredAppImageError&) { + return -ENOENT; + } + + // return generic I/O error if we couldn't generate a better reply until this point + // (hint: this line _should_ be unreachable) + return -EIO; + } + + static int readdir(const char* path, void* buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info* fi) { + // we only have the root dir, so any other path shall be rejected + if (strcmp(path, "/") != 0) + return -ENOENT; + + // these entries must appear in every directory + filler(buf, ".", nullptr, 0); + filler(buf, "..", nullptr, 0); + + // two virtual files provided by the filesystem process + filler(buf, "map", nullptr, 0); + filler(buf, "register", nullptr, 0); + + // virtual entries mapping to the real registered AppImages + for (const auto& entry : PrivateData::registeredAppImages) { + auto filename = generateFilenameForId(entry.first); + filler(buf, filename.c_str(), nullptr, 0); + } + + return 0; + } + + static int read(const char* path, char* buf, size_t bufsize, off_t offset, struct fuse_file_info* fi) { + if (strcmp(path, "/map") == 0) { + return handleReadMap(buf, bufsize, offset); + } + + // shall be written to only + // this is handled by getattr() already, but a bit more error checking doesn't hurt + if (strcmp(path, "/register") == 0) { + return handleWriteRegister(buf, bufsize, offset); + } + + // only left option is that the path refers to a registered AppImage + // in this case, we first check whether the path does resolve to a registered AppImage + try { + auto& registeredAppImage = mapPathToRegisteredAppImage(path); + + return handleReadRegisteredAppImage(buf, bufsize, offset, fi); + } catch (const CouldNotFindRegisteredAppImageError&) { + // must be an unknown file + return -ENOENT; + } + + // return generic I/O error if we couldn't generate a better reply until this point + // (hint: this line _should_ be unreachable) + return -EIO; + } + + static int open(const char* path, struct fuse_file_info* fi) { + // opening both files is permitted + if (strcmp(path, "/register") == 0) { + auto pathBuf = new std::vector; + fi->fh = reinterpret_cast(pathBuf); + return 0; + } + + if (strcmp(path, "/map") == 0 && !(fi->flags & O_RDWR || fi->flags & O_WRONLY)) { + return 0; + } + + // opening registered files is permitted as well... + try { + auto& registeredAppImage = mapPathToRegisteredAppImage(path); + + // but only if they are opened read-only + // TODO: check open flags + + // reuse stored fp for file I/O + fi->fh = reinterpret_cast(registeredAppImage.fp()); + + return 0; + } catch (const CouldNotFindRegisteredAppImageError&) { + return -ENOENT; + } + } + + static int write(const char* path, const char* buf, size_t bufsize, off_t offset, struct fuse_file_info* fi) { + if (strcmp(path, "/register") != 0) + return -ENOENT; + + const std::string fragment(buf, bufsize); + auto* dataBuf = reinterpret_cast*>(fi->fh); + + std::copy(fragment.begin(), fragment.end(), std::back_inserter(*dataBuf)); + + std::cout << fragment << std::flush; + return static_cast(bufsize); + } + + static int truncate(const char* path, off_t) { + // files doesn't needed to be truncated + if (strcmp(path, "/register") == 0) + return 0; + + return -EPERM; + } + + static int release(const char* path, struct fuse_file_info* fi) { + if (strcmp(path, "/register") == 0 && (fi->fh != 0)) { + auto* buf = reinterpret_cast*>(fi->fh); + + std::string requestedPath(buf->data(), buf->size()); + + while (requestedPath.back() == '\n' || requestedPath.back() == '\r') + requestedPath.pop_back(); + + try { + registerAppImage(requestedPath); + } catch (const AppImageAlreadyRegisteredError& e) { + std::cout << "AppImage already registered: " << requestedPath << std::endl; + } catch (const AppImageLauncherFSError&) { + // ignore other errors + } + + delete buf; + fi->fh = 0; + } + + return 0; + }; +}; + +// static members must be initialized out-of-source, which makes this a bit ugly +int AppImageLauncherFS::PrivateData::counter = 0; +const time_t AppImageLauncherFS::PrivateData::timeOfCreation = time(nullptr); +AppImageLauncherFS::PrivateData::registered_appimages_t AppImageLauncherFS::PrivateData::registeredAppImages; +const char AppImageLauncherFS::PrivateData::REGISTER_MSG[] = "Write paths to AppImages into this virtual file, one per line, to register them\n"; + +// default constructor +AppImageLauncherFS::AppImageLauncherFS() : d(std::make_shared()) {} + +// +std::shared_ptr AppImageLauncherFS::operations() const { + auto ops = std::make_shared(); + + // available functionality + ops->getattr = d->getattr; + ops->read = d->read; + ops->readdir = d->readdir; + ops->open = d->open; + ops->write = d->write; + ops->truncate = d->truncate; + ops->release = d->release; + + return ops; +} + +std::string AppImageLauncherFS::mountpoint() { + return d->mountpoint; +} + +int AppImageLauncherFS::run() { + // create FUSE state instance + // the filesystem object encapsules all the functionality, and can generate a FUSE operations struct for us + auto fuseOps = operations(); + + // create fake args containing future mountpoint + std::vector args; + + auto mp = mountpoint(); + + // check whether another instance is running + if (d->otherInstanceRunning()) + throw AlreadyRunningError(""); + + // make sure mountpoint dir exists over lifetime of this object + bf::create_directories(mp); + bf::permissions(mp, static_cast(d->MOUNTPOINT_MODE)); + + // we need a normal char pointer + std::vector mpBuf(mp.size() + 1, '\0'); + strcpy(mpBuf.data(), mp.c_str()); + + // path to this binary is none of FUSE's concern + args.push_back(""); + args.push_back(mpBuf.data()); + + // force foreground mode + args.push_back("-f"); + + // "sort of debug mode" + if (getenv("DEBUG") != nullptr) { + // disable multithreading for better debugging + args.push_back("-s"); + + // enable debug output (implies f) + args.push_back("-d"); + } + + int fuse_stat = fuse_main(static_cast(args.size()), args.data(), fuseOps.get(), this); + + return fuse_stat; +} + +std::shared_ptr AppImageLauncherFS::instance = nullptr; + +std::shared_ptr AppImageLauncherFS::getInstance() { + if (instance == nullptr) + instance = std::shared_ptr(new AppImageLauncherFS); + + return instance; +} diff --git a/src/fusefs/fs.h b/src/fusefs/fs.h new file mode 100644 index 00000000..1d2b4f8a --- /dev/null +++ b/src/fusefs/fs.h @@ -0,0 +1,42 @@ +#pragma once + +// system includes +#include +#include + +// library includes +#include + +class AppImageLauncherFS { +private: + class PrivateData; + std::shared_ptr d; + + static std::shared_ptr instance; + +// this class is a singleton +// therefore, no public constructor, no copying, and no public constructor, but a getInstance() method +private: + // constructor doesn't take any arguments + AppImageLauncherFS(); + + // private copy constructor = no copies + AppImageLauncherFS(const AppImageLauncherFS&); + // same goes for the assignment operator + AppImageLauncherFS& operator=(const AppImageLauncherFS&); + +public: + static std::shared_ptr getInstance(); + +public: + // returns calculated mountpoint directory path + std::string mountpoint(); + + // returns a FUSE style list of operations that can be passed to fuse_main etc. + // you should prefer using run() + std::shared_ptr operations() const; + + // runs filesystem with FUSE + // returns exit code received from FUSE + int run(); +}; diff --git a/src/fusefs/main.cpp b/src/fusefs/main.cpp new file mode 100644 index 00000000..d52022e2 --- /dev/null +++ b/src/fusefs/main.cpp @@ -0,0 +1,69 @@ +// system includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// library includes +#include + +// local includes +#include "error.h" +#include "fs.h" + + +int main(int argc, char *argv[]) { + // sanity check + // the FS must be run by regular users only + // running as root is likely to create security holes + if ((getuid() == 0) || (geteuid() == 0)) { + fprintf(stderr, "Must not be run as root\n"); + return 1; + } + + // See which version of fuse we're running + std::cerr << "FUSE version: " << FUSE_MAJOR_VERSION << "." << FUSE_MINOR_VERSION << std::endl; + + // Perform some sanity checking on the command line: make sure + // there are enough arguments, and that neither of the last two + // start with a hyphen (this will break if you actually have a + // rootpoint or mountpoint whose name starts with a hyphen, but so + // will a zillion other programs) + if ((argc != 1)) { + std::cerr << "Usage: " << argv[0] << std::endl; + return 1; + } + + std::shared_ptr fs; + + // create filesystem instance + for (int i = 0; i < 2; i++) { + try { + fs = AppImageLauncherFS::getInstance(); + } catch (const AlreadyRunningError&) { + std::cerr << "Another instance is running already" << std::endl; + return 1; + } + } + + if (fs == nullptr) { + std::cerr << "Failed to create filesystem instance" << std::endl; + return 1; + } + + std::cerr << "mountpoint: " << fs->mountpoint() << std::endl; + + std::cerr << "Starting FUSE filesystem" << std::endl; + int fuse_stat = fs->run(); + std::cerr << "Shutting down FUSE filesystem" << std::endl; + + return fuse_stat; +}