Skip to content
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
fcd04d6
Revert "Rollup merge of #157518 - CAD97:xdg_basedir, r=aapoalas"
CAD97 Jun 22, 2026
46652a0
add fs::UserDirs
CAD97 Jul 7, 2026
9a2a784
add unix fs::UserDirsExt
CAD97 Jul 7, 2026
0870d94
add windows fs::UserDirsExt
CAD97 Jul 7, 2026
177dcf1
add darwin fs::UserDirsExt
CAD97 Jul 7, 2026
04e6a34
remove impl Default for UserDirs
CAD97 Jul 7, 2026
44d77cf
add UserDirs::user_home
CAD97 Jul 7, 2026
e224808
order UserDirs fields more consistently
CAD97 Jul 7, 2026
e3ddb27
minor fixes
CAD97 Jul 8, 2026
fa9eb89
use libc for darwin sysdir(3)
CAD97 Jul 8, 2026
d48fa46
skip can_fetch_xdg_user_dirs test on darwin
CAD97 Jul 8, 2026
230559c
minor fixes
CAD97 Jul 8, 2026
c8f118a
minor fixes
CAD97 Jul 8, 2026
82aa5c3
allow missing user-dirs.dirs for test
CAD97 Jul 9, 2026
2f3bec4
fully cfg gate darwin UserDirsExt
CAD97 Jul 9, 2026
1e279da
add missing cast
CAD97 Jul 9, 2026
5c65239
fix doc links
CAD97 Jul 9, 2026
58c5d24
fix broken doclinks
CAD97 Jul 9, 2026
cb80d92
fix doclinks
CAD97 Jul 9, 2026
73d28c3
make doc not look like broken doclink
CAD97 Jul 9, 2026
b33e3c8
refactor darwin UserDirsExt
CAD97 Jul 13, 2026
4caf68d
use objc names in darwin UserDirsExt docs
CAD97 Jul 13, 2026
0640b15
add more UserDirs docs
CAD97 Jul 13, 2026
6a6b33f
add user_home to UserDirs tests
CAD97 Jul 13, 2026
6a73485
fix darwin sysdir iter
CAD97 Jul 13, 2026
46ea015
fix darwin UserDirsExt typo
CAD97 Jul 13, 2026
76ddaa3
split std:;fs::UserDirs
CAD97 Jul 14, 2026
c39364a
fix darwin fs::dirs typo
CAD97 Jul 14, 2026
9bd1cdc
fix imports
CAD97 Jul 14, 2026
d453917
refactor xdg unquote
CAD97 Jul 14, 2026
2e7a433
remove commented shlex dependency
CAD97 Jul 14, 2026
5bf2537
fix doclinks
CAD97 Jul 14, 2026
0eb2d6d
fix remaining UserDirs references
CAD97 Jul 14, 2026
90f7788
fix renames
CAD97 Jul 14, 2026
fa28a6a
fixes
CAD97 Jul 14, 2026
9098b46
fix
CAD97 Jul 14, 2026
74ae37e
fix imports
CAD97 Jul 14, 2026
058b060
fix wasi pal
CAD97 Jul 14, 2026
dcb34b6
maybe fix import warning
CAD97 Jul 14, 2026
2f81f4b
unix doesnt mean cfg(unix)
CAD97 Jul 14, 2026
b8dccd8
wasi no fs?
CAD97 Jul 14, 2026
3a0106d
used or unused make up your mind
CAD97 Jul 15, 2026
ce2ad68
handle wasi right?
CAD97 Jul 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions library/std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ path = "../windows_link"
rand = { version = "0.9.0", default-features = false, features = ["alloc"] }
rand_xorshift = "0.4.0"

# [target.'cfg(unix)'.dependencies]
# shlex = { version = "1.3.0", default-features = false, features = [
# "rustc-dep-of-std", # allowlisted but missing rustc-dep-of-std feature
# ] }

[target.'cfg(any(all(target_family = "wasm", not(any(unix, target_os = "wasi"))), target_os = "xous", target_os = "vexos", all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies]
dlmalloc = { version = "0.2.10", features = ['rustc-dep-of-std'] }

Expand Down
1 change: 0 additions & 1 deletion library/std/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,6 @@ impl Error for JoinPathsError {
/// For example, [XDG Base Directories] on Unix or the `LOCALAPPDATA` and `APPDATA` environment variables on Windows.
///
/// [XDG Base Directories]: https://specifications.freedesktop.org/basedir-spec/latest/
// feature(xdg_basedir): This should link to std::os::unix::xdg once it's stabilized
///
/// # Unix
///
Expand Down
5 changes: 5 additions & 0 deletions library/std/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner, fs as fs_imp};
use crate::time::SystemTime;
use crate::{error, fmt};

pub(crate) mod dirs;

#[unstable(feature = "dir_discovery", issue = "157515")]
pub use self::dirs::UserDirs;

/// An object providing access to an open file on the filesystem.
///
/// An instance of a `File` can be read and/or written depending on what options
Expand Down
598 changes: 598 additions & 0 deletions library/std/src/fs/dirs.rs

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions library/std/src/os/darwin/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ use crate::fs::{self, Metadata};
use crate::sys::{AsInner, AsInnerMut, IntoInner};
use crate::time::SystemTime;

mod dirs;

#[unstable(feature = "dir_discovery", issue = "157515")]
pub use self::dirs::UserDirsExt;

/// OS-specific extensions to [`fs::Metadata`].
///
/// [`fs::Metadata`]: crate::fs::Metadata
Expand Down
266 changes: 266 additions & 0 deletions library/std/src/os/darwin/fs/dirs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
use crate::ffi::{CStr, c_char};
use crate::fs::UserDirs;
use crate::io::{self, ErrorKind, const_error};
use crate::path::{Path, PathBuf};

trait Sealed {}
impl Sealed for UserDirs {}

/// Darwin-specific extensions to [`fs::UserDirs`](UserDirs).
#[unstable(feature = "dir_discovery", issue = "157515")]
#[expect(private_bounds, reason = "sealed")]
pub trait UserDirsExt: Sized + Sealed {
/// Load the standard user directory paths for the current user.
///
/// On iOS, tvOS, watchOS, visionOS, and sandboxed macOS applications,
/// [`cache_home`], [`config_home`], [`data_home`], and [`state_home`]
/// are within the application's sandbox bundle. Outside the sandbox,
/// these are subdirectories of the `~/Library` directory on macOS.
///
/// The produced directory paths are not guaranteed to be the canonical
/// paths to the directories; they are allowed to be sandbox-redirected
/// paths as long as the user directory is accessible there.
///
/// The loaded common directories are:
///
/// | `UserDirs` | [`NSSearchPathDirectory`] |
/// | ---------- | ----------------------- |
/// | [`cache_home`] | [`NSCachesDirectory`] (`~/Library/Caches`) |
/// | [`config_home`] | [`NSApplicationSupportDirectory`] (`~/Library/Application Support`) |
/// | [`data_home`] | [`NSApplicationSupportDirectory`] (`~/Library/Application Support`) |
/// | [`state_home`] | [`NSApplicationSupportDirectory`] (`~/Library/Application Support`) |
/// | [`desktop`] | [`NSDesktopDirectory`] (`~/Desktop`) |
/// | [`documents`] | [`NSDocumentDirectory`] (`~/Documents`) |
/// | [`downloads`] | [`NSDownloadsDirectory`] |
/// | [`music`] | [`NSMusicDirectory`] (`~/Music`) |
/// | [`pictures`] | [`NSPicturesDirectory`] (`~/Pictures`) |
/// | [`public_share`] | [`NSSharedPublicDirectory`] (`~/Public`) |
/// | [`videos`] | [`NSMoviesDirectory`] (`~/Movies`) |
///
/// Note that the Application Support directory is used for the config,
/// data, and state directories. It is always possible for multiple user
/// directories to be configured to the same path, but this is the common
/// configuration on Apple platforms, making it even more important to not
/// assume files in different user directories cannot alias each other.
///
/// # Errors
///
/// Errors if the underlying system directory discovery API returns
/// multiple directories for a queried standard directory, if a
/// directory path is not valid Unicode, or if the [user home](UserDirs::user_home)
/// cannot be determined.
///
/// # Implementation-specific behavior
///
/// Uses the `sysdir(3)` API from `libSystem` to discover the standard
/// user directories. Iterates each of `SYSDIR_DIRECTORY_DOCUMENT`,
/// `SYSDIR_DIRECTORY_DESKTOP`, `SYSDIR_DIRECTORY_CACHES`,
/// `SYSDIR_DIRECTORY_APPLICATION_SUPPORT`, `SYSDIR_DIRECTORY_DOWNLOADS`,
/// `SYSDIR_DIRECTORY_MOVIES`, `SYSDIR_DIRECTORY_MUSIC`,
/// `SYSDIR_DIRECTORY_PICTURES`, `SYSDIR_DIRECTORY_SHARED_PUBLIC` once.
///
/// This behavior may change in the future. One example change that we
/// explicitly reserve the right to make is to load additional common
/// directories not currently in this list.
///
/// [`cache_home`]: UserDirs::cache_home
/// [`config_home`]: UserDirs::config_home
/// [`data_home`]: UserDirs::data_home
/// [`state_home`]: UserDirs::state_home
/// [`desktop`]: UserDirs::desktop
/// [`documents`]: UserDirs::documents
/// [`downloads`]: UserDirs::downloads
/// [`music`]: UserDirs::music
/// [`pictures`]: UserDirs::pictures
/// [`public_share`]: UserDirs::public_share
/// [`videos`]: UserDirs::videos
///
/// [`NSSearchPathDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory?language=objc
/// [`NSCachesDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/cachesdirectory?language=objc
/// [`NSApplicationSupportDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/applicationsupportdirectory?language=objc
/// [`NSDesktopDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/desktopdirectory?language=objc
/// [`NSDocumentDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/documentdirectory?language=objc
/// [`NSDownloadsDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/downloadsdirectory?language=objc
/// [`NSMusicDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/musicdirectory?language=objc
/// [`NSPicturesDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/picturesdirectory?language=objc
/// [`NSSharedPublicDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/sharedpublicdirectory?language=objc
/// [`NSMoviesDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/moviesdirectory?language=objc
#[unstable(feature = "dir_discovery", issue = "157515")]
fn sysdir() -> io::Result<Self>;
}

#[unstable(feature = "dir_discovery", issue = "157515")]
#[cfg(target_vendor = "apple")]
impl UserDirsExt for UserDirs {
fn sysdir() -> io::Result<Self> {
use libc::sysdir_search_path_directory_t::*;

let mut dirs = UserDirs::new();
let home =
dirs.user_home().ok_or(const_error!(ErrorKind::NotFound, "no home directory"))?;

let caches = sys::get_user_dir(&home, SYSDIR_DIRECTORY_CACHES)?;
let application_support = sys::get_user_dir(&home, SYSDIR_DIRECTORY_APPLICATION_SUPPORT)?;
let desktop = sys::get_user_dir(&home, SYSDIR_DIRECTORY_DESKTOP)?;
let documents = sys::get_user_dir(&home, SYSDIR_DIRECTORY_DOCUMENT)?;
let downloads = sys::get_user_dir(&home, SYSDIR_DIRECTORY_DOWNLOADS)?;
let movies = sys::get_user_dir(&home, SYSDIR_DIRECTORY_MOVIES)?;
let music = sys::get_user_dir(&home, SYSDIR_DIRECTORY_MUSIC)?;
let pictures = sys::get_user_dir(&home, SYSDIR_DIRECTORY_PICTURES)?;
let public_share = sys::get_user_dir(&home, SYSDIR_DIRECTORY_SHARED_PUBLIC)?;

dirs.home.cache = caches;
// Apple puts config/data/state all in Application Support
dirs.home.config = application_support.clone();
dirs.home.data = application_support.clone();
dirs.home.state = application_support;

dirs.media.desktop = desktop;
dirs.media.documents = documents;
dirs.media.downloads = downloads;
dirs.media.music = music;
dirs.media.pictures = pictures;
dirs.media.public_share = public_share;
dirs.media.videos = movies;

Ok(dirs)
}
}

/// Safer wrapper around the sysdir(3) API
#[cfg(target_vendor = "apple")]
mod sys {
use crate::io::{self, ErrorKind, const_error};
use crate::path::{Path, PathBuf};

/// Get the path for a system directory using `sysdir(3)`.
pub fn get_user_dir(
home: &Path,
kind: libc::sysdir_search_path_directory_t,
) -> io::Result<Option<PathBuf>> {
use libc::sysdir_search_path_domain_mask_t::SYSDIR_DOMAIN_MASK_USER;

// SAFETY: SYSDIR_DOMAIN_MASK_USER < SYSDIR_DOMAIN_MASK_ALL
let mut iter = unsafe { Iter::new(home, kind, SYSDIR_DOMAIN_MASK_USER) };
let Some(path) = iter.next() else {
return Ok(None);
};

if iter.next().is_some() {
// more than one path returned (shouldn't happen for SYSDIR_DOMAIN_MASK_USER)
return Err(const_error!(
ErrorKind::InvalidData,
"multiple paths returned for standard user directory",
));
}

Ok(Some(path?))
}

struct Iter<'a> {
home: &'a Path,
state: libc::sysdir_search_path_enumeration_state,
}

impl Drop for Iter<'_> {
fn drop(&mut self) {
for _ in self {}
}
}

impl<'a> Iter<'a> {
// SAFETY: `mask` must be <= `SYSDIR_DOMAIN_MASK_ALL`

@madsmtm madsmtm Jul 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming, tbqh; the manpage doesn't really specify. Note that here libc incorrectly translates sysdir_search_path_domain_mask_t as an enum when it's a bitmask, so there's no potential for unsafety currently, but I'm being conservative.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did see that libc issue, yeah.

I'm confident that it's not an issue though, the only way I could imagine it would work differently is if they decided in the future to use the rest of the integer for flags, and one of those flags doing something unsound. But that sounds improbable given that we're using the API in exactly the intended / documented fashion.

(It sounds a lot more likely to me that they'd add a new API if they really needed some new functionality).

pub unsafe fn new(
home: &'a Path,
kind: libc::sysdir_search_path_directory_t,
mask: libc::sysdir_search_path_domain_mask_t,
) -> Self {
// SAFETY: forwarded to the caller
let state = unsafe { libc::sysdir_start_search_path_enumeration(kind, mask) };
Self { home, state }
}
}

impl Iterator for Iter<'_> {
type Item = io::Result<PathBuf>;

fn next(&mut self) -> Option<io::Result<PathBuf>> {
let mut buf = [0u8; libc::PATH_MAX as usize];
if self.state != 0 {
// SAFETY: `self.state` is nonzero and comes from prior sysdir_{start|get_next}_search_path_enumeration call
// SAFETY: sysdir_get_next_search_path_enumeration will write at most `PATH_MAX` bytes to `path`
self.state = unsafe {
libc::sysdir_get_next_search_path_enumeration(
self.state,
buf.as_mut_ptr() as *mut libc::c_char,
)
};
}

if self.state == 0 {
// exhausted
return None;
}

let Ok(path) = std::ffi::CStr::from_bytes_until_nul(&buf) else {
// should be impossible given username length limits, but be defensive
return Some(Err(const_error!(
ErrorKind::InvalidData,
"standard user directory path too long",
)));
};

let Ok(path) = path.to_str() else {
// FIXME: is this possible, and if so, how should it be handled?
return Some(Err(const_error!(
ErrorKind::InvalidData,
"standard user directory path not valid UTF-8",
)));
};

// expand `~` shorthand
Some(match path {
"~" => Ok(self.home.into()),
_ if path.starts_with("~/") => Ok(self.home.join(&path[2..])),
_ if path.starts_with("~") => Err(const_error!(
ErrorKind::InvalidData,
"standard user directory relative to different user",
)),
_ => {
let path = PathBuf::from(path);
if path.is_relative() {
Err(const_error!(
ErrorKind::InvalidData,
"standard user directory path not absolute",
))
} else {
Ok(path)
}
}
})
}
}
}

#[cfg(test)]
#[cfg(target_vendor = "apple")]
mod tests {
use super::*;

#[test]
fn can_fetch_sysdir_paths() {
let dirs = UserDirs::sysdir().unwrap();
assert!(dirs.user_home().is_some());
assert!(dirs.cache_home().is_some());
assert!(dirs.config_home().is_some());
assert!(dirs.data_home().is_some());
assert!(dirs.state_home().is_some());
assert!(dirs.desktop().is_some());
assert!(dirs.documents().is_some());
assert!(dirs.downloads().is_some());
assert!(dirs.music().is_some());
assert!(dirs.pictures().is_some());
assert!(dirs.public_share().is_some());
assert!(dirs.videos().is_some());
}
}
5 changes: 5 additions & 0 deletions library/std/src/os/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ use crate::{io, sys};
#[cfg(test)]
mod tests;

mod dirs;

#[unstable(feature = "dir_discovery", issue = "157515")]
pub use self::dirs::UserDirsExt;

/// Unix-specific extensions to [`fs::File`].
#[stable(feature = "file_offset", since = "1.15.0")]
pub trait FileExt {
Expand Down
Loading
Loading