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}