/* * Carla shared memory utils * Copyright (C) 2013-2023 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For a full copy of the GNU General Public License see the doc/GPL.txt file. */ #ifndef CARLA_SHM_UTILS_HPP_INCLUDED #define CARLA_SHM_UTILS_HPP_INCLUDED #include "CarlaUtils.hpp" #ifdef CARLA_OS_WIN struct carla_shm_t { HANDLE map; bool isServer; const char* filename; }; # define carla_shm_t_INIT { INVALID_HANDLE_VALUE, true, nullptr } #else # ifndef __WINE__ # include # endif # include # include struct carla_shm_t { int fd; const char* filename; std::size_t size; }; # define carla_shm_t_INIT { -1, nullptr, 0 } #endif // ----------------------------------------------------------------------- // shared memory calls /* * Null object returned when a shared memory operation fails. */ static const carla_shm_t gNullCarlaShm = carla_shm_t_INIT; /* * Check if a shared memory object is valid. */ static inline bool carla_is_shm_valid(const carla_shm_t& shm) noexcept { #ifdef CARLA_OS_WIN return (shm.filename != nullptr); #else return (shm.fd >= 0); #endif } /* * Initialize a shared memory object to an invalid state. */ static inline void carla_shm_init(carla_shm_t& shm) noexcept { shm = gNullCarlaShm; } /* * Create and open a new shared memory object. * Returns an invalid object if the operation failed or the filename already exists. */ static inline carla_shm_t carla_shm_create(const char* const filename) noexcept { CARLA_SAFE_ASSERT_RETURN(filename != nullptr && filename[0] != '\0', gNullCarlaShm); carla_shm_t ret; #ifdef CARLA_OS_WIN ret.map = INVALID_HANDLE_VALUE; ret.isServer = true; ret.filename = carla_strdup_safe(filename); #else try { ret.fd = ::shm_open(filename, O_CREAT|O_EXCL|O_RDWR, 0600); ret.filename = (ret.fd >= 0) ? carla_strdup_safe(filename) : nullptr; ret.size = 0; if (ret.fd >= 0 && ret.filename == nullptr) { ::close(ret.fd); ::shm_unlink(filename); ret.fd = -1; } } CARLA_SAFE_EXCEPTION_RETURN("carla_shm_create", gNullCarlaShm); #endif return ret; } /* * Attach to an existing shared memory object. */ static inline carla_shm_t carla_shm_attach(const char* const filename) noexcept { CARLA_SAFE_ASSERT_RETURN(filename != nullptr && filename[0] != '\0', gNullCarlaShm); carla_shm_t ret; #ifdef CARLA_OS_WIN ret.map = INVALID_HANDLE_VALUE; ret.isServer = false; ret.filename = carla_strdup_safe(filename); #else try { ret.fd = ::shm_open(filename, O_RDWR, 0); ret.filename = nullptr; ret.size = 0; } CARLA_SAFE_EXCEPTION_RETURN("carla_shm_attach", gNullCarlaShm); #endif return ret; } /* * Close a shared memory object and invalidate it. */ static inline void carla_shm_close(carla_shm_t& shm) noexcept { CARLA_SAFE_ASSERT_RETURN(carla_is_shm_valid(shm),); #ifdef CARLA_OS_WIN if (shm.isServer) { CARLA_SAFE_ASSERT(shm.map == INVALID_HANDLE_VALUE); } #endif #ifdef CARLA_OS_WIN if (shm.filename != nullptr) delete[] shm.filename; #else try { ::close(shm.fd); if (shm.filename != nullptr) { ::shm_unlink(shm.filename); delete[] shm.filename; } } CARLA_SAFE_EXCEPTION("carla_shm_close"); #endif shm = gNullCarlaShm; } /* * Map a shared memory object to @a size bytes and return the memory address. * @note One shared memory object can only have one mapping at a time. */ static inline void* carla_shm_map(carla_shm_t& shm, const std::size_t size) noexcept { CARLA_SAFE_ASSERT_RETURN(carla_is_shm_valid(shm), nullptr); CARLA_SAFE_ASSERT_RETURN(size > 0, nullptr); #ifdef CARLA_OS_WIN CARLA_SAFE_ASSERT_RETURN(shm.map == INVALID_HANDLE_VALUE, nullptr); #else CARLA_SAFE_ASSERT_RETURN(shm.size == 0, nullptr); #endif try { #ifdef CARLA_OS_WIN HANDLE map; if (shm.isServer) { SECURITY_ATTRIBUTES sa; carla_zeroStruct(sa); sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; map = ::CreateFileMappingA(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE|SEC_COMMIT, 0, static_cast(size), shm.filename); if (map == nullptr || map == INVALID_HANDLE_VALUE) { const DWORD errorCode = ::GetLastError(); carla_stderr2("CreateFileMapping failed for '%s', size:%lu, isServer:%i, errorCode:%x", shm.filename, size, shm.isServer, errorCode); return nullptr; } } else { map = ::OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, shm.filename); CARLA_SAFE_ASSERT_RETURN(map != nullptr, nullptr); CARLA_SAFE_ASSERT_RETURN(map != INVALID_HANDLE_VALUE, nullptr); } void* const ptr = ::MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, size); if (ptr == nullptr) { const DWORD errorCode = ::GetLastError(); carla_stderr2("MapViewOfFile failed for '%s', size:%lu, isServer:%i, errorCode:%u", shm.filename, size, shm.isServer, errorCode); ::CloseHandle(map); return nullptr; } ::VirtualLock(ptr, size); shm.map = map; return ptr; #else if (shm.filename != nullptr) { const int ret(::ftruncate(shm.fd, static_cast(size))); CARLA_SAFE_ASSERT_RETURN(ret == 0, nullptr); } void* ptr; #ifdef MAP_LOCKED ptr = ::mmap(nullptr, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, shm.fd, 0); CARLA_SAFE_ASSERT_RETURN(ptr != nullptr, nullptr); if (ptr == MAP_FAILED) #endif { ptr = ::mmap(nullptr, size, PROT_READ|PROT_WRITE, MAP_SHARED, shm.fd, 0); CARLA_SAFE_ASSERT_RETURN(ptr != nullptr, nullptr); if (ptr == MAP_FAILED) { carla_stderr2("carla_shm_map() - mmap failed: %s", std::strerror(errno)); return nullptr; } #ifndef MAP_LOCKED try { ::mlock(ptr, size); } CARLA_SAFE_EXCEPTION("carla_shm_map mlock"); #endif } shm.size = size; return ptr; #endif } CARLA_SAFE_EXCEPTION_RETURN("carla_shm_map", nullptr); } /* * Unmap a shared memory object address. */ static inline void carla_shm_unmap(carla_shm_t& shm, void* const ptr) noexcept { CARLA_SAFE_ASSERT_RETURN(carla_is_shm_valid(shm),); CARLA_SAFE_ASSERT_RETURN(ptr != nullptr,); #ifdef CARLA_OS_WIN CARLA_SAFE_ASSERT_RETURN(shm.map != INVALID_HANDLE_VALUE,); #else CARLA_SAFE_ASSERT_RETURN(shm.size > 0,); #endif try { #ifdef CARLA_OS_WIN const HANDLE map = shm.map; shm.map = INVALID_HANDLE_VALUE; ::UnmapViewOfFile(ptr); ::CloseHandle(map); #else const std::size_t size(shm.size); shm.size = 0; const int ret(::munmap(ptr, size)); CARLA_SAFE_ASSERT(ret == 0); #endif } CARLA_SAFE_EXCEPTION("carla_shm_unmap"); } #ifndef __WINE__ // ----------------------------------------------------------------------- // advanced calls /* * Create and open a new shared memory object for a XXXXXX temp filename. * Will keep trying until a free random filename is obtained. */ static inline carla_shm_t carla_shm_create_temp(char* const fileBase) noexcept { // check if the fileBase name is valid CARLA_SAFE_ASSERT_RETURN(fileBase != nullptr, gNullCarlaShm); const std::size_t fileBaseLen(std::strlen(fileBase)); CARLA_SAFE_ASSERT_RETURN(fileBaseLen > 6, gNullCarlaShm); CARLA_SAFE_ASSERT_RETURN(std::strcmp(fileBase + (fileBaseLen - 6), "XXXXXX") == 0, gNullCarlaShm); // character set to use randomly static const char charSet[] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; static const int charSetLen = static_cast(std::strlen(charSet) - 1); // -1 to avoid trailing '\0' // try until getting a valid shm is obtained or an error occurs for (;;) { // fill the XXXXXX characters randomly for (std::size_t c = fileBaseLen - 6; c < fileBaseLen; ++c) fileBase[c] = charSet[std::rand() % charSetLen]; #ifdef CARLA_OS_WIN // Windows: check if file already exists const HANDLE h = ::CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE|SEC_COMMIT, 0, 8, fileBase); if (h == INVALID_HANDLE_VALUE) { carla_stderr("carla_shm_create_temp(%s) - file mapping test error", fileBase); return gNullCarlaShm; } const DWORD error = ::GetLastError(); ::CloseHandle(h); if (error == ERROR_ALREADY_EXISTS) { carla_stderr("carla_shm_create_temp(%s) - file exists, retrying", fileBase); continue; } #endif // (try to) create new shm for this filename const carla_shm_t shm = carla_shm_create(fileBase); // all ok! if (carla_is_shm_valid(shm)) return shm; #ifndef CARLA_OS_WIN // Non-Windows: if file already exists, keep trying if (errno == EEXIST) { carla_stderr("carla_shm_create_temp(%s) - file exists, retrying", fileBase); continue; } const int localerrno = errno; carla_stderr("carla_shm_create_temp(%s) - failed, error code %i", fileBase, localerrno); #endif // some unknown error occurred, return null return gNullCarlaShm; } } // ----------------------------------------------------------------------- // shared memory, templated calls /* * Map a shared memory object, handling object type and size. */ template static inline T* carla_shm_map(carla_shm_t& shm) noexcept { return (T*)carla_shm_map(shm, sizeof(T)); } /* * Map a shared memory object and return if it's non-null. */ template static inline bool carla_shm_map(carla_shm_t& shm, T*& value) noexcept { value = (T*)carla_shm_map(shm, sizeof(T)); return (value != nullptr); } #endif // __WINE__ // ----------------------------------------------------------------------- #endif // CARLA_SHM_UTILS_HPP_INCLUDED