forked from beerpsi/chuniio-yubideck
Initial commit
This commit is contained in:
commit
d247e838b4
121
.gitignore
vendored
Normal file
121
.gitignore
vendored
Normal 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
8
.idea/.gitignore
vendored
Normal 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
|
11
.idea/chuniio-yubideck.iml
Normal file
11
.idea/chuniio-yubideck.iml
Normal 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
8
.idea/modules.xml
Normal 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
6
.idea/vcs.xml
Normal 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
27
Cargo.toml
Normal 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
65
README.md
Normal 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
3
rustfmt.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
imports_granularity = "Crate"
|
||||||
|
reorder_imports = true
|
||||||
|
group_imports = "StdExternalCrate"
|
6
src/configuration.rs
Normal file
6
src/configuration.rs
Normal 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
467
src/lib.rs
Normal 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
42
src/log.rs
Normal 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();
|
||||||
|
}
|
11
vendor/shared_memory/.github/dependabot.yml
vendored
Normal file
11
vendor/shared_memory/.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "cargo"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
58
vendor/shared_memory/.github/workflows/rust.yml
vendored
Normal file
58
vendor/shared_memory/.github/workflows/rust.yml
vendored
Normal 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
5
vendor/shared_memory/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
|
.vscode/
|
||||||
|
/.idea
|
42
vendor/shared_memory/Cargo.toml
vendored
Normal file
42
vendor/shared_memory/Cargo.toml
vendored
Normal 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
33
vendor/shared_memory/README.md
vendored
Normal 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
20
vendor/shared_memory/changelog.md
vendored
Normal 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
78
vendor/shared_memory/examples/basic.rs
vendored
Normal 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
36
vendor/shared_memory/examples/event.rs
vendored
Normal 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
117
vendor/shared_memory/examples/mutex.rs
vendored
Normal 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
48
vendor/shared_memory/src/error.rs
vendored
Normal 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
267
vendor/shared_memory/src/lib.rs
vendored
Normal 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
217
vendor/shared_memory/src/unix.rs
vendored
Normal 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
281
vendor/shared_memory/src/windows.rs
vendored
Normal 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
117
vendor/shared_memory/tests/general.rs
vendored
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
90
vendor/shared_memory/tests/posix_semantics.rs
vendored
Normal file
90
vendor/shared_memory/tests/posix_semantics.rs
vendored
Normal 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();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user