diff --git a/CHANGELOG.md b/CHANGELOG.md index 052a9b47..6869829f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improve error messages generated by the command line - Build with crossterm terminal backend by default +- Move UNIX IPC socket from the user's cache path to the user's runtime directory ### Fixed diff --git a/doc/users.md b/doc/users.md index e39e0592..2d2d5e64 100644 --- a/doc/users.md +++ b/doc/users.md @@ -180,10 +180,12 @@ Note: \ - mandatory arg; [BAR] - optional arg | `save [current]` | Save selected item, if `current` is passed the currently playing item will be saved | ## Remote control (IPC) -Apart from MPRIS, ncspot will also create a domain socket on UNIX platforms -(Linux, macOS, *BSD) at `~/.cache/ncspot/ncspot.sock`. Applications or scripts -can connect to this socket to send commands or be notified of the currently -playing track, i.e. with `netcat`: +Apart from MPRIS, ncspot will also create a domain socket on UNIX platforms (Linux, macOS, *BSD). +The socket will be created in the platform's runtime directory. If XDG_RUNTIME_DIR is set, it will +be created under `$XDG_RUNTIME_DIR/ncspot`. If XDG_RUNTIME_DIR isn't set, it will be created under +`/run/user/` for Linux if it exists. In all other cases, it will be created under +`/tmp/ncspot-`. Applications or scripts can connect to this socket to send commands or be +notified of the currently playing track, i.e. with `netcat`: ``` % nc -U ~/.cache/ncspot/ncspot.sock diff --git a/src/application.rs b/src/application.rs index 8b671210..872d7f74 100644 --- a/src/application.rs +++ b/src/application.rs @@ -17,7 +17,7 @@ use crate::library::Library; use crate::queue::Queue; use crate::spotify::{PlayerEvent, Spotify}; use crate::ui::create_cursive; -use crate::{authentication, ui}; +use crate::{authentication, ui, utils}; use crate::{command, queue, spotify}; #[cfg(feature = "mpris")] @@ -140,7 +140,9 @@ impl Application { #[cfg(unix)] let ipc = ipc::IpcSocket::new( ASYNC_RUNTIME.get().unwrap().handle(), - crate::config::cache_path("ncspot.sock"), + utils::create_runtime_directory() + .unwrap() + .join("ncspot.sock"), event_manager.clone(), ) .map_err(|e| e.to_string())?; diff --git a/src/utils.rs b/src/utils.rs index e4ae725b..bacc963f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] -use std::fmt::Write; +use std::{fmt::Write, path::PathBuf}; /// Returns a human readable String of a Duration /// @@ -59,3 +59,54 @@ pub fn download(url: String, path: std::path::PathBuf) -> Result<(), std::io::Er std::io::copy(&mut resp, &mut file)?; Ok(()) } + +/// Create the application specific runtime directory and return the path to it. +/// +/// If the directory already exists and has the correct permissions, this function just returns the +/// existing directory. The contents stored in this directory are not necessarily persisted across +/// reboots. Stored files should be small since they could reside in memory (like on a tmpfs mount). +#[cfg(unix)] +pub fn create_runtime_directory() -> Result> { + use std::{ + fs::{self, Permissions}, + os::unix::prelude::PermissionsExt, + }; + + let linux_runtime_directory = + PathBuf::from(format!("/run/user/{}/", unsafe { libc::getuid() })); + let unix_runtime_directory = PathBuf::from("/tmp/"); + + let user_runtime_directory = if let Some(xdg_runtime_directory) = xdg_runtime_directory() { + Some(xdg_runtime_directory.join("ncspot")) + } else if cfg!(linux) && linux_runtime_directory.exists() { + Some(linux_runtime_directory.join("ncspot")) + } else if unix_runtime_directory.exists() { + Some(unix_runtime_directory.join(format!("ncspot-{}", unsafe { libc::getuid() }))) + } else { + None + } + .ok_or("no runtime directory found")?; + + let creation_result = fs::create_dir(&user_runtime_directory); + + if creation_result.is_ok() + || matches!( + creation_result.as_ref().unwrap_err().kind(), + std::io::ErrorKind::AlreadyExists + ) + { + // Needed when created inside a world readable directory, to prevent unauthorized access. + // Doesn't hurt otherwise. + fs::set_permissions(&user_runtime_directory, Permissions::from_mode(0o700))?; + + Ok(user_runtime_directory) + } else { + #[allow(clippy::unnecessary_unwrap)] + Err(Box::new(creation_result.unwrap_err())) + } +} + +#[cfg(unix)] +fn xdg_runtime_directory() -> Option { + std::env::var("XDG_RUNTIME_DIR").ok().map(Into::into) +}