fix #3699: client: prefer xdg cache directory for tmp files

Adds a helper to create temporal files in XDG_CACHE_HOME. If we cannot
create a file there, we fallback to /tmp as before.

Note that the temporary files stored by the client might grow
arbitrarily in size, making XDG_RUNTIME_DIR a less desirable option.
Citing the Arch wiki [1]:

> Should not store large files as it may be mounted as a tmpfs.

While the cache directory is most often not backed up by an ephemeral
FS, using the `O_TMPFILE` flag avoids the need for potential cleanup,
e.g. on interruption of a command. As with this flag set the data will
be discarded when the last file descriptor is closed.

[1] https://wiki.archlinux.org/title/XDG_Base_Directory

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
 [ TL: mention TMPFILE flag for clarity ]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Maximiliano Sandoval 2024-07-17 12:18:35 +02:00 committed by Thomas Lamprecht
parent 2ecdbe9a96
commit fcccc3dfa5
4 changed files with 40 additions and 49 deletions

View File

@ -1,7 +1,6 @@
use anyhow::{format_err, Error}; use anyhow::{format_err, Error};
use std::fs::File; use std::fs::File;
use std::io::{Seek, SeekFrom, Write}; use std::io::{Seek, SeekFrom, Write};
use std::os::unix::fs::OpenOptionsExt;
use std::sync::Arc; use std::sync::Arc;
use futures::future::AbortHandle; use futures::future::AbortHandle;
@ -141,18 +140,14 @@ impl BackupReader {
/// Download a .blob file /// Download a .blob file
/// ///
/// This creates a temporary file in /tmp (using O_TMPFILE). The data is verified using /// This creates a temporary file (See [`crate::tools::create_tmp_file`] for
/// the provided manifest. /// details). The data is verified using the provided manifest.
pub async fn download_blob( pub async fn download_blob(
&self, &self,
manifest: &BackupManifest, manifest: &BackupManifest,
name: &str, name: &str,
) -> Result<DataBlobReader<'_, File>, Error> { ) -> Result<DataBlobReader<'_, File>, Error> {
let mut tmpfile = std::fs::OpenOptions::new() let mut tmpfile = crate::tools::create_tmp_file()?;
.write(true)
.read(true)
.custom_flags(libc::O_TMPFILE)
.open("/tmp")?;
self.download(name, &mut tmpfile).await?; self.download(name, &mut tmpfile).await?;
@ -167,18 +162,14 @@ impl BackupReader {
/// Download dynamic index file /// Download dynamic index file
/// ///
/// This creates a temporary file in /tmp (using O_TMPFILE). The index is verified using /// This creates a temporary file (See [`crate::tools::create_tmp_file`] for
/// the provided manifest. /// details). The index is verified using the provided manifest.
pub async fn download_dynamic_index( pub async fn download_dynamic_index(
&self, &self,
manifest: &BackupManifest, manifest: &BackupManifest,
name: &str, name: &str,
) -> Result<DynamicIndexReader, Error> { ) -> Result<DynamicIndexReader, Error> {
let mut tmpfile = std::fs::OpenOptions::new() let mut tmpfile = crate::tools::create_tmp_file()?;
.write(true)
.read(true)
.custom_flags(libc::O_TMPFILE)
.open("/tmp")?;
self.download(name, &mut tmpfile).await?; self.download(name, &mut tmpfile).await?;
@ -194,18 +185,14 @@ impl BackupReader {
/// Download fixed index file /// Download fixed index file
/// ///
/// This creates a temporary file in /tmp (using O_TMPFILE). The index is verified using /// This creates a temporary file (See [`crate::tools::create_tmp_file`] for
/// the provided manifest. /// details). The index is verified using the provided manifest.
pub async fn download_fixed_index( pub async fn download_fixed_index(
&self, &self,
manifest: &BackupManifest, manifest: &BackupManifest,
name: &str, name: &str,
) -> Result<FixedIndexReader, Error> { ) -> Result<FixedIndexReader, Error> {
let mut tmpfile = std::fs::OpenOptions::new() let mut tmpfile = crate::tools::create_tmp_file()?;
.write(true)
.read(true)
.custom_flags(libc::O_TMPFILE)
.open("/tmp")?;
self.download(name, &mut tmpfile).await?; self.download(name, &mut tmpfile).await?;

View File

@ -1,6 +1,5 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::future::Future; use std::future::Future;
use std::os::unix::fs::OpenOptionsExt;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -531,11 +530,7 @@ impl BackupWriter {
manifest: &BackupManifest, manifest: &BackupManifest,
known_chunks: Arc<Mutex<HashSet<[u8; 32]>>>, known_chunks: Arc<Mutex<HashSet<[u8; 32]>>>,
) -> Result<FixedIndexReader, Error> { ) -> Result<FixedIndexReader, Error> {
let mut tmpfile = std::fs::OpenOptions::new() let mut tmpfile = crate::tools::create_tmp_file()?;
.write(true)
.read(true)
.custom_flags(libc::O_TMPFILE)
.open("/tmp")?;
let param = json!({ "archive-name": archive_name }); let param = json!({ "archive-name": archive_name });
self.h2 self.h2
@ -570,11 +565,7 @@ impl BackupWriter {
manifest: &BackupManifest, manifest: &BackupManifest,
known_chunks: Arc<Mutex<HashSet<[u8; 32]>>>, known_chunks: Arc<Mutex<HashSet<[u8; 32]>>>,
) -> Result<DynamicIndexReader, Error> { ) -> Result<DynamicIndexReader, Error> {
let mut tmpfile = std::fs::OpenOptions::new() let mut tmpfile = crate::tools::create_tmp_file()?;
.write(true)
.read(true)
.custom_flags(libc::O_TMPFILE)
.open("/tmp")?;
let param = json!({ "archive-name": archive_name }); let param = json!({ "archive-name": archive_name });
self.h2 self.h2

View File

@ -5,9 +5,11 @@ use std::ffi::OsStr;
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::FromRawFd; use std::os::unix::io::FromRawFd;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::sync::OnceLock;
use anyhow::{bail, format_err, Context, Error}; use anyhow::{bail, format_err, Context, Error};
use serde_json::{json, Value}; use serde_json::{json, Value};
@ -736,3 +738,27 @@ pub async fn pxar_metadata_catalog_lookup<T: Clone + ReadAt>(
Ok(entries) Ok(entries)
} }
/// Creates a temporary file (with `O_TMPFILE`) in `XDG_CACHE_HOME`. If we
/// cannot create the file there it will be created in `/tmp` instead.
pub fn create_tmp_file() -> std::io::Result<std::fs::File> {
static TMP_PATH: OnceLock<std::path::PathBuf> = OnceLock::new();
let tmp_path = TMP_PATH.get_or_init(|| {
xdg::BaseDirectories::new()
.map(|base| base.get_cache_home())
.unwrap_or_else(|_| std::path::PathBuf::from("/tmp"))
});
let mut open_opts_binding = std::fs::OpenOptions::new();
let builder = open_opts_binding
.write(true)
.read(true)
.custom_flags(libc::O_TMPFILE);
builder.open(tmp_path).or_else(|err| {
if tmp_path != std::path::Path::new("/tmp") {
builder.open("/tmp")
} else {
Err(err)
}
})
}

View File

@ -1,5 +1,4 @@
use std::io::{Seek, SeekFrom}; use std::io::{Seek, SeekFrom};
use std::os::unix::fs::OpenOptionsExt;
use std::sync::Arc; use std::sync::Arc;
use anyhow::{bail, format_err, Error}; use anyhow::{bail, format_err, Error};
@ -106,11 +105,7 @@ async fn dump_catalog(param: Value) -> Result<Value, Error> {
let mut reader = BufferedDynamicReader::new(index, chunk_reader); let mut reader = BufferedDynamicReader::new(index, chunk_reader);
let mut catalogfile = std::fs::OpenOptions::new() let mut catalogfile = pbs_client::tools::create_tmp_file()?;
.write(true)
.read(true)
.custom_flags(libc::O_TMPFILE)
.open("/tmp")?;
std::io::copy(&mut reader, &mut catalogfile) std::io::copy(&mut reader, &mut catalogfile)
.map_err(|err| format_err!("unable to download catalog - {}", err))?; .map_err(|err| format_err!("unable to download catalog - {}", err))?;
@ -198,11 +193,7 @@ async fn catalog_shell(param: Value) -> Result<(), Error> {
) )
.await?; .await?;
let mut tmpfile = std::fs::OpenOptions::new() let mut tmpfile = pbs_client::tools::create_tmp_file()?;
.write(true)
.read(true)
.custom_flags(libc::O_TMPFILE)
.open("/tmp")?;
let (manifest, _) = client.download_manifest().await?; let (manifest, _) = client.download_manifest().await?;
manifest.check_fingerprint(crypt_config.as_ref().map(Arc::as_ref))?; manifest.check_fingerprint(crypt_config.as_ref().map(Arc::as_ref))?;
@ -233,11 +224,7 @@ async fn catalog_shell(param: Value) -> Result<(), Error> {
most_used, most_used,
); );
let mut reader = BufferedDynamicReader::new(index, chunk_reader); let mut reader = BufferedDynamicReader::new(index, chunk_reader);
let mut catalogfile = std::fs::OpenOptions::new() let mut catalogfile = pbs_client::tools::create_tmp_file()?;
.write(true)
.read(true)
.custom_flags(libc::O_TMPFILE)
.open("/tmp")?;
std::io::copy(&mut reader, &mut catalogfile) std::io::copy(&mut reader, &mut catalogfile)
.map_err(|err| format_err!("unable to download catalog - {}", err))?; .map_err(|err| format_err!("unable to download catalog - {}", err))?;