Skip to main content

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}