diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f200212..7f128552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,16 @@ Versioning](https://semver.org/spec/v2.0.0.html). ### Changed +- Almost the entirety of yabridge's backend has had a rewrite to get rid of any + all dependencies on the Boost libraries to make packaging easier and to remove + the runtime dependency on Boost.Filesystem for distro packaged versions of + yabridge. This prevents yabridge from breaking when Boost gets updated + independently of the yabridge package. +- When mapping shared memory for audio and the user does not have permissions to + lock the memory, yabridge will now retry mapping the memory without locking it + instead of immediately terminating the process. An annoying desktop + notification will still be shown every time you load a plugin until you fix + this. - `effProcessEvents` VST2 calls are now filtered out from the log when `YABRIDGE_DEBUG_LEVEL` is set to 1. diff --git a/src/common/audio-shm.cpp b/src/common/audio-shm.cpp index b1c3111d..fbf4bfe4 100644 --- a/src/common/audio-shm.cpp +++ b/src/common/audio-shm.cpp @@ -20,11 +20,17 @@ #include "logging/common.h" +using namespace std::literals::string_literals; + AudioShmBuffer::AudioShmBuffer(const Config& config) : config_(config), - shm_(boost::interprocess::open_or_create, - config.name.c_str(), - boost::interprocess::read_write) { + shm_fd_(shm_open(config.name.c_str(), O_RDWR | O_CREAT, 0600)) { + if (shm_fd_ == -1) { + throw std::system_error( + std::error_code(errno, std::system_category()), + "Could not create shared memory object " + config_.name); + } + setup_mapping(); } @@ -33,21 +39,23 @@ AudioShmBuffer::~AudioShmBuffer() noexcept { // removed, so we'll do it on both sides to reduce the chance that we leak // shared memory if (!is_moved_) { - boost::interprocess::shared_memory_object::remove(config_.name.c_str()); + munmap(shm_bytes_, config_.size); + close(shm_fd_); + shm_unlink(config_.name.c_str()); } } AudioShmBuffer::AudioShmBuffer(AudioShmBuffer&& o) noexcept : config_(std::move(o.config_)), - shm_(std::move(o.shm_)), - buffer_(std::move(o.buffer_)) { + shm_fd_(std::move(o.shm_fd_)), + shm_bytes_(std::move(o.shm_bytes_)) { o.is_moved_ = true; } AudioShmBuffer& AudioShmBuffer::operator=(AudioShmBuffer&& o) noexcept { config_ = std::move(o.config_); - shm_ = std::move(o.shm_); - buffer_ = std::move(o.buffer_); + shm_fd_ = std::move(o.shm_fd_); + shm_bytes_ = std::move(o.shm_bytes_); o.is_moved_ = true; return *this; @@ -65,17 +73,22 @@ void AudioShmBuffer::resize(const Config& new_config) { } void AudioShmBuffer::setup_mapping() { - try { - // Apparently you get a `Resource temporarily unavailable` when calling - // `ftruncate()` with a size of 0 on shared memory - if (config_.size > 0) { - shm_.truncate(config_.size); - buffer_ = boost::interprocess::mapped_region( - shm_, boost::interprocess::read_write, 0, config_.size, nullptr, - MAP_LOCKED); - } - } catch (const boost::interprocess::interprocess_exception& error) { - if (error.get_native_error() == EAGAIN) { + // Apparently you get a `Resource temporarily unavailable` when calling + // `ftruncate()` with a size of 0 on shared memory + if (config_.size > 0) { + // I don't think this can fail + assert(ftruncate(shm_fd_, config_.size) == 0); + + // But this can, if the user does not have permissions to use (enough) + // locked emmory, we'll try it without locking memory and show a big + // obnoxious warning and try again without locking the memory. + uint8_t* old_shm_bytes = shm_bytes_; + shm_bytes_ = static_cast( + old_shm_bytes + ? mremap(old_shm_bytes, shm_size_, config_.size, MREMAP_MAYMOVE) + : mmap(nullptr, config_.size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_LOCKED, shm_fd_, 0)); + if (shm_bytes_ == MAP_FAILED) { Logger logger = Logger::create_exception_logger(); logger.log(""); @@ -85,8 +98,22 @@ void AudioShmBuffer::setup_mapping() { logger.log(" wiki for instructions on how to set up"); logger.log(" realtime privileges and memlock limits."); logger.log(""); - } - throw; + // Growing into a size that we cannot lock sounds like a super rare + // edge case, but let's handle it anyways + if (old_shm_bytes) { + assert(munmap(old_shm_bytes, shm_size_) == 0); + } + shm_bytes_ = static_cast(mmap(nullptr, config_.size, + PROT_READ | PROT_WRITE, + MAP_SHARED, shm_fd_, 0)); + if (shm_bytes_ == MAP_FAILED) { + throw std::system_error( + std::error_code(errno, std::system_category()), + "Could not map shared memory"); + } + } } + + shm_size_ = config_.size; } diff --git a/src/common/audio-shm.h b/src/common/audio-shm.h index 283925f6..f95e71ac 100644 --- a/src/common/audio-shm.h +++ b/src/common/audio-shm.h @@ -16,13 +16,10 @@ #pragma once +#include #include -#ifdef __WINE__ -#include "../wine-host/asio-fix.h" -#endif -#include -#include +#include /** * A shared memory object that allows audio buffers to be shared between the @@ -109,6 +106,9 @@ class AudioShmBuffer { * Connect to or create the shared memory object and map it to this * process's memory. The configuration is created on the Wine side using the * process described in `Config`'s docstring. + * + * @throw std::system_error If the shared memory object could not be + * created or mapped. */ AudioShmBuffer(const Config& config); @@ -131,6 +131,7 @@ class AudioShmBuffer { * * @throw `std::invalid_argument` If the config is for a buffer with a * different name. + * @throw std::system_error If the shared memory object could not be mapped. */ void resize(const Config& new_config); @@ -148,15 +149,17 @@ class AudioShmBuffer { * addresses might change after a call to `resize()`. */ template + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) T* input_channel_ptr(const uint32_t bus, const uint32_t channel) noexcept { - return reinterpret_cast(buffer_.get_address()) + + return reinterpret_cast(shm_bytes_) + config_.input_offsets[bus][channel]; } template + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) const T* input_channel_ptr(const uint32_t bus, const uint32_t channel) const noexcept { - return reinterpret_cast(buffer_.get_address()) + + return reinterpret_cast(shm_bytes_) + config_.input_offsets[bus][channel]; } @@ -166,15 +169,17 @@ class AudioShmBuffer { * addresses might change after a call to `resize()`. */ template + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) T* output_channel_ptr(const uint32_t bus, const uint32_t channel) noexcept { - return reinterpret_cast(buffer_.get_address()) + + return reinterpret_cast(shm_bytes_) + config_.output_offsets[bus][channel]; } template + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) const T* output_channel_ptr(const uint32_t bus, const uint32_t channel) const noexcept { - return reinterpret_cast(buffer_.get_address()) + + return reinterpret_cast(shm_bytes_) + config_.output_offsets[bus][channel]; } @@ -183,11 +188,23 @@ class AudioShmBuffer { private: /** * Resize the shared memory object, and set up the memory mapping. + * + * @throw std::system_error If the shared memory object could not be mapped. */ void setup_mapping(); - boost::interprocess::shared_memory_object shm_; - boost::interprocess::mapped_region buffer_; + /** + * The file descriptor for our shared memory object. + */ + int shm_fd_ = 0; + /** + * A pointer to our mapped shared memory region. + */ + uint8_t* shm_bytes_ = nullptr; + /** + * The size of the mapped shared memory area, used for remapping. + */ + size_t shm_size_ = 0; bool is_moved_ = false; }; diff --git a/src/wine-host/asio-fix.h b/src/wine-host/asio-fix.h index a3689dbb..d487e48b 100644 --- a/src/wine-host/asio-fix.h +++ b/src/wine-host/asio-fix.h @@ -38,12 +38,8 @@ // included here, but including headers from the detail directory directly // didn't sound like a great idea. -// FIXME: Remove Boost stuff -#include #include -#include // #include -// #include #pragma pop_macro("WIN32") #pragma pop_macro("_WIN32")