Initial commit

This commit is contained in:
beerpsi 2023-12-24 03:39:58 +07:00
commit d247e838b4
26 changed files with 2184 additions and 0 deletions

121
.gitignore vendored Normal file
View File

@ -0,0 +1,121 @@
/target
/Cargo.lock
# Created by https://www.toptal.com/developers/gitignore/api/jetbrains
# Edit at https://www.toptal.com/developers/gitignore?templates=jetbrains
### JetBrains ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
# Azure Toolkit for IntelliJ plugin
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml
# End of https://www.toptal.com/developers/gitignore/api/jetbrains

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/chuniio-yubideck.iml" filepath="$PROJECT_DIR$/.idea/chuniio-yubideck.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

27
Cargo.toml Normal file
View File

@ -0,0 +1,27 @@
[package]
name = "chuniio-yubideck"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[profile.release]
strip = true # Automatically strip symbols from the binary.
opt-level = "z" # Optimize for size.
lto = true
codegen-units = 1
panic = "abort"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
chusan = []
[dependencies]
anyhow = "1.0.76"
env_logger = { version = "0.10.1", default-features = false }
lazy_static = "1.4.0"
log = "0.4.20"
rusb = "0.9.3"
shared_memory = { path = "vendor/shared_memory" }
winapi = { version = "0.3.9", features = ["minwindef", "winnt", "winerror", "debugapi", "winbase", "winuser"] }

65
README.md Normal file
View File

@ -0,0 +1,65 @@
# chuniio-yubideck
ChuniIO driver for YubiDeck FW 3.0.
**UNTESTED!!!** Here be dragons.
Thanks to:
- [hlcm0](https://github.com/hlcm0/yubideck-io-firmware/blob/main/code/pico_yubideck_emu/report.h) and [4yn](https://github.com/4yn/slidershim/blob/main/src-slider_io/src/device/hid.rs#L257)
for YubiDeck protocol information
## Configuration
segatools.ini
```ini
[chuniio]
;; For Chunithm NEW or newer
path32=chuniio_yubideck_chusan.dll
path64=chuniio_yubideck_amdaemon.dll
;; For CHUNITHM PARADISE and older
path=chuniio_yubideck.dll
[io3]
test=0x31
service=0x32
coin=0x33
```
## USB Protocol
USB device: `1973:2001`, interface 0
- Endpoint IN Interrupt (0x81)
- Data length: 45 bytes
- `data[0]`: bits 0-5: beam 1-6 (1 = blocked)
- `data[1]`: bits 0-2 for the 3 buttons (1 = pressed)
- `data[2..34]`: pressure of touch sensor 1-32 (counting from top left)
- `data[34]`: Card type
- 0: No card
- 1: MIFARE Classic
- 2: FeliCa
- `data[35..45]`: Card IDm/Access Code
- Endpoint OUT Interrupt (0x02)
- Data length: 61 bytes
- `data[0]`: Packet type
- Packet type 1:
- `data[1..61]`: Slider LED for the first 20 sensors, RGB
- Packet type 2:
- `data[1..34]`: Slider LED for the last 11 sensors, RGB
- `data[34..37]`: Left air LED, RGB
- `data[37..40]`: Right air LED, RGB
- `data[40..43]`: Card reader LED, RGB
- `data[43..61]`: Empty padding
## Build instructions
```shell
# For CHUNITHM NEW
cargo build --features chusan --target i686-pc-windows-msvc --release
cargo build --features chusan --target x86_64-pc-windows-msvc --release
cp target/i686-pc-windows-msvc/release/chuniio_yubideck.dll chuniio_yubideck_chusan.dll
cp target/x86_64-pc-windows-msvc/release/chuniio_yubideck.dll chuniio_yubideck_amdaemon.dll
# For CHUNITHM
cargo build --target i686-pc-windows-msvc --release
cp target/i686-pc-windows-msvc/release/chuniio_yubideck.dll .
```

3
rustfmt.toml Normal file
View File

@ -0,0 +1,3 @@
imports_granularity = "Crate"
reorder_imports = true
group_imports = "StdExternalCrate"

6
src/configuration.rs Normal file
View File

@ -0,0 +1,6 @@
#[derive(Debug, Clone, Copy)]
pub struct Configuration {
pub test_key: u32,
pub service_key: u32,
pub coin_key: u32,
}

467
src/lib.rs Normal file
View File

@ -0,0 +1,467 @@
mod configuration;
mod log;
#[cfg(any(
all(feature = "chusan", target_arch = "x86_64"),
not(feature = "chusan")
))]
use std::{ffi::c_int, sync::atomic::AtomicU16};
use std::{
ffi::{c_void, CString},
fmt::Display,
sync::{
atomic::{AtomicBool, Ordering},
Arc, OnceLock,
},
thread,
time::Duration,
};
#[cfg(any(all(feature = "chusan", target_arch = "x86"), not(feature = "chusan")))]
use std::{sync::Mutex, thread::JoinHandle};
use ::log::{error, info};
use anyhow::{anyhow, Result};
use lazy_static::lazy_static;
use rusb::{DeviceHandle, GlobalContext};
use shared_memory::{Shmem, ShmemConf, ShmemError};
#[cfg(any(
all(feature = "chusan", target_arch = "x86_64"),
not(feature = "chusan")
))]
use winapi::um::winuser::GetAsyncKeyState;
use winapi::{
shared::{
minwindef::{BOOL, DWORD, HINSTANCE, LPVOID, TRUE},
winerror::{E_FAIL, S_OK},
},
um::{
winbase::GetPrivateProfileIntA,
winnt::{DLL_PROCESS_ATTACH, HRESULT},
},
};
use crate::{configuration::Configuration, log::init_logger};
#[cfg(any(all(feature = "chusan", target_arch = "x86"), not(feature = "chusan")))]
type SliderCallbackFn = unsafe extern "C" fn(data: *const u8);
static DEVICE: OnceLock<DeviceHandle<GlobalContext>> = OnceLock::new();
static mut INPUT_SHMEM: Option<Arc<Shmem>> = None;
#[cfg(any(all(feature = "chusan", target_arch = "x86"), not(feature = "chusan")))]
static mut SLIDER_THREAD: OnceLock<JoinHandle<()>> = OnceLock::new();
#[cfg(any(all(feature = "chusan", target_arch = "x86"), not(feature = "chusan")))]
static SLIDER_ACTIVE: AtomicBool = AtomicBool::new(false);
#[cfg(any(
all(feature = "chusan", target_arch = "x86_64"),
not(feature = "chusan")
))]
static COIN_COUNT: AtomicU16 = AtomicU16::new(0);
#[cfg(any(
all(feature = "chusan", target_arch = "x86_64"),
not(feature = "chusan")
))]
static COIN_PRESSED: AtomicBool = AtomicBool::new(false);
#[cfg(any(all(feature = "chusan", target_arch = "x86"), not(feature = "chusan")))]
static mut OUTPUT_SHMEM: Option<Arc<Mutex<Shmem>>> = None;
lazy_static! {
static ref CONFIGURATION: Configuration = {
let io3 = CString::new("io3").unwrap();
let test = CString::new("test").unwrap();
let service = CString::new("service").unwrap();
let coin = CString::new("coin").unwrap();
let cfg_file = CString::new(".\\segatools.ini").unwrap();
unsafe {
Configuration {
test_key: GetPrivateProfileIntA(
io3.as_ptr(),
test.as_ptr(),
0x31,
cfg_file.as_ptr(),
),
service_key: GetPrivateProfileIntA(
io3.as_ptr(),
service.as_ptr(),
0x32,
cfg_file.as_ptr(),
),
coin_key: GetPrivateProfileIntA(
io3.as_ptr(),
coin.as_ptr(),
0x33,
cfg_file.as_ptr(),
),
}
}
};
}
#[no_mangle]
extern "system" fn DllMain(_dll_module: HINSTANCE, call_reason: DWORD, _reserved: LPVOID) -> BOOL {
match call_reason {
DLL_PROCESS_ATTACH => init_logger(),
_ => {}
}
TRUE
}
#[no_mangle]
pub extern "C" fn chuni_io_get_api_version() -> u16 {
0x0101
}
#[no_mangle]
#[cfg(any(
all(feature = "chusan", target_arch = "x86_64"),
not(feature = "chusan")
))]
pub extern "C" fn chuni_io_jvs_init() -> HRESULT {
if cfg!(not(feature = "chusan")) {
if let Err(e) = yubideck_init() {
error!("Could not initialize YubiDeck: {e:#?}");
return E_FAIL;
}
}
return create_input_shared_memory();
}
#[no_mangle]
#[cfg(all(feature = "chusan", target_arch = "x86"))]
pub extern "C" fn chuni_io_jvs_init() -> HRESULT {
S_OK
}
#[no_mangle]
#[cfg(any(
all(feature = "chusan", target_arch = "x86_64"),
not(feature = "chusan")
))]
pub extern "C" fn chuni_io_jvs_poll(opbtn: *mut u8, beams: *mut u8) {
if opbtn.is_null() || beams.is_null() {
return;
}
let Some(input_shmem) = (unsafe { &INPUT_SHMEM }) else {
return;
};
let input = unsafe { input_shmem.as_slice() };
let ir_value = input[0];
let mut buttons = input[1] & 3; // Buttons are in order: coin, service, test. We take the last 2 bits
unsafe {
if GetAsyncKeyState(CONFIGURATION.test_key as c_int) != 0 {
buttons |= 1;
}
if GetAsyncKeyState(CONFIGURATION.service_key as c_int) != 0 {
buttons |= 2;
}
*opbtn = buttons;
*beams = ir_value;
}
}
#[no_mangle]
#[cfg(all(feature = "chusan", target_arch = "x86"))]
pub extern "C" fn chuni_io_jvs_poll(_opbtn: *mut u8, _beams: *mut u8) {}
#[no_mangle]
#[cfg(any(
all(feature = "chusan", target_arch = "x86_64"),
not(feature = "chusan")
))]
pub extern "C" fn chuni_io_jvs_read_coin_counter(total: *mut u16) {
if total.is_null() {
return;
}
let Some(input_shmem) = (unsafe { &INPUT_SHMEM }) else {
return;
};
let input = unsafe { input_shmem.as_slice() };
let coin_pressed = (input[1] & 4) != 0;
if coin_pressed || unsafe { GetAsyncKeyState(CONFIGURATION.coin_key as c_int) } != 0 {
let coin_previously_pressed = COIN_PRESSED.fetch_or(true, Ordering::Relaxed);
if !coin_previously_pressed {
COIN_COUNT.fetch_add(1, Ordering::Relaxed);
}
} else {
COIN_PRESSED.store(false, Ordering::Relaxed);
}
unsafe {
*total = COIN_COUNT.load(Ordering::Relaxed);
}
}
#[no_mangle]
#[cfg(all(feature = "chusan", target_arch = "x86"))]
pub extern "C" fn chuni_io_jvs_read_coin_counter(_total: *mut u16) {}
#[no_mangle]
#[cfg(any(all(feature = "chusan", target_arch = "x86"), not(feature = "chusan")))]
pub unsafe extern "C" fn chuni_io_slider_init() -> HRESULT {
match create_shared_memory("Local\\YubideckOutput", 122, true) {
Ok(s) => OUTPUT_SHMEM = Some(Arc::new(Mutex::new(s))),
Err(e) => {
error!("Could not obtain shared memory: {e:#?}");
return E_FAIL;
}
}
if cfg!(not(feature = "chusan")) {
// Already initialized in chuni_io_jvs_init()
return S_OK;
}
if let Err(e) = yubideck_init() {
error!("Failed to initialize YubiDeck: {e:#?}");
return E_FAIL;
}
return create_input_shared_memory();
}
#[no_mangle]
#[cfg(all(feature = "chusan", target_arch = "x86_64"))]
pub extern "C" fn chuni_io_slider_init(_callback: *const c_void) -> HRESULT {
S_OK
}
#[no_mangle]
#[cfg(any(all(feature = "chusan", target_arch = "x86"), not(feature = "chusan")))]
pub unsafe extern "C" fn chuni_io_slider_start(callback: *const c_void) {
if callback.is_null() {
return;
}
if SLIDER_THREAD.get().is_some() {
return;
}
SLIDER_ACTIVE.store(true, Ordering::Relaxed);
let callback = std::mem::transmute::<_, SliderCallbackFn>(callback);
let thread = thread::spawn(move || {
let Some(input_shmem) = (unsafe { &INPUT_SHMEM }) else {
return;
};
let usb_in = input_shmem.as_slice();
let mut pressure = [0u8; 32];
while SLIDER_ACTIVE.load(Ordering::Relaxed) {
pressure.copy_from_slice(&usb_in[2..34]);
for i in 0..16 {
pressure.swap(i * 2, i * 2 + 1);
}
callback(pressure.as_ptr());
thread::sleep(Duration::from_nanos(1_000_000));
}
});
SLIDER_THREAD.set(thread).unwrap();
}
#[no_mangle]
#[cfg(all(feature = "chusan", target_arch = "x86_64"))]
pub extern "C" fn chuni_io_slider_start(_callback: *const c_void) {}
#[no_mangle]
#[cfg(any(all(feature = "chusan", target_arch = "x86"), not(feature = "chusan")))]
pub extern "C" fn chuni_io_slider_stop() {
let Some(thread) = (unsafe { SLIDER_THREAD.take() }) else {
return;
};
SLIDER_ACTIVE.store(false, Ordering::Relaxed);
thread.join().expect("Couldn't join slider input thread");
}
#[no_mangle]
#[cfg(all(feature = "chusan", target_arch = "x86_64"))]
pub extern "C" fn chuni_io_slider_stop() {}
#[no_mangle]
#[cfg(any(all(feature = "chusan", target_arch = "x86"), not(feature = "chusan")))]
pub unsafe extern "C" fn chuni_io_slider_set_leds(rgb: *const u8) {
if rgb.is_null() {
return;
}
let Some(device) = DEVICE.get() else { return };
let Some(out_shmem) = &OUTPUT_SHMEM else {
error!("OUTPUT_SHMEM is unset.");
return;
};
let Ok(mut out_shmem) = out_shmem.lock() else {
error!("Could not acquire mutex of output shared memory");
return;
};
let buf = out_shmem.as_slice_mut();
let ground = std::slice::from_raw_parts(rgb, 93);
buf[0] = 0;
buf[61] = 1;
for (buf_chunk, state_chunk) in buf[1..61]
.chunks_mut(3)
.zip(ground.chunks(3).skip(11).take(20).rev())
{
buf_chunk[0] = state_chunk[0];
buf_chunk[1] = state_chunk[1];
buf_chunk[2] = state_chunk[2];
}
for (buf_chunk, state_chunk) in buf[62..95]
.chunks_mut(3)
.zip(ground.chunks(3).take(11).rev())
{
buf_chunk[0] = state_chunk[0];
buf_chunk[1] = state_chunk[1];
buf_chunk[2] = state_chunk[2];
}
if let Err(e) = device.write_interrupt(0x02, &buf[0..61], Duration::from_millis(20)) {
error!("Error writing first batch of output data: {e:#?}");
}
if let Err(e) = device.write_interrupt(0x02, &buf[61..], Duration::from_millis(20)) {
error!("Error writing second batch of output data: {e:#?}");
}
}
#[no_mangle]
#[cfg(all(feature = "chusan", target_arch = "x86_64"))]
pub extern "C" fn chuni_io_slider_set_leds(_rgb: *const u8) {}
#[no_mangle]
pub extern "C" fn chuni_io_led_init() -> HRESULT {
S_OK
}
#[no_mangle]
#[cfg(any(all(feature = "chusan", target_arch = "x86"), not(feature = "chusan")))]
pub unsafe extern "C" fn chuni_io_led_set_colors(board: u8, rgb: *const u8) {
let Some(device) = DEVICE.get() else { return };
let Some(out_shmem) = &OUTPUT_SHMEM else {
error!("OUTPUT_SHMEM is unset.");
return;
};
let Ok(mut out_shmem) = out_shmem.lock() else {
error!("Could not acquire mutex of output shared memory");
return;
};
let buf = out_shmem.as_slice_mut();
let data = std::slice::from_raw_parts(rgb, 183);
buf[61] = 1;
match board {
0 => {
// left air
buf[95] = data[150];
buf[96] = data[151];
buf[97] = data[152];
}
1 => {
// right air
buf[98] = data[180];
buf[99] = data[181];
buf[100] = data[182];
}
_ => {}
}
if let Err(e) = device.write_interrupt(0x02, &buf[61..], Duration::from_millis(20)) {
error!("Error writing second batch of output data: {e:#?}");
}
}
#[no_mangle]
#[cfg(all(feature = "chusan", target_arch = "x86_64"))]
pub extern "C" fn chuni_io_led_set_colors(_rgb: *const u8) {}
fn create_input_shared_memory() -> HRESULT {
match create_shared_memory("Local\\YubideckInput", 45, false) {
Ok(s) => {
unsafe { INPUT_SHMEM = Some(Arc::new(s)) };
S_OK
}
Err(e) => {
error!("Could not acquire shared memory: {e:#?}");
E_FAIL
}
}
}
fn create_shared_memory<S>(os_id: S, size: usize, is_owner: bool) -> Result<Shmem>
where
S: AsRef<str> + Display + Copy,
{
let shmem_conf = ShmemConf::new().size(size).os_id(os_id);
let shmem_result = match shmem_conf.clone().create() {
Ok(s) => Ok(s),
Err(ShmemError::MappingIdExists) => shmem_conf.open(),
Err(e) => {
return Err(anyhow!(
"Failed to create/open shared memory {os_id}: {e:#}"
));
}
};
shmem_result
.map(|mut m| {
m.set_owner(is_owner);
m
})
.map_err(|e| anyhow!("Failed to create/open shared memory {os_id}: {e:#}"))
}
fn yubideck_init() -> Result<()> {
let Some(mut device) = rusb::open_device_with_vid_pid(0x1973, 0x2001) else {
return Err(anyhow!("YubiDeck not found."));
};
device.set_active_configuration(1)?;
device.claim_interface(0)?;
DEVICE
.set(device)
.map_err(|e| anyhow!("Cannot store device handle: {e:#?}"))?;
thread::spawn(input_thread_proc);
Ok(())
}
fn input_thread_proc() {
info!("Input thread started");
let mut shmem = match create_shared_memory("Local\\YubideckInput", 45, true) {
Ok(s) => s,
Err(e) => {
error!("Could not obtain shared memory for YubiDeck input: {e:#}");
return;
}
};
let usb_in = unsafe { shmem.as_slice_mut() };
let device = DEVICE.get().unwrap();
loop {
if let Err(e) = device.read_interrupt(0x81, usb_in, Duration::from_millis(20)) {
error!("Failed to read data from YubiDeck: {e:#}");
}
}
}

42
src/log.rs Normal file
View File

@ -0,0 +1,42 @@
use std::{ffi::CString, io::Write};
use winapi::um::debugapi::OutputDebugStringA;
#[derive(Debug)]
pub struct Logger {}
impl Write for Logger {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if let Ok(c_str) = CString::new(buf) {
unsafe { OutputDebugStringA(c_str.as_ptr()) }
}
std::io::stdout().write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
std::io::stdout().flush()
}
}
pub fn init_logger() {
env_logger::builder()
.filter_level(::log::LevelFilter::Error)
.filter_module(
"chuniio-tasoller",
if cfg!(debug_assertions) {
::log::LevelFilter::Debug
} else {
::log::LevelFilter::Info
},
)
.parse_default_env()
.target(env_logger::Target::Pipe(Box::new(Logger {})))
.format(|f, record| {
let target = record.target();
let level = record.level();
writeln!(f, "{} {} -> {}", level, target, record.args())
})
.init();
}

View File

@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

View File

@ -0,0 +1,58 @@
name: build
on:
push:
branches: [master]
pull_request:
jobs:
build:
name: Build
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
toolchain: [stable]
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout Sources
uses: actions/checkout@v2.4.0
- name: Install Rust Toolchain
uses: actions-rs/toolchain@v1.0.7
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Check Code Format
uses: actions-rs/cargo@v1.0.3
with:
command: fmt
args: -- --check
- name: Code Lint
uses: actions-rs/cargo@v1.0.3
with:
command: clippy
args: --all-targets -- -D warnings
- name: Build
uses: actions-rs/cargo@v1.0.3
with:
command: build
- name: Tests
uses: actions-rs/cargo@v1.0.3
with:
command: test
- name: Examples
uses: actions-rs/cargo@v1.0.3
env:
RUST_LOG: "trace"
with:
command: run
args: --all-features --example mutex -- 15

5
vendor/shared_memory/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/target
**/*.rs.bk
Cargo.lock
.vscode/
/.idea

42
vendor/shared_memory/Cargo.toml vendored Normal file
View File

@ -0,0 +1,42 @@
[package]
name = "shared_memory"
description = "A user friendly crate that allows you to share memory between processes"
version = "0.12.4-chuniio"
authors = ["ElasT0ny <elast0ny00@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2018"
#Extra fields for crates.io
readme = "README.md"
documentation = "https://docs.rs/shared_memory"
repository = "https://github.com/elast0ny/shared_memory-rs"
keywords = ["shmem", "shared", "memory", "inter-process", "process"]
categories = [
"os::unix-apis",
"os::windows-apis",
"memory-management",
"concurrency",
"asynchronous",
]
exclude = ["ci/*", ".github/*"]
[features]
default = []
logging = ["log"]
[dependencies]
cfg-if = "1.0"
log = { version = "0.4", optional = true }
[target.'cfg(unix)'.dependencies]
nix = "0.23"
libc = "0.2"
[target.'cfg(windows)'.dependencies]
win-sys = "0.3"
[dev-dependencies]
raw_sync = "0.1"
clap = {version = "3", features = ["derive"]}
env_logger = "0"

33
vendor/shared_memory/README.md vendored Normal file
View File

@ -0,0 +1,33 @@
# shared_memory
[![Build Status](https://github.com/elast0ny/shared_memory-rs/workflows/build/badge.svg)](https://github.com/elast0ny/shared_memory-rs/actions?query=workflow%3Abuild)
[![crates.io](https://img.shields.io/crates/v/shared_memory.svg)](https://crates.io/crates/shared_memory)
[![mio](https://docs.rs/shared_memory/badge.svg)](https://docs.rs/shared_memory/)
[![Lines of Code](https://tokei.rs/b1/github/elast0ny/shared_memory-rs?category=code)](https://tokei.rs/b1/github/elast0ny/shared_memory-rs?category=code)
A crate that allows you to share memory between __processes__.
This crate provides lightweight wrappers around shared memory APIs in an OS agnostic way. It is intended to be used with it's sister crate [raw_sync](https://github.com/elast0ny/raw_sync-rs) which provide simple primitves to synchronize access to the shared memory (Mutex, RwLock, Events, etc...).
| raw_sync |
|----|
|[![crates.io](https://img.shields.io/crates/v/raw_sync.svg)](https://crates.io/crates/raw_sync) [![docs.rs](https://docs.rs/raw_sync/badge.svg)](https://docs.rs/raw_sync/)|
## Usage
For usage examples, see code located in [examples/](examples/) :
| Examples | Description |
|----------|-------------|
|[event](examples/event.rs)| Shows the use of shared events through shared memory|
|[mutex](examples/mutex.rs)| Shows the use of a shared mutex through shared memory|
## License
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
* [MIT license](http://opensource.org/licenses/MIT)
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.

20
vendor/shared_memory/changelog.md vendored Normal file
View File

@ -0,0 +1,20 @@
# Changelog
# 0.12.4
- Allow Windows users to open shared memory that isnt managed by this crate
- Added tests
# 0.12.2
- Default feature behavior is to disable logging on release builds
- Reverted edition bump back to 2018
- Updated to use Microsoft's `windows-rs` crate
# ~~0.12.1 (yanked)~~
- ~~Updated to latest edition (2021)~~
# 0.12.0
- Windows implementation now follows POSIX behavior in regards to ownership and deletion, see [#59](https://github.com/elast0ny/shared_memory-rs/pull/59) for more details
# __0.11.X__
This release breaks backwards compatibility and removes a bunch of previous features which hid many unsafe behaviors (automatically casting shared memory to Rust types).
The release also marks the split between `shared_memory` and its synchronization primitives into a seperate crate `raw_sync`.

78
vendor/shared_memory/examples/basic.rs vendored Normal file
View File

@ -0,0 +1,78 @@
use std::thread;
use clap::Parser;
use shared_memory::*;
/// Spawns N threads that increment a value to 100
#[derive(Parser)]
#[clap(author, version, about)]
struct Args {
/// Number of threads to spawn
num_threads: usize,
/// Count to this value
#[clap(long, short, default_value_t = 50)]
count_to: u8,
}
fn main() {
env_logger::init();
let args = Args::parse();
if args.num_threads < 1 {
eprintln!("Invalid number of threads");
return;
}
let mut threads = Vec::with_capacity(args.num_threads);
let _ = std::fs::remove_file("basic_mapping");
let max = args.count_to;
// Spawn N threads
for i in 0..args.num_threads {
let thread_id = i + 1;
threads.push(thread::spawn(move || {
increment_value("basic_mapping", thread_id, max);
}));
}
// Wait for threads to exit
for t in threads.drain(..) {
t.join().unwrap();
}
}
/// Increments a value that lives in shared memory
fn increment_value(shmem_flink: &str, thread_num: usize, max: u8) {
// Create or open the shared memory mapping
let shmem = match ShmemConf::new().size(4096).flink(shmem_flink).create() {
Ok(m) => m,
Err(ShmemError::LinkExists) => ShmemConf::new().flink(shmem_flink).open().unwrap(),
Err(e) => {
eprintln!(
"Unable to create or open shmem flink {} : {}",
shmem_flink, e
);
return;
}
};
// Get pointer to the shared memory
let raw_ptr = shmem.as_ptr();
// WARNING: This is prone to race conditions as no sync/locking is used
unsafe {
while std::ptr::read_volatile(raw_ptr) < max {
// Increment shared value by one
*raw_ptr += 1;
println!(
"[thread:{}] {}",
thread_num,
std::ptr::read_volatile(raw_ptr)
);
// Sleep for a bit
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
}

36
vendor/shared_memory/examples/event.rs vendored Normal file
View File

@ -0,0 +1,36 @@
use raw_sync::{events::*, Timeout};
use shared_memory::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
// Attempt to create a mapping or open if it already exists
println!("Getting the shared memory mapping");
let shmem = match ShmemConf::new().size(4096).flink("event_mapping").create() {
Ok(m) => m,
Err(ShmemError::LinkExists) => ShmemConf::new().flink("event_mapping").open()?,
Err(e) => return Err(Box::new(e)),
};
if shmem.is_owner() {
//Create an event in the shared memory
println!("Creating event in shared memory");
let (evt, used_bytes) = unsafe { Event::new(shmem.as_ptr(), true)? };
println!("\tUsed {} bytes", used_bytes);
println!("Launch another instance of this example to signal the event !");
evt.wait(Timeout::Infinite)?;
println!("\tGot signal !");
} else {
// Open existing event
println!("Openning event from shared memory");
let (evt, used_bytes) = unsafe { Event::from_existing(shmem.as_ptr())? };
println!("\tEvent uses {} bytes", used_bytes);
println!("Signaling event !");
evt.set(EventState::Signaled)?;
println!("\tSignaled !");
}
println!("Done !");
Ok(())
}

117
vendor/shared_memory/examples/mutex.rs vendored Normal file
View File

@ -0,0 +1,117 @@
use std::sync::atomic::{AtomicU8, Ordering};
use std::thread;
use clap::Parser;
use raw_sync::locks::*;
use shared_memory::*;
/// Spawns N threads that increment a value to 10 using a mutex
#[derive(Parser)]
#[clap(author, version, about)]
struct Args {
/// Number of threads to spawn
num_threads: usize,
/// Count to this value
#[clap(long, short, default_value_t = 50)]
count_to: u8,
}
fn main() {
env_logger::init();
let args = Args::parse();
if args.num_threads < 1 {
eprintln!("num_threads should be 2 or more");
return;
}
let mut threads = Vec::with_capacity(args.num_threads);
let _ = std::fs::remove_file("mutex_mapping");
// Spawn N threads
for i in 0..args.num_threads {
let thread_id = i + 1;
threads.push(thread::spawn(move || {
increment_value("mutex_mapping", thread_id);
}));
}
// Wait for threads to exit
for t in threads.drain(..) {
t.join().unwrap();
}
}
fn increment_value(shmem_flink: &str, thread_num: usize) {
// Create or open the shared memory mapping
let shmem = match ShmemConf::new().size(4096).flink(shmem_flink).create() {
Ok(m) => m,
Err(ShmemError::LinkExists) => ShmemConf::new().flink(shmem_flink).open().unwrap(),
Err(e) => {
eprintln!(
"Unable to create or open shmem flink {} : {}",
shmem_flink, e
);
return;
}
};
let mut raw_ptr = shmem.as_ptr();
let is_init: &mut AtomicU8;
unsafe {
is_init = &mut *(raw_ptr as *mut u8 as *mut AtomicU8);
raw_ptr = raw_ptr.add(8);
};
// Initialize or wait for initialized mutex
let mutex = if shmem.is_owner() {
is_init.store(0, Ordering::Relaxed);
// Initialize the mutex
let (lock, _bytes_used) = unsafe {
Mutex::new(
raw_ptr, // Base address of Mutex
raw_ptr.add(Mutex::size_of(Some(raw_ptr))), // Address of data protected by mutex
)
.unwrap()
};
is_init.store(1, Ordering::Relaxed);
lock
} else {
// wait until mutex is initialized
while is_init.load(Ordering::Relaxed) != 1 {}
// Load existing mutex
let (lock, _bytes_used) = unsafe {
Mutex::from_existing(
raw_ptr, // Base address of Mutex
raw_ptr.add(Mutex::size_of(Some(raw_ptr))), // Address of data protected by mutex
)
.unwrap()
};
lock
};
// Loop until mutex data reaches 10
loop {
// Scope where mutex will be locked
{
let mut guard = mutex.lock().unwrap();
// Cast mutex data to &mut u8
let val: &mut u8 = unsafe { &mut **guard };
if *val > 5 {
println!("[thread#{}] done !", thread_num);
return;
}
// Print contents and increment value
println!("[thread#{}] Val : {}", thread_num, *val);
*val += 1;
// Hold lock for a second
std::thread::sleep(std::time::Duration::from_secs(1));
}
// Timeout this thread for a second
std::thread::sleep(std::time::Duration::from_secs(1));
}
}

48
vendor/shared_memory/src/error.rs vendored Normal file
View File

@ -0,0 +1,48 @@
#[derive(Debug)]
pub enum ShmemError {
MapSizeZero,
NoLinkOrOsId,
FlinkInvalidOsId,
LinkCreateFailed(std::io::Error),
LinkWriteFailed(std::io::Error),
LinkExists,
LinkOpenFailed(std::io::Error),
LinkReadFailed(std::io::Error),
LinkDoesNotExist,
MappingIdExists,
MapCreateFailed(u32),
MapOpenFailed(u32),
UnknownOsError(u32),
}
impl std::fmt::Display for ShmemError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ShmemError::MapSizeZero => f.write_str("You cannot create a shared memory mapping of 0 size"),
ShmemError::NoLinkOrOsId => f.write_str("Tried to open mapping without flink path or os_id"),
ShmemError::FlinkInvalidOsId => f.write_str("Tried to open mapping from both flink and os_id but the flink did not point to the same os_id"),
ShmemError::LinkCreateFailed(err) => write!(f, "Creating the link file failed, {}", err),
ShmemError::LinkWriteFailed(err) => write!(f, "Writing the link file failed, {}", err),
ShmemError::LinkExists => f.write_str("Shared memory link already exists"),
ShmemError::LinkOpenFailed(err) => write!(f, "Opening the link file failed, {}", err),
ShmemError::LinkReadFailed(err) => write!(f, "Reading the link file failed, {}", err),
ShmemError::LinkDoesNotExist => f.write_str("Requested link file does not exist"),
ShmemError::MappingIdExists => f.write_str("Shared memory OS specific ID already exists"),
ShmemError::MapCreateFailed(err) => write!(f, "Creating the shared memory failed, os error {}", err),
ShmemError::MapOpenFailed(err) => write!(f, "Opening the shared memory failed, os error {}", err),
ShmemError::UnknownOsError(err) => write!(f, "An unexpected OS error occurred, os error {}", err),
}
}
}
impl std::error::Error for ShmemError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ShmemError::LinkCreateFailed(err) => Some(err),
ShmemError::LinkWriteFailed(err) => Some(err),
ShmemError::LinkOpenFailed(err) => Some(err),
ShmemError::LinkReadFailed(err) => Some(err),
_ => None,
}
}
}

267
vendor/shared_memory/src/lib.rs vendored Normal file
View File

@ -0,0 +1,267 @@
//! A thin wrapper around shared memory system calls
//!
//! For help on how to get started, take a look at the [examples](https://github.com/elast0ny/shared_memory-rs/tree/master/examples) !
use std::fs::{File, OpenOptions};
use std::io::{ErrorKind, Read, Write};
use std::fs::remove_file;
use std::path::{Path, PathBuf};
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "logging")] {
pub use log;
} else {
#[allow(unused_macros)]
mod log {
macro_rules! trace (($($tt:tt)*) => {{}});
macro_rules! debug (($($tt:tt)*) => {{}});
macro_rules! info (($($tt:tt)*) => {{}});
macro_rules! warn (($($tt:tt)*) => {{}});
macro_rules! error (($($tt:tt)*) => {{}});
pub(crate) use {debug, trace};
}
}
}
use crate::log::*;
mod error;
pub use error::*;
//Load up the proper OS implementation
cfg_if! {
if #[cfg(target_os="windows")] {
mod windows;
use windows as os_impl;
} else if #[cfg(any(target_os="freebsd", target_os="linux", target_os="macos"))] {
mod unix;
use crate::unix as os_impl;
} else {
compile_error!("shared_memory isnt implemented for this platform...");
}
}
#[derive(Clone, Default)]
/// Struct used to configure different parameters before creating a shared memory mapping
pub struct ShmemConf {
owner: bool,
os_id: Option<String>,
overwrite_flink: bool,
flink_path: Option<PathBuf>,
size: usize,
ext: os_impl::ShmemConfExt,
}
impl Drop for ShmemConf {
fn drop(&mut self) {
// Delete the flink if we are the owner of the mapping
if self.owner {
if let Some(flink_path) = self.flink_path.as_ref() {
debug!("Deleting file link {}", flink_path.to_string_lossy());
let _ = remove_file(flink_path);
}
}
}
}
impl ShmemConf {
/// Create a new default shmem config
pub fn new() -> Self {
ShmemConf::default()
}
/// Provide a specific os identifier for the mapping
///
/// When not specified, a randomly generated identifier will be used
pub fn os_id<S: AsRef<str>>(mut self, os_id: S) -> Self {
self.os_id = Some(String::from(os_id.as_ref()));
self
}
/// Overwrites file links if it already exist when calling `create()`
pub fn force_create_flink(mut self) -> Self {
self.overwrite_flink = true;
self
}
/// Create the shared memory mapping with a file link
///
/// This creates a file on disk that contains the unique os_id for the mapping.
/// This can be useful when application want to rely on filesystems to share mappings
pub fn flink<S: AsRef<Path>>(mut self, path: S) -> Self {
self.flink_path = Some(PathBuf::from(path.as_ref()));
self
}
/// Sets the size of the mapping that will be used in `create()`
pub fn size(mut self, size: usize) -> Self {
self.size = size;
self
}
/// Create a new mapping using the current configuration
pub fn create(mut self) -> Result<Shmem, ShmemError> {
if self.size == 0 {
return Err(ShmemError::MapSizeZero);
}
if let Some(ref flink_path) = self.flink_path {
if !self.overwrite_flink && flink_path.is_file() {
return Err(ShmemError::LinkExists);
}
}
// Create the mapping
// Do not generate a random ID if no os_id is set, since we're optimizing for size
let mapping = match self.os_id {
None => return Err(ShmemError::NoLinkOrOsId),
Some(ref specific_id) => os_impl::create_mapping(specific_id, self.size)?,
};
debug!("Created shared memory mapping '{}'", mapping.unique_id);
// Create flink
if let Some(ref flink_path) = self.flink_path {
debug!("Creating file link that points to mapping");
let mut open_options: OpenOptions = OpenOptions::new();
open_options.write(true);
if self.overwrite_flink {
open_options.create(true).truncate(true);
} else {
open_options.create_new(true);
}
match open_options.open(flink_path) {
Ok(mut f) => {
// write the shmem uid asap
if let Err(e) = f.write(mapping.unique_id.as_bytes()) {
let _ = std::fs::remove_file(flink_path);
return Err(ShmemError::LinkWriteFailed(e));
}
}
Err(e) if e.kind() == ErrorKind::AlreadyExists => {
return Err(ShmemError::LinkExists)
}
Err(e) => return Err(ShmemError::LinkCreateFailed(e)),
}
debug!(
"Created file link '{}' with id '{}'",
flink_path.to_string_lossy(),
mapping.unique_id
);
}
self.owner = true;
self.size = mapping.map_size;
Ok(Shmem {
config: self,
mapping,
})
}
/// Opens an existing mapping using the current configuration
pub fn open(mut self) -> Result<Shmem, ShmemError> {
// Must at least have a flink or an os_id
if self.flink_path.is_none() && self.os_id.is_none() {
debug!("Open called with no file link or unique id...");
return Err(ShmemError::NoLinkOrOsId);
}
let mut flink_uid = String::new();
let mut retry = 0;
loop {
let unique_id = if let Some(ref unique_id) = self.os_id {
retry = 5;
unique_id.as_str()
} else {
let flink_path = self.flink_path.as_ref().unwrap();
debug!(
"Open shared memory from file link {}",
flink_path.to_string_lossy()
);
let mut f = match File::open(flink_path) {
Ok(f) => f,
Err(e) => return Err(ShmemError::LinkOpenFailed(e)),
};
flink_uid.clear();
if let Err(e) = f.read_to_string(&mut flink_uid) {
return Err(ShmemError::LinkReadFailed(e));
}
flink_uid.as_str()
};
match os_impl::open_mapping(unique_id, self.size, &self.ext) {
Ok(m) => {
self.size = m.map_size;
self.owner = false;
return Ok(Shmem {
config: self,
mapping: m,
});
}
// If we got this failing os_id from the flink, try again in case the shmem owner didnt write the full
// unique_id to the file
Err(ShmemError::MapOpenFailed(_)) if self.os_id.is_none() && retry < 5 => {
retry += 1;
std::thread::sleep(std::time::Duration::from_millis(50));
}
Err(e) => return Err(e),
}
}
}
}
/// Structure used to extract information from an existing shared memory mapping
pub struct Shmem {
config: ShmemConf,
mapping: os_impl::MapData,
}
#[allow(clippy::len_without_is_empty)]
impl Shmem {
/// Returns whether we created the mapping or not
pub fn is_owner(&self) -> bool {
self.config.owner
}
/// Allows for gaining/releasing ownership of the mapping
///
/// Warning : You must ensure at least one process owns the mapping in order to ensure proper cleanup code is ran
pub fn set_owner(&mut self, is_owner: bool) -> bool {
self.mapping.set_owner(is_owner);
let prev_val = self.config.owner;
self.config.owner = is_owner;
prev_val
}
/// Returns the OS unique identifier for the mapping
pub fn get_os_id(&self) -> &str {
self.mapping.unique_id.as_str()
}
/// Returns the flink path if present
pub fn get_flink_path(&self) -> Option<&PathBuf> {
self.config.flink_path.as_ref()
}
/// Returns the total size of the mapping
pub fn len(&self) -> usize {
self.mapping.map_size
}
/// Returns a raw pointer to the mapping
pub fn as_ptr(&self) -> *mut u8 {
self.mapping.as_mut_ptr()
}
/// Returns mapping as a byte slice
/// # Safety
/// This function is unsafe because it is impossible to ensure the range of bytes is immutable
pub unsafe fn as_slice(&self) -> &[u8] {
std::slice::from_raw_parts(self.as_ptr(), self.len())
}
/// Returns mapping as a mutable byte slice
/// # Safety
/// This function is unsafe because it is impossible to ensure the returned mutable refence is unique/exclusive
pub unsafe fn as_slice_mut(&mut self) -> &mut [u8] {
std::slice::from_raw_parts_mut(self.as_ptr(), self.len())
}
}

217
vendor/shared_memory/src/unix.rs vendored Normal file
View File

@ -0,0 +1,217 @@
use std::os::unix::io::RawFd;
use std::ptr::null_mut;
use crate::log::*;
use nix::fcntl::OFlag;
use nix::sys::mman::{mmap, munmap, shm_open, shm_unlink, MapFlags, ProtFlags};
use nix::sys::stat::{fstat, Mode};
use nix::unistd::{close, ftruncate};
use crate::ShmemError;
#[derive(Clone, Default)]
pub struct ShmemConfExt;
pub struct MapData {
//On linux, you must shm_unlink() the object created for the mapping. It wont disappear automatically.
owner: bool,
//File descriptor to our open mapping
map_fd: RawFd,
//Shared mapping uid
pub unique_id: String,
//Total size of the mapping
pub map_size: usize,
//Pointer to the first address of our mapping
pub map_ptr: *mut u8,
}
impl MapData {
pub fn as_mut_ptr(&self) -> *mut u8 {
self.map_ptr
}
}
/// Shared memory teardown for linux
impl Drop for MapData {
///Takes care of properly closing the SharedMem (munmap(), shmem_unlink(), close())
fn drop(&mut self) {
//Unmap memory
if !self.map_ptr.is_null() {
trace!(
"munmap(map_ptr:{:p},map_size:{})",
self.map_ptr,
self.map_size
);
if let Err(_e) = unsafe { munmap(self.map_ptr as *mut _, self.map_size) } {
debug!("Failed to munmap() shared memory mapping : {}", _e);
};
}
//Unlink shmem
if self.map_fd != 0 {
//unlink shmem if we created it
if self.owner {
debug!("Deleting persistent mapping");
trace!("shm_unlink({})", self.unique_id.as_str());
if let Err(_e) = shm_unlink(self.unique_id.as_str()) {
debug!("Failed to shm_unlink() shared memory : {}", _e);
};
}
trace!("close({})", self.map_fd);
if let Err(_e) = close(self.map_fd) {
debug!(
"os_impl::Linux : Failed to close() shared memory file descriptor : {}",
_e
);
};
}
}
}
impl MapData {
pub fn set_owner(&mut self, is_owner: bool) -> bool {
let prev_val = self.owner;
self.owner = is_owner;
prev_val
}
}
/// Creates a mapping specified by the uid and size
pub fn create_mapping(unique_id: &str, map_size: usize) -> Result<MapData, ShmemError> {
//Create shared memory file descriptor
debug!("Creating persistent mapping at {}", unique_id);
let shmem_fd = match shm_open(
unique_id, //Unique name that usualy pops up in /dev/shm/
OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_RDWR, //create exclusively (error if collision) and read/write to allow resize
Mode::S_IRUSR | Mode::S_IWUSR, //Permission allow user+rw
) {
Ok(v) => {
trace!(
"shm_open({}, {:X}, {:X}) == {}",
unique_id,
OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_RDWR,
Mode::S_IRUSR | Mode::S_IWUSR,
v
);
v
}
Err(nix::Error::EEXIST) => return Err(ShmemError::MappingIdExists),
Err(e) => return Err(ShmemError::MapCreateFailed(e as u32)),
};
let mut new_map: MapData = MapData {
owner: true,
unique_id: String::from(unique_id),
map_fd: shmem_fd,
map_size,
map_ptr: null_mut(),
};
//Enlarge the memory descriptor file size to the requested map size
debug!("Creating memory mapping");
trace!("ftruncate({}, {})", new_map.map_fd, new_map.map_size);
match ftruncate(new_map.map_fd, new_map.map_size as _) {
Ok(_) => {}
Err(e) => return Err(ShmemError::UnknownOsError(e as u32)),
};
//Put the mapping in our address space
debug!("Loading mapping into address space");
new_map.map_ptr = match unsafe {
mmap(
null_mut(), //Desired addr
new_map.map_size, //size of mapping
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, //Permissions on pages
MapFlags::MAP_SHARED, //What kind of mapping
new_map.map_fd, //fd
0, //Offset into fd
)
} {
Ok(v) => {
trace!(
"mmap(NULL, {}, {:X}, {:X}, {}, 0) == {:p}",
new_map.map_size,
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
MapFlags::MAP_SHARED,
new_map.map_fd,
v
);
v as *mut _
}
Err(e) => return Err(ShmemError::MapCreateFailed(e as u32)),
};
Ok(new_map)
}
/// Opens an existing mapping specified by its uid
pub fn open_mapping(
unique_id: &str,
_map_size: usize,
_ext: &ShmemConfExt,
) -> Result<MapData, ShmemError> {
//Open shared memory
debug!("Openning persistent mapping at {}", unique_id);
let shmem_fd = match shm_open(
unique_id,
OFlag::O_RDWR, //Open read write
Mode::S_IRUSR,
) {
Ok(v) => {
trace!(
"shm_open({}, {:X}, {:X}) == {}",
unique_id,
OFlag::O_RDWR,
Mode::S_IRUSR,
v
);
v
}
Err(e) => return Err(ShmemError::MapOpenFailed(e as u32)),
};
let mut new_map: MapData = MapData {
owner: false,
unique_id: String::from(unique_id),
map_fd: shmem_fd,
map_size: 0,
map_ptr: null_mut(),
};
//Get mmap size
new_map.map_size = match fstat(new_map.map_fd) {
Ok(v) => v.st_size as usize,
Err(e) => return Err(ShmemError::MapOpenFailed(e as u32)),
};
//Map memory into our address space
debug!("Loading mapping into address space");
new_map.map_ptr = match unsafe {
mmap(
null_mut(), //Desired addr
new_map.map_size, //size of mapping
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, //Permissions on pages
MapFlags::MAP_SHARED, //What kind of mapping
new_map.map_fd, //fd
0, //Offset into fd
)
} {
Ok(v) => {
trace!(
"mmap(NULL, {}, {:X}, {:X}, {}, 0) == {:p}",
new_map.map_size,
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
MapFlags::MAP_SHARED,
new_map.map_fd,
v
);
v as *mut _
}
Err(e) => return Err(ShmemError::MapOpenFailed(e as u32)),
};
Ok(new_map)
}

281
vendor/shared_memory/src/windows.rs vendored Normal file
View File

@ -0,0 +1,281 @@
use std::fs::{File, OpenOptions};
use std::io::ErrorKind;
use std::os::windows::{fs::OpenOptionsExt, io::AsRawHandle};
use std::path::PathBuf;
use crate::{log::*, ShmemConf};
use win_sys::*;
use crate::ShmemError;
#[derive(Clone, Default)]
pub struct ShmemConfExt {
allow_raw: bool,
}
impl ShmemConf {
/// If set to true, enables openning raw shared memory that is not managed by this crate
pub fn allow_raw(mut self, allow: bool) -> Self {
self.ext.allow_raw = allow;
self
}
}
pub struct MapData {
owner: bool,
/// Pointer to the first byte of our mapping
/// Keep this above `file_map` so it gets dropped first
pub view: ViewOfFile,
/// The handle to our open mapping
#[allow(dead_code)]
file_map: FileMapping,
/// This file is used for shmem persistence. When an owner wants to drop the mapping,
/// it opens the file with FILE_FLAG_DELETE_ON_CLOSE, renames the file and closes it.
/// This makes it so future calls to open the old mapping will fail (as it was renamed) and
/// deletes the renamed file once all handles have been closed.
#[allow(dead_code)]
persistent_file: Option<File>,
//Shared mapping uid
pub unique_id: String,
//Total size of the mapping
pub map_size: usize,
}
///Teardown UnmapViewOfFile and close CreateMapping handle
impl Drop for MapData {
///Takes care of properly closing the SharedMem
fn drop(&mut self) {
// Inspired by the boost implementation at
// https://github.com/boostorg/interprocess/blob/140b50efb3281fa3898f3a4cf939cfbda174718f/include/boost/interprocess/detail/win32_api.hpp
// Emulate POSIX behavior by
// 1. Opening the mmapped file with `FILE_FLAG_DELETE_ON_CLOSE`, causing it to be
// deleted when all its handles have been closed.
// 2. Renaming the mmapped file to prevent future access/opening.
// Once this has run, existing file/mapping handles remain usable but the file is
// deleted once all handles have been closed and no new handles can be opened
// because the file has been renamed. This matches the behavior of shm_unlink()
// on unix.
if self.owner {
let mut base_path = get_tmp_dir().unwrap();
// 1. Set file attributes so that it deletes itself once everyone has closed it
let file_path = base_path.join(self.unique_id.trim_start_matches('/'));
debug!("Setting mapping to delete after everyone has closed it");
match OpenOptions::new()
.access_mode(GENERIC_READ | GENERIC_WRITE | DELETE)
.share_mode((FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE).0)
.create(false)
.attributes((FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE).0)
.open(&file_path)
{
Ok(_) => {
// 2. Rename file to prevent further use
base_path.push(&format!(
"{}_deleted",
self.unique_id.trim_start_matches('/')
));
debug!(
"Renaming {} to {}",
file_path.to_string_lossy(),
base_path.to_string_lossy()
);
if let Err(_e) = std::fs::rename(&file_path, &base_path) {
debug!(
"Failed to rename persistent_file {} : {}",
file_path.to_string_lossy(),
_e
);
}
}
Err(_e) => {
debug!(
"Failed to set DELETE_ON_CLOSE on persistent_file {} : {}",
file_path.to_string_lossy(),
_e
);
}
};
}
}
}
impl MapData {
pub fn set_owner(&mut self, is_owner: bool) -> bool {
let prev_val = self.owner;
self.owner = is_owner;
prev_val
}
pub fn as_mut_ptr(&self) -> *mut u8 {
self.view.as_mut_ptr() as _
}
}
/// Returns the path to a temporary directory in which to store files backing the shared memory. If it
/// doesn't exist, the directory is created.
fn get_tmp_dir() -> Result<PathBuf, ShmemError> {
debug!("Getting & creating shared_memory-rs temp dir");
let mut path = std::env::temp_dir();
path.push("shared_memory-rs");
if path.is_dir() {
return Ok(path);
}
match std::fs::create_dir_all(path.as_path()) {
Ok(_) => Ok(path),
Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(path),
Err(e) => Err(ShmemError::UnknownOsError(e.raw_os_error().unwrap() as _)),
}
}
fn new_map(
unique_id: &str,
mut map_size: usize,
create: bool,
allow_raw: bool,
) -> Result<MapData, ShmemError> {
// Create file to back the shared memory
let mut file_path = get_tmp_dir()?;
file_path.push(unique_id.trim_start_matches('/'));
debug!(
"{} persistent_file at {}",
if create { "Creating" } else { "Openning" },
file_path.to_string_lossy()
);
let mut opt = OpenOptions::new();
opt.read(true)
.write(true)
.share_mode((FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE).0)
.attributes((FILE_ATTRIBUTE_TEMPORARY).0);
if create {
opt.create_new(true);
} else {
opt.create(false);
};
let mut persistent_file = None;
let map_h = match opt.open(&file_path) {
Ok(f) => {
//Create/open Mapping using persistent file
debug!(
"{} memory mapping",
if create { "Creating" } else { "Openning" },
);
let high_size: u32 = ((map_size as u64 & 0xFFFF_FFFF_0000_0000_u64) >> 32) as u32;
let low_size: u32 = (map_size as u64 & 0xFFFF_FFFF_u64) as u32;
trace!(
"CreateFileMapping({:?}, NULL, {:X}, {}, {}, '{}')",
HANDLE(f.as_raw_handle() as _),
PAGE_READWRITE.0,
high_size,
low_size,
unique_id,
);
match CreateFileMapping(
HANDLE(f.as_raw_handle() as _),
None,
PAGE_READWRITE,
high_size,
low_size,
unique_id,
) {
Ok(v) => {
persistent_file = Some(f);
v
}
Err(e) => {
let err_code = e.win32_error().unwrap();
return if err_code == ERROR_ALREADY_EXISTS {
Err(ShmemError::MappingIdExists)
} else {
Err(if create {
ShmemError::MapCreateFailed(err_code.0)
} else {
ShmemError::MapOpenFailed(err_code.0)
})
};
}
}
}
Err(e) if e.kind() == ErrorKind::AlreadyExists => return Err(ShmemError::MappingIdExists),
Err(e) => {
if create {
return Err(ShmemError::MapCreateFailed(e.raw_os_error().unwrap() as _));
} else if !allow_raw {
return Err(ShmemError::MapOpenFailed(ERROR_FILE_NOT_FOUND.0));
}
// This may be a mapping that isnt managed by this crate
// Try to open the mapping without any backing file
trace!(
"OpenFileMappingW({:?}, {}, '{}')",
FILE_MAP_ALL_ACCESS,
false,
unique_id,
);
match OpenFileMapping(FILE_MAP_ALL_ACCESS, false, unique_id) {
Ok(h) => h,
Err(e) => {
return Err(ShmemError::MapOpenFailed(e.win32_error().unwrap().0));
}
}
}
};
trace!("0x{:X}", map_h);
//Map mapping into address space
debug!("Loading mapping into address space");
trace!(
"MapViewOfFile(0x{:X}, {:X}, 0, 0, 0)",
map_h,
(FILE_MAP_READ | FILE_MAP_WRITE).0,
);
let map_ptr = match MapViewOfFile(map_h.as_handle(), FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0) {
Ok(v) => v,
Err(e) => {
return Err(if create {
ShmemError::MapCreateFailed(e.win32_error().unwrap().0)
} else {
ShmemError::MapOpenFailed(e.win32_error().unwrap().0)
})
}
};
trace!("\t{:p}", map_ptr);
if !create {
//Get the real size of the openned mapping
let mut info = MEMORY_BASIC_INFORMATION::default();
if let Err(e) = VirtualQuery(map_ptr.as_mut_ptr(), &mut info) {
return Err(ShmemError::UnknownOsError(e.win32_error().unwrap().0));
}
map_size = info.RegionSize;
}
Ok(MapData {
owner: create,
file_map: map_h,
persistent_file,
unique_id: unique_id.to_string(),
map_size,
view: map_ptr,
})
}
//Creates a mapping specified by the uid and size
pub fn create_mapping(unique_id: &str, map_size: usize) -> Result<MapData, ShmemError> {
new_map(unique_id, map_size, true, false)
}
//Opens an existing mapping specified by its uid
pub fn open_mapping(
unique_id: &str,
map_size: usize,
ext: &ShmemConfExt,
) -> Result<MapData, ShmemError> {
new_map(unique_id, map_size, false, ext.allow_raw)
}

117
vendor/shared_memory/tests/general.rs vendored Normal file
View File

@ -0,0 +1,117 @@
use std::path::Path;
use shared_memory::ShmemConf;
#[test]
fn create_new() {
let mut s = ShmemConf::new().size(4090).create().unwrap();
assert!(s.is_owner());
assert!(!s.get_os_id().is_empty());
assert!(s.len() >= 4090);
assert!(!s.as_ptr().is_null());
unsafe {
assert_eq!(s.as_slice().len(), s.len());
assert_eq!(s.as_slice_mut().len(), s.len());
}
}
#[test]
fn create_with_flink() {
let flink = Path::new("create_new1");
let mut s = ShmemConf::new().flink(flink).size(4090).create().unwrap();
assert!(s.is_owner());
assert!(!s.get_os_id().is_empty());
assert!(flink.is_file());
assert!(s.len() >= 4090);
assert!(!s.as_ptr().is_null());
unsafe {
assert_eq!(s.as_slice().len(), s.len());
assert_eq!(s.as_slice_mut().len(), s.len());
}
drop(s);
assert!(!flink.is_file());
}
#[test]
fn open_os_id() {
let s1 = ShmemConf::new().size(4090).create().unwrap();
// Open with the unique os id
let os_id = s1.get_os_id().to_string();
let mut s2 = ShmemConf::new().os_id(&os_id).open().unwrap();
assert!(!s2.is_owner());
assert!(!s2.get_os_id().is_empty());
assert!(s2.len() >= 4090);
assert!(!s2.as_ptr().is_null());
unsafe {
assert_eq!(s2.as_slice().len(), s2.len());
assert_eq!(s2.as_slice_mut().len(), s2.len());
}
// Drop the owner of the mapping
drop(s1);
// Make sure it can be openned again
assert!(ShmemConf::new().os_id(&os_id).open().is_err());
drop(s2);
}
#[test]
fn open_flink() {
let flink = Path::new("create_new2");
let s1 = ShmemConf::new().flink(flink).size(4090).create().unwrap();
// Open with file base link
let mut s2 = ShmemConf::new().flink(&flink).open().unwrap();
assert!(!s2.is_owner());
assert!(!s2.get_os_id().is_empty());
assert!(flink.is_file());
assert!(s2.len() >= 4090);
assert!(!s2.as_ptr().is_null());
unsafe {
assert_eq!(s2.as_slice().len(), s2.len());
assert_eq!(s2.as_slice_mut().len(), s2.len());
}
// Drop the owner of the mapping
drop(s1);
// Make sure it can be openned again
assert!(ShmemConf::new().flink(&flink).open().is_err());
drop(s2);
}
#[test]
fn share_data() {
let s1 = ShmemConf::new()
.size(core::mem::size_of::<u32>())
.create()
.unwrap();
// Open with the unique os id
let os_id = s1.get_os_id().to_string();
let s2 = ShmemConf::new().os_id(&os_id).open().unwrap();
let ptr1 = s1.as_ptr() as *mut u32;
let ptr2 = s2.as_ptr() as *mut u32;
// Confirm that the two pointers are different
assert_ne!(ptr1, ptr2);
// Write a value from s1 and read it from s2
unsafe {
let shared_val = 0xBADC0FEE;
ptr1.write_volatile(shared_val);
let read_val = ptr2.read_volatile();
assert_eq!(read_val, shared_val);
}
}

View File

@ -0,0 +1,90 @@
use shared_memory::ShmemConf;
use std::sync::mpsc::channel;
use std::thread;
#[test]
fn persistence() {
let os_id = {
let mut shmem = ShmemConf::new().size(4096).create().unwrap();
shmem.set_owner(false);
String::from(shmem.get_os_id())
};
let mut shmem = ShmemConf::new().os_id(os_id).open().unwrap();
shmem.set_owner(true);
}
#[test]
fn posix_behavior() {
let (tx_a, rx_a) = channel();
let (tx_b, rx_b) = channel();
let (tx_c, rx_c) = channel();
let thread_a = thread::Builder::new()
.name(String::from("A"))
.spawn(move || {
let os_id = {
let shmem = ShmemConf::new().size(4096).create().unwrap();
let os_id = String::from(shmem.get_os_id());
// Creating two `Shmem`s with the same `os_id` should fail
assert!(ShmemConf::new().size(4096).os_id(&os_id).create().is_err());
tx_b.send(os_id.clone()).unwrap();
tx_c.send(os_id.clone()).unwrap();
// Wait for threads B and C to confirm they have created their instances.
rx_a.recv().unwrap();
rx_a.recv().unwrap();
// Tell thread B to drop its instance.
tx_b.send(String::new()).unwrap();
os_id
// Owned shmem drops here after a second owned instance has been
// dropped in thread B.
};
// Should not be able to reopen shared memory after an owned instance
// has been dropped in thread B.
assert!(ShmemConf::new().size(4096).os_id(os_id).open().is_err());
// Tell thread C to drop the unowned instance.
tx_c.send(String::new()).unwrap();
})
.unwrap();
let thread_b = thread::Builder::new()
.name(String::from("B"))
.spawn({
let tx_a = tx_a.clone();
move || {
let existing_os_id = rx_b.recv().unwrap();
// Creating two `Shmem`s with the same `os_id` should fail
assert!(ShmemConf::new()
.size(4096)
.os_id(&existing_os_id)
.create()
.is_err());
{
// Should be able to open the existing shared memory
let mut shmem = ShmemConf::new().os_id(&existing_os_id).open().unwrap();
shmem.set_owner(true);
tx_a.send(String::new()).unwrap();
rx_b.recv().unwrap();
// When the owning shmem is dropped here, we
// 1. should be able to still drop the original shared memory in thread A.
// 2. should not be able to reopen it with the same name in thread A, even
// if an instance is kept alive in thread C.
}
}
})
.unwrap();
let thread_c = thread::Builder::new()
.name(String::from("C"))
.spawn(move || {
// This thread keeps a shared memory instance alive until it's told to
// drop it.
let existing_os_id = rx_c.recv().unwrap();
let _shmem = ShmemConf::new().os_id(&existing_os_id).open().unwrap();
// Indicate to thread A that the instance has been created.
tx_a.send(String::new()).unwrap();
// Shut down signal.
rx_c.recv().unwrap();
})
.unwrap();
thread_a.join().unwrap();
thread_b.join().unwrap();
thread_c.join().unwrap();
}