sysdir/lib.rs
1// src/lib.rs
2//
3// Copyright (c) 2023 Ryan Lopopolo <rjl@hyperbo.la>
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE> or
6// <http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT>
7// or <http://opensource.org/licenses/MIT>, at your option. All files in the
8// project carrying such notice may not be copied, modified, or distributed
9// except according to those terms.
10
11#![warn(clippy::all)]
12#![warn(clippy::pedantic)]
13#![warn(clippy::cargo)]
14#![allow(unknown_lints)]
15#![warn(missing_copy_implementations)]
16#![warn(missing_debug_implementations)]
17#![warn(missing_docs)]
18#![warn(rust_2018_idioms)]
19#![warn(trivial_casts, trivial_numeric_casts)]
20#![warn(unused_qualifications)]
21#![warn(variant_size_differences)]
22// Enable feature callouts in generated documentation:
23// https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html
24//
25// This approach is borrowed from tokio.
26#![cfg_attr(docsrs, feature(doc_cfg))]
27#![cfg_attr(docsrs, feature(doc_alias))]
28
29//! Enumeration of the filesystem paths for the various standard system
30//! directories where apps, resources, etc. get installed.
31//!
32//! This crate exposes Rust bindings to the `sysdir(3)` library functions
33//! provided by `libSystem.dylib` on macOS, iOS, tvOS, and watchOS.
34//!
35//! For more detailed documentation, refer to the [`sysdir(3)` man page](mod@man).
36//!
37//! # Platform Support
38//!
39//! The `sysdir` API first appeared in OS X 10.12, iOS 10, watchOS 3 and tvOS 10
40//! replacing the deprecated `NSSystemDirectories(3)` API.
41//!
42//! Note that this crate is completely empty on non-Apple platforms.
43//!
44//! ## Linkage
45//!
46//! `sysdir(3)` is provided by `libSystem`, which is linked into every binary on
47//! Apple platforms. This crate does not link to `CoreFoundation`, `Foundation`,
48//! or any other system libraries and frameworks.
49//!
50//! # Examples
51//!
52#![cfg_attr(
53 any(
54 target_os = "macos",
55 target_os = "ios",
56 target_os = "tvos",
57 target_os = "watchos"
58 ),
59 doc = "```"
60)]
61#![cfg_attr(
62 not(any(
63 target_os = "macos",
64 target_os = "ios",
65 target_os = "tvos",
66 target_os = "watchos"
67 )),
68 doc = "```compile_fail"
69)]
70//! use core::ffi::{c_char, CStr};
71//!
72//! use sysdir::*;
73//!
74//! let mut path = [0; PATH_MAX as usize];
75//!
76//! let dir = sysdir_search_path_directory_t::SYSDIR_DIRECTORY_USER;
77//! let domain_mask = SYSDIR_DOMAIN_MASK_LOCAL;
78//!
79//! unsafe {
80//! let mut state = sysdir_start_search_path_enumeration(dir, domain_mask);
81//! loop {
82//! let path = path.as_mut_ptr().cast::<c_char>();
83//! state = sysdir_get_next_search_path_enumeration(state, path);
84//! if state == 0 {
85//! break;
86//! }
87//! let path = CStr::from_ptr(path);
88//! let s = path.to_str().unwrap();
89//! assert_eq!(s, "/Users");
90//! }
91//! }
92//! ```
93
94#![no_std]
95#![doc(html_root_url = "https://docs.rs/sysdir/1.2.2")]
96
97// Ensure code blocks in `README.md` compile
98#[cfg(all(
99 doctest,
100 any(
101 target_os = "macos",
102 target_os = "ios",
103 target_os = "tvos",
104 target_os = "watchos"
105 )
106))]
107#[doc = include_str!("../README.md")]
108mod readme {}
109
110/// man page for `sysdir(3)`.
111///
112/// ```text
113#[doc = include_str!("../sysdir.3")]
114/// ```
115#[cfg(any(doc, doctest))]
116pub mod man {}
117
118/// Raw bindings to `sysdir(3)`, provided by `libSystem`.
119///
120/// The `sysdir` API first appeared in OS X 10.12, iOS 10, watchOS 3 and tvOS 10
121/// replacing the deprecated `NSSystemDirectories(3)` API.
122#[allow(missing_docs)]
123#[allow(non_camel_case_types)]
124#[allow(clippy::all)]
125#[allow(clippy::pedantic)]
126#[allow(clippy::restriction)]
127#[cfg(any(
128 target_os = "macos",
129 target_os = "ios",
130 target_os = "tvos",
131 target_os = "watchos"
132))]
133mod sys;
134
135#[cfg(any(
136 target_os = "macos",
137 target_os = "ios",
138 target_os = "tvos",
139 target_os = "watchos"
140))]
141pub use self::sys::*;
142
143#[cfg(all(
144 test,
145 any(
146 target_os = "macos",
147 target_os = "ios",
148 target_os = "tvos",
149 target_os = "watchos"
150 )
151))]
152mod tests {
153 use core::ffi::{c_char, CStr};
154
155 use super::*;
156
157 // EXAMPLES
158 //
159 // ```c
160 // #include <limits.h>
161 // #include <sysdir.h>
162 //
163 // char path[PATH_MAX];
164 // sysdir_search_path_enumeration_state state = sysdir_start_search_path_enumeration(dir, domainMask);
165 // while ( (state = sysdir_get_next_search_path_enumeration(state, path)) != 0 ) {
166 // // Handle directory path
167 // }
168 // ```
169 #[test]
170 fn example_and_linkage() {
171 let mut count = 0_usize;
172 let mut path = [0; PATH_MAX as usize];
173
174 let dir = sysdir_search_path_directory_t::SYSDIR_DIRECTORY_USER;
175 let domain_mask = SYSDIR_DOMAIN_MASK_LOCAL;
176
177 unsafe {
178 let mut state = sysdir_start_search_path_enumeration(dir, domain_mask);
179 loop {
180 let path = path.as_mut_ptr().cast::<c_char>();
181 state = sysdir_get_next_search_path_enumeration(state, path);
182 if state == 0 {
183 break;
184 }
185 let path = CStr::from_ptr(path);
186 let s = path.to_str().unwrap();
187 assert_eq!(s, "/Users");
188 count += 1;
189 }
190 }
191
192 assert_eq!(count, 1, "Should iterate once and find `/Users`");
193 }
194
195 #[test]
196 fn example_and_linkage_with_opaque_state_helpers() {
197 let mut count = 0_usize;
198 let mut path = [0; PATH_MAX as usize];
199
200 let dir = sysdir_search_path_directory_t::SYSDIR_DIRECTORY_USER;
201 let domain_mask = SYSDIR_DOMAIN_MASK_LOCAL;
202
203 unsafe {
204 let mut state = sysdir_start_search_path_enumeration(dir, domain_mask);
205 loop {
206 let path = path.as_mut_ptr().cast::<c_char>();
207 state = sysdir_get_next_search_path_enumeration(state, path);
208 if state.is_finished() {
209 break;
210 }
211 let path = CStr::from_ptr(path);
212 let s = path.to_str().unwrap();
213 assert_eq!(s, "/Users");
214 count += 1;
215 }
216 }
217
218 assert_eq!(count, 1, "Should iterate once and find `/Users`");
219 }
220}