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
28//! Enumeration of the filesystem paths for the various standard system
29//! directories where apps, resources, etc. get installed.
30//!
31//! This crate exposes Rust bindings to the `sysdir(3)` library functions
32//! provided by `libSystem.dylib` on macOS, iOS, tvOS, and watchOS.
33//!
34//! For more detailed documentation, refer to the [`sysdir(3)` man page](mod@man).
35//!
36//! # Platform Support
37//!
38//! The `sysdir` API first appeared in OS X 10.12, iOS 10, watchOS 3 and tvOS 10
39//! replacing the deprecated `NSSystemDirectories(3)` API.
40//!
41//! Note that this crate is completely empty on non-Apple platforms.
42//!
43//! ## Linkage
44//!
45//! `sysdir(3)` is provided by `libSystem`, which is linked into every binary on
46//! Apple platforms. This crate does not link to `CoreFoundation`, `Foundation`,
47//! or any other system libraries and frameworks.
48//!
49//! # Examples
50//!
51#![cfg_attr(
52 any(
53 target_os = "macos",
54 target_os = "ios",
55 target_os = "tvos",
56 target_os = "watchos"
57 ),
58 doc = "```"
59)]
60#![cfg_attr(
61 not(any(
62 target_os = "macos",
63 target_os = "ios",
64 target_os = "tvos",
65 target_os = "watchos"
66 )),
67 doc = "```compile_fail"
68)]
69//! use core::ffi::{c_char, CStr};
70//!
71//! use sysdir::*;
72//!
73//! let mut path = [0; PATH_MAX as usize];
74//!
75//! let dir = sysdir_search_path_directory_t::SYSDIR_DIRECTORY_USER;
76//! let domain_mask = SYSDIR_DOMAIN_MASK_LOCAL;
77//!
78//! unsafe {
79//! let mut state = sysdir_start_search_path_enumeration(dir, domain_mask);
80//! loop {
81//! let path = path.as_mut_ptr().cast::<c_char>();
82//! state = sysdir_get_next_search_path_enumeration(state, path);
83//! if state == 0 {
84//! break;
85//! }
86//! let path = CStr::from_ptr(path);
87//! let s = path.to_str().unwrap();
88//! assert_eq!(s, "/Users");
89//! }
90//! }
91//! ```
92
93#![no_std]
94#![doc(html_root_url = "https://docs.rs/sysdir/1.3.2")]
95
96// Ensure code blocks in `README.md` compile
97#[cfg(all(
98 doctest,
99 any(
100 target_os = "macos",
101 target_os = "ios",
102 target_os = "tvos",
103 target_os = "watchos"
104 )
105))]
106#[doc = include_str!("../README.md")]
107mod readme {}
108
109/// man page for `sysdir(3)`.
110///
111/// ```text
112#[doc = include_str!("../sysdir.3")]
113/// ```
114#[cfg(any(doc, doctest))]
115pub mod man {}
116
117/// Raw bindings to `sysdir(3)`, provided by `libSystem`.
118///
119/// The `sysdir` API first appeared in OS X 10.12, iOS 10, watchOS 3 and tvOS 10
120/// replacing the deprecated `NSSystemDirectories(3)` API.
121#[allow(missing_docs)]
122#[allow(non_camel_case_types)]
123#[allow(clippy::all)]
124#[allow(clippy::pedantic)]
125#[allow(clippy::restriction)]
126#[cfg(any(
127 target_os = "macos",
128 target_os = "ios",
129 target_os = "tvos",
130 target_os = "watchos"
131))]
132mod sys;
133
134#[cfg(any(
135 target_os = "macos",
136 target_os = "ios",
137 target_os = "tvos",
138 target_os = "watchos"
139))]
140pub use self::sys::*;
141
142#[cfg(all(
143 test,
144 any(
145 target_os = "macos",
146 target_os = "ios",
147 target_os = "tvos",
148 target_os = "watchos"
149 )
150))]
151mod tests {
152 use core::ffi::{CStr, c_char};
153
154 use super::*;
155
156 // EXAMPLES
157 //
158 // ```c
159 // #include <limits.h>
160 // #include <sysdir.h>
161 //
162 // char path[PATH_MAX];
163 // sysdir_search_path_enumeration_state state = sysdir_start_search_path_enumeration(dir, domainMask);
164 // while ( (state = sysdir_get_next_search_path_enumeration(state, path)) != 0 ) {
165 // // Handle directory path
166 // }
167 // ```
168 #[test]
169 fn example_and_linkage() {
170 let mut count = 0_usize;
171 let mut path = [0; PATH_MAX as usize];
172
173 let dir = sysdir_search_path_directory_t::SYSDIR_DIRECTORY_USER;
174 let domain_mask = SYSDIR_DOMAIN_MASK_LOCAL;
175
176 unsafe {
177 let mut state = sysdir_start_search_path_enumeration(dir, domain_mask);
178 loop {
179 let path = path.as_mut_ptr().cast::<c_char>();
180 state = sysdir_get_next_search_path_enumeration(state, path);
181 if state == 0 {
182 break;
183 }
184 let path = CStr::from_ptr(path);
185 let s = path.to_str().unwrap();
186 assert_eq!(s, "/Users");
187 count += 1;
188 }
189 }
190
191 assert_eq!(count, 1, "Should iterate once and find `/Users`");
192 }
193
194 #[test]
195 fn example_and_linkage_with_opaque_state_helpers() {
196 let mut count = 0_usize;
197 let mut path = [0; PATH_MAX as usize];
198
199 let dir = sysdir_search_path_directory_t::SYSDIR_DIRECTORY_USER;
200 let domain_mask = SYSDIR_DOMAIN_MASK_LOCAL;
201
202 unsafe {
203 let mut state = sysdir_start_search_path_enumeration(dir, domain_mask);
204 loop {
205 let path = path.as_mut_ptr().cast::<c_char>();
206 state = sysdir_get_next_search_path_enumeration(state, path);
207 if state.is_finished() {
208 break;
209 }
210 let path = CStr::from_ptr(path);
211 let s = path.to_str().unwrap();
212 assert_eq!(s, "/Users");
213 count += 1;
214 }
215 }
216
217 assert_eq!(count, 1, "Should iterate once and find `/Users`");
218 }
219}