1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
//! Bindings for libc fts.

use libc::*;

/// struct FTS in fts.h ( opaque struct )
pub enum FTS {}

/// struct FTSENT in fts.h
#[repr(C)]
#[derive(Debug)]
pub struct FTSENT {
    /// cycle node
    pub fts_cycle  : *const FTSENT,
    /// parent directory
    pub fts_parent : *const FTSENT,
    /// next file in directory
    pub fts_link   : *const FTSENT,
    /// local numeric value
    pub fts_number : c_long       ,
    /// local address value
    pub fts_pointer: *const c_void,
    /// access path
    pub fts_accpath: *const c_char,
    /// root path
    pub fts_path   : *const c_char,
    /// errno for this node
    pub fts_errno  : c_int        ,
    /// fd for symlink
    pub fts_symfd  : c_int        ,
    /// strlen(fts_path)
    pub fts_pathlen: c_ushort     ,
    /// strlen(fts_name)
    pub fts_namelen: c_ushort     ,
    /// inode
    pub fts_ino    : ino_t        ,
    /// device
    pub fts_dev    : dev_t        ,
    /// link count
    pub fts_nlink  : nlink_t      ,
    /// depth (-1 to N)
    pub fts_level  : c_short      ,
    /// user flags for FTSENT structure
    pub fts_info   : c_ushort     ,
    /// private flags for FTSENT structure
    pub fts_flags  : c_ushort     ,
    /// fts_set() instructions
    pub fts_instr  : c_ushort     ,
    /// stat(2) information
    pub fts_statp  : *const stat  ,
    /// file name
    pub fts_name   : [u8;0]   ,
}

/// fts_level: level of root parent
pub const FTS_ROOTPARENTLEVEL: c_short  = -1    ;
/// fts_level: level of root
pub const FTS_ROOTLEVEL      : c_short  = 0     ;
/// fts_info: preorder directory
pub const FTS_D              : c_ushort = 1     ;
/// fts_info: directory that causes cycles
pub const FTS_DC             : c_ushort = 2     ;
/// fts_info: none of the above
pub const FTS_DEFAULT        : c_ushort = 3     ;
/// fts_info: unreadable directory
pub const FTS_DNR            : c_ushort = 4     ;
/// fts_info: dot or dot-dot
pub const FTS_DOT            : c_ushort = 5     ;
/// fts_info: postorder directory
pub const FTS_DP             : c_ushort = 6     ;
/// fts_info: error; errno is set
pub const FTS_ERR            : c_ushort = 7     ;
/// fts_info: regular file
pub const FTS_F              : c_ushort = 8     ;
/// fts_info: initialized only
pub const FTS_INIT           : c_ushort = 9     ;
/// fts_info: stat(2) failed
pub const FTS_NS             : c_ushort = 10    ;
/// fts_info: no stat(2) requested
pub const FTS_NSOK           : c_ushort = 11    ;
/// fts_info: symbolic link
pub const FTS_SL             : c_ushort = 12    ;
/// fts_info: symbolic link without target
pub const FTS_SLNONE         : c_ushort = 13    ;
/// fts_info: whiteout object
pub const FTS_W              : c_ushort = 14    ;
/// fts_flags: don't chdir .. to the parent
pub const FTS_DONTCHDIR      : c_ushort = 0x01  ;
/// fts_flags: followed a symlink to get here
pub const FTS_SYMFOLLOW      : c_ushort = 0x02  ;
/// fts_instr: read node again
pub const FTS_AGAIN          : c_int    = 1     ;
/// fts_instr: follow symbolic link
pub const FTS_FOLLOW         : c_int    = 2     ;
/// fts_instr: no instructions
pub const FTS_NOINSTR        : c_int    = 3     ;
/// fts_instr: discard node
pub const FTS_SKIP           : c_int    = 4     ;
/// fts_open options: follow command line symlinks
pub const FTS_COMFOLLOW      : c_int    = 0x0001;
/// fts_open options: logical walk
pub const FTS_LOGICAL        : c_int    = 0x0002;
/// fts_open options: don't change directories
pub const FTS_NOCHDIR        : c_int    = 0x0004;
/// fts_open options: don't get stat info
pub const FTS_NOSTAT         : c_int    = 0x0008;
/// fts_open options: physical walk
pub const FTS_PHYSICAL       : c_int    = 0x0010;
/// fts_open options: return dot and dot-dot
pub const FTS_SEEDOT         : c_int    = 0x0020;
/// fts_open options: don't cross devices
pub const FTS_XDEV           : c_int    = 0x0040;
/// fts_open options: return whiteout information
pub const FTS_WHITEOUT       : c_int    = 0x0080;
/// fts_open options: valid user option mask
pub const FTS_OPTIONMASK     : c_int    = 0x00ff;
/// fts_open options: (private) child names only
pub const FTS_NAMEONLY       : c_int    = 0x0100;
/// fts_open options: (private) unrecoverable error
pub const FTS_STOP           : c_int    = 0x0200;

extern {
    /// fts_open() in fts.h
    ///
    /// # C function
    /// ```c
    /// FTS *fts_open(char * const *path_argv, int options, 
    ///               int (*compar)(const FTSENT **, const FTSENT **));
    /// ```
    ///
    /// # Safety
    /// `path_argv` must be a pointer of a null-terminated array of C strings( null-terminated ).
    ///
    /// `options` must contain either `FTS_LOGICAL` or `FTS_PHYSICAL`.
    ///
    /// # Examples
    /// ```
    /// let path  = std::ffi::CString::new( "." ).unwrap().as_ptr();
    /// let paths = vec![path, std::ptr::null()];
    /// let _fts  = unsafe { fts::ffi::fts_open( paths.as_ptr(), fts::ffi::FTS_LOGICAL, None ) };
    /// ```
    #[cfg_attr(target_os = "macos", link_name = "fts_open$INODE64")]
    pub fn fts_open( path_argv: *const *const c_char,
                     options  : c_int,
                     compar   : Option<extern "C" fn( *const *const FTSENT, *const *const FTSENT ) -> c_int> ) -> *mut FTS;

    /// fts_read() in fts.h
    ///
    /// # C function
    /// ```c
    /// FTSENT *fts_read(FTS *ftsp);
    /// ```
    ///
    /// # Safety
    /// `ftsp` must be a valid pointer of struct FTS.
    ///
    /// # Examples
    /// ```
    /// let path    = std::ffi::CString::new( "." ).unwrap().as_ptr();
    /// let paths   = vec![path, std::ptr::null()];
    /// let fts     = unsafe { fts::ffi::fts_open( paths.as_ptr(), fts::ffi::FTS_LOGICAL, None ) };
    /// let _ftsent = unsafe { fts::ffi::fts_read( fts ) };
    /// ```
    #[cfg_attr(target_os = "macos", link_name = "fts_read$INODE64")]
    pub fn fts_read( ftsp: *mut FTS ) -> *const FTSENT;

    /// fts_children() in fts.h
    ///
    /// # C function
    /// ```c
    /// FTSENT *fts_children(FTS *ftsp, int options);
    /// ```
    ///
    /// # Safety
    /// `ftsp` must be a valid pointer of struct FTS.
    ///
    /// `options` must be either 0 or `FTS_NAMEONLY`.
    ///
    /// # Examples
    /// ```
    /// let path    = std::ffi::CString::new( "." ).unwrap().as_ptr();
    /// let paths   = vec![path, std::ptr::null()];
    /// let fts     = unsafe { fts::ffi::fts_open( paths.as_ptr(), fts::ffi::FTS_LOGICAL, None ) };
    /// let _ftsent = unsafe { fts::ffi::fts_children( fts, 0 ) };
    /// ```
    #[cfg_attr(target_os = "macos", link_name = "fts_children$INODE64")]
    pub fn fts_children( ftsp: *mut FTS, options: c_int ) -> *const FTSENT;

    /// fts_set() in fts.h
    ///
    /// # C function
    /// ```c
    /// int fts_set(FTS *ftsp, FTSENT *f, int options);
    /// ```
    ///
    /// # Safety
    /// `ftsp` must be a valid pointer of struct FTS.
    ///
    /// `f` must be a valid pointer of struct FTSENT.
    ///
    /// `options` must be `FTS_AGAIN`, `FTS_FOLLOW` or `FTS_SKIP`.
    ///
    /// # Examples
    /// ```
    /// let path   = std::ffi::CString::new( "." ).unwrap().as_ptr();
    /// let paths  = vec![path, std::ptr::null()];
    /// let fts    = unsafe { fts::ffi::fts_open( paths.as_ptr(), fts::ffi::FTS_LOGICAL, None ) };
    /// let ftsent = unsafe { fts::ffi::fts_read( fts ) };
    /// let _      = unsafe { fts::ffi::fts_set( fts, ftsent, fts::ffi::FTS_AGAIN ) };
    /// ```
    #[cfg_attr(target_os = "macos", link_name = "fts_set$INODE64")]
    pub fn fts_set( ftsp: *mut FTS, f: *const FTSENT, options: c_int ) -> c_int;

    /// fts_close() in fts.h
    ///
    /// # C function
    /// ```c
    /// int fts_close(FTS *ftsp);
    /// ```
    ///
    /// # Safety
    /// `ftsp` must be a valid pointer of struct FTS.
    ///
    /// # Examples
    /// ```
    /// let path   = std::ffi::CString::new( "." ).unwrap().as_ptr();
    /// let paths  = vec![path, std::ptr::null()];
    /// let fts    = unsafe { fts::ffi::fts_open( paths.as_ptr(), fts::ffi::FTS_LOGICAL, None ) };
    /// let _      = unsafe { fts::ffi::fts_close( fts ) };
    /// ```
    #[cfg_attr(target_os = "macos", link_name = "fts_close$INODE64")]
    pub fn fts_close( ftsp: *mut FTS ) -> c_int;
}

#[cfg(test)]
mod test {
    use super::*;
    use std::ffi::CString;
    use std::ptr;

    fn ftsent_valid( ftsent: *const FTSENT ) {
        unsafe {
            assert!( !ftsent               .is_null() );
            assert!( !(*ftsent).fts_accpath.is_null() );
            assert!( !(*ftsent).fts_path   .is_null() );
            assert!(  (*ftsent).fts_pathlen != 0      );
            assert!(  (*ftsent).fts_namelen != 0      );
            assert!(  (*ftsent).fts_level   >= -1     );
            assert!(  (*ftsent).fts_number  == 0      );
            assert!(  (*ftsent).fts_pointer.is_null() );
            assert!( !(*ftsent).fts_parent .is_null() );
            assert!( !(*ftsent).fts_statp  .is_null() );
        }
    }

    #[test]
    fn normal() {
        unsafe {
            let path  = CString::new( "." ).unwrap().as_ptr();
            let paths = vec![path, ptr::null()];
            let fts   = fts_open( paths.as_ptr(), FTS_LOGICAL, None );
            assert!( !fts.is_null() );

            let mut ftsent = fts_read( fts );
            while !ftsent.is_null() {
                ftsent_valid( ftsent );
                ftsent = fts_read( fts );
            }

            let ret = fts_close( fts );
            assert!( ret == 0 );
        }
    }

    #[test]
    fn children() {
        unsafe {
            let path  = CString::new( "." ).unwrap().as_ptr();
            let paths = vec![path, ptr::null()];
            let fts   = fts_open( paths.as_ptr(), FTS_LOGICAL, None );
            assert!( !fts.is_null() );

            let _ = fts_read( fts );
            let mut ftsent = fts_children( fts, 0 );
            while !ftsent.is_null() {
                ftsent_valid( ftsent );
                ftsent = (*ftsent).fts_link;
            }

            let ret = fts_close( fts );
            assert!( ret == 0 );
        }
    }
}