From b521debbb77dc1d8adca5e1196a220549c7708a8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Jun 2021 15:23:41 +0200 Subject: [PATCH] [yabridgectl] Determine libyabridge-vst3.so arch We're going to need this for supporting merged VST3 bundles with a 32-bit libyabridge-vst3.so. --- tools/yabridgectl/src/actions.rs | 8 +++---- tools/yabridgectl/src/config.rs | 21 +++++++++++++---- tools/yabridgectl/src/utils.rs | 40 ++++++++++++++++++++++++++++++-- 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 523d9e86..8366324c 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -108,7 +108,7 @@ pub fn show_status(config: &Config) -> Result<()> { "libyabridge-vst3.so: {}\n", files .libyabridge_vst3 - .map(|path| format!("'{}'", path.display())) + .map(|(path, arch)| format!("'{}' ({})", path.display(), arch)) .unwrap_or_else(|| "".red().to_string()) ); } @@ -204,11 +204,11 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { let files: YabridgeFiles = config.files()?; let libyabridge_vst2_hash = utils::hash_file(&files.libyabridge_vst2)?; let libyabridge_vst3_hash = match &files.libyabridge_vst3 { - Some(path) => Some(utils::hash_file(path)?), + Some((path, _)) => Some(utils::hash_file(path)?), None => None, }; - if let Some(libyabridge_vst3_path) = &files.libyabridge_vst3 { + if let Some((libyabridge_vst3_path, _)) = &files.libyabridge_vst3 { println!("Setting up VST2 and VST3 plugins using:"); println!("- {}", files.libyabridge_vst2.display()); println!("- {}\n", libyabridge_vst3_path.display()); @@ -308,7 +308,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { if install_file( options.force, config.method, - files.libyabridge_vst3.as_ref().unwrap(), + &files.libyabridge_vst3.as_ref().unwrap().0, libyabridge_vst3_hash, &native_module_path, )? || *updated_libyabridge diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index 6e05e347..28c8757f 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -27,7 +27,8 @@ use std::path::{Path, PathBuf}; use which::which; use xdg::BaseDirectories; -use crate::files::{self, SearchResults}; +use crate::files::{self, LibArchitecture, SearchResults}; +use crate::utils; /// The name of the config file, relative to `$XDG_CONFIG_HOME/YABRIDGECTL_PREFIX`. pub const CONFIG_FILE_NAME: &str = "config.toml"; @@ -143,8 +144,9 @@ pub struct YabridgeFiles { /// The path to `libyabridge-vst2.so` we should use. pub libyabridge_vst2: PathBuf, /// The path to `libyabridge-vst3.so` we should use, if yabridge has been compiled with VST3 - /// support. - pub libyabridge_vst3: Option, + /// support. We need to know if it's a 32-bit or a 64-bit library so we can properly set up the + /// merged VST3 bundles. + pub libyabridge_vst3: Option<(PathBuf, LibArchitecture)>, /// The path to `yabridge-host.exe`. This is the path yabridge will actually use, and it does /// not have to be relative to `yabridge_home`. pub yabridge_host_exe: PathBuf, @@ -260,7 +262,18 @@ impl Config { // Based on that we can check if `libyabridge-vst3.so` exists, since yabridge can be // compiled without VST3 support let libyabridge_vst3 = match libyabridge_vst2.with_file_name(LIBYABRIDGE_VST3_NAME) { - path if path.exists() => Some(path), + path if path.exists() => { + // We need to know `libyabridge-vst3.so`'s architecture to be able to set up the + // bundle properly + let arch = utils::get_elf_architecture(&path).with_context(|| { + format!( + "Could not determine ELF architecture for '{}'", + path.display() + ) + })?; + + Some((path, arch)) + } _ => None, }; diff --git a/tools/yabridgectl/src/utils.rs b/tools/yabridgectl/src/utils.rs index dbcc5455..69cca610 100644 --- a/tools/yabridgectl/src/utils.rs +++ b/tools/yabridgectl/src/utils.rs @@ -16,13 +16,14 @@ //! Small helper utilities. -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use colored::Colorize; use is_executable::IsExecutable; use std::collections::hash_map::DefaultHasher; use std::env; use std::fs; use std::hash::Hasher; +use std::io::{Read, Seek, SeekFrom}; use std::os::unix::fs as unix_fs; use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; @@ -30,7 +31,7 @@ use std::process::{Command, Stdio}; use textwrap::Wrapper; use crate::config::{self, Config, KnownConfig, YABRIDGE_HOST_EXE_NAME}; -use crate::files::NativeFile; +use crate::files::{LibArchitecture, NativeFile}; /// (Part of) the expected output when running `yabridge-host.exe`. Used to verify that everything's /// working correctly. We'll only match this prefix so we can modify the exact output at a later @@ -86,6 +87,41 @@ pub fn symlink, Q: AsRef>(src: P, dst: Q) -> Result<()> { }) } +/// Get the architecture of the ELF file at `path`. This detection is a bit naive, but we'd rather +/// not depend on `libmagic` or `libreadelf` just for this, since encountering a 32-bit yabridge +/// library is going to be incredibly rare. +/// +/// This is based on this file header specification: +/// https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header +pub fn get_elf_architecture(path: &Path) -> Result { + // We'll assume `path` points to an ELF file and immediately skip to the good stuff + let mut file = fs::File::open(path)?; + + // I doubt yabridge will ever run on a Big-Endian machine until audio production on Windows on + // ARM also becomes big, but we still need to account for this + let little_endian = { + let mut endianness_bytes = [0u8; 1]; + file.seek(SeekFrom::Start(0x05))?; // e_ident[EI_DATA], 1 byte + file.read_exact(&mut endianness_bytes)?; + endianness_bytes[0] == 1 + }; + + let mut machine_arch_bytes = [0u8; 2]; + file.seek(SeekFrom::Start(0x12))?; // e_machine, 2 bytes + file.read_exact(&mut machine_arch_bytes)?; + + let machine_arch = if little_endian { + u16::from_le_bytes(machine_arch_bytes) + } else { + u16::from_be_bytes(machine_arch_bytes) + }; + match machine_arch { + 0x03 => Ok(LibArchitecture::Lib32), // x86 + 0x3E => Ok(LibArchitecture::Lib64), // AMD x86-64 + _ => Err(anyhow!("'{}' is not a recognized ELF machine ISA")), + } +} + /// Get the type of a file, if it exists. pub fn get_file_type(path: PathBuf) -> Option { match path.symlink_metadata() {