Skip to content

Commit

Permalink
fix!: create IPC socket in user runtime directory (#1313)
Browse files Browse the repository at this point in the history
Each user has their own runtime directory at `/run/user/<uid>`. Creating
the IPC socket in there makes sure it is cleaned up regardless of
whether `ncspot` exits normally.

BREAKING CHANGE: move IPC socket location
  • Loading branch information
ThomasFrans authored Oct 28, 2023
1 parent 2c14883 commit fcf6899
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 6 additions & 4 deletions doc/users.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,12 @@ Note: \<FOO\> - 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/<uid>` for Linux if it exists. In all other cases, it will be created under
`/tmp/ncspot-<uid>`. 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
Expand Down
6 changes: 4 additions & 2 deletions src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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())?;
Expand Down
53 changes: 52 additions & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
@@ -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
///
Expand Down Expand Up @@ -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<PathBuf, Box<dyn std::error::Error>> {
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<PathBuf> {
std::env::var("XDG_RUNTIME_DIR").ok().map(Into::into)
}

0 comments on commit fcf6899

Please sign in to comment.