apr 0.4.5

Rust bindings for Apache Portable Runtime
Documentation
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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
//! File handling
use crate::{pool::Pool, status::Status};
use apr_sys;
use std::io::{Read, Write};
use std::path::Path;

pub use apr_sys::apr_file_t;

/// File open flags
pub struct OpenFlags(i32);

impl OpenFlags {
    /// Open file for reading
    pub const READ: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_READ as i32);
    /// Open file for writing
    pub const WRITE: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_WRITE as i32);
    /// Create file if it doesn't exist
    pub const CREATE: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_CREATE as i32);
    /// Open file in append mode
    pub const APPEND: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_APPEND as i32);
    /// Truncate file if it exists
    pub const TRUNCATE: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_TRUNCATE as i32);
    /// Open file in binary mode
    pub const BINARY: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_BINARY as i32);
    /// Fail if file exists (used with CREATE)
    pub const EXCL: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_EXCL as i32);
    /// Open file with buffering
    pub const BUFFERED: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_BUFFERED as i32);
    /// Delete file when closed
    pub const DELONCLOSE: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_DELONCLOSE as i32);
    /// Platform-dependent thread-safe mode
    pub const XTHREAD: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_XTHREAD as i32);
    /// Platform-dependent shared lock mode
    pub const SHARELOCK: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_SHARELOCK as i32);
    /// Don't register cleanup when pool is destroyed
    pub const NOCLEANUP: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_NOCLEANUP as i32);
    /// Advisory flag for sendfile support
    pub const SENDFILE_ENABLED: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_SENDFILE_ENABLED as i32);
    /// Platform-dependent large file support
    pub const LARGEFILE: OpenFlags = OpenFlags(apr_sys::APR_FOPEN_LARGEFILE as i32);

    /// Combine multiple flags
    pub fn combine(flags: &[OpenFlags]) -> Self {
        let combined = flags.iter().fold(0, |acc, flag| acc | flag.0);
        OpenFlags(combined)
    }
}

/// File permissions
pub type FilePerms = apr_sys::apr_fileperms_t;

/// APR File wrapper providing safe RAII access
#[repr(transparent)]
pub struct File {
    raw: *mut apr_sys::apr_file_t,
    // Files are tied to a pool and are not thread-safe
    _no_send: std::marker::PhantomData<*mut ()>,
}

impl File {
    /// Open a file with specified flags and permissions
    pub fn open<P: AsRef<Path>>(
        path: P,
        flags: OpenFlags,
        perms: FilePerms,
        pool: &Pool<'_>,
    ) -> Result<Self, Status> {
        let path_str = path.as_ref().to_string_lossy();
        let path_cstr = alloc::ffi::CString::new(path_str.as_ref())
            .map_err(|_| Status::from(apr_sys::APR_EINVAL as i32))?;

        let mut file_ptr: *mut apr_sys::apr_file_t = std::ptr::null_mut();
        let status = unsafe {
            apr_sys::apr_file_open(
                &mut file_ptr,
                path_cstr.as_ptr(),
                flags.0,
                perms,
                pool.as_mut_ptr(),
            )
        };

        if status == apr_sys::APR_SUCCESS as i32 {
            Ok(File {
                raw: file_ptr,
                _no_send: std::marker::PhantomData,
            })
        } else {
            Err(Status::from(status))
        }
    }

    /// Create a temporary file
    ///
    /// The temporary file will have secure default permissions (typically 0600).
    pub fn temp_file(template: &str, flags: OpenFlags, pool: &Pool<'_>) -> Result<Self, Status> {
        let template_cstr = alloc::ffi::CString::new(template)
            .map_err(|_| Status::from(apr_sys::APR_EINVAL as i32))?;

        let mut file_ptr: *mut apr_sys::apr_file_t = std::ptr::null_mut();
        let status = unsafe {
            apr_sys::apr_file_mktemp(
                &mut file_ptr,
                template_cstr.as_ptr() as *mut core::ffi::c_char,
                flags.0,
                pool.as_mut_ptr(),
            )
        };

        if status == apr_sys::APR_SUCCESS as i32 {
            Ok(File {
                raw: file_ptr,
                _no_send: std::marker::PhantomData,
            })
        } else {
            Err(Status::from(status))
        }
    }

    /// Get a reference to stdin
    pub fn stdin(pool: &Pool<'_>) -> Result<Self, Status> {
        let mut file_ptr: *mut apr_sys::apr_file_t = std::ptr::null_mut();
        let status = unsafe { apr_sys::apr_file_open_stdin(&mut file_ptr, pool.as_mut_ptr()) };

        if status == apr_sys::APR_SUCCESS as i32 {
            Ok(File {
                raw: file_ptr,
                _no_send: std::marker::PhantomData,
            })
        } else {
            Err(Status::from(status))
        }
    }

    /// Get a reference to stdout
    pub fn stdout(pool: &Pool<'_>) -> Result<Self, Status> {
        let mut file_ptr: *mut apr_sys::apr_file_t = std::ptr::null_mut();
        let status = unsafe { apr_sys::apr_file_open_stdout(&mut file_ptr, pool.as_mut_ptr()) };

        if status == apr_sys::APR_SUCCESS as i32 {
            Ok(File {
                raw: file_ptr,
                _no_send: std::marker::PhantomData,
            })
        } else {
            Err(Status::from(status))
        }
    }

    /// Get a reference to stderr
    pub fn stderr(pool: &Pool<'_>) -> Result<Self, Status> {
        let mut file_ptr: *mut apr_sys::apr_file_t = std::ptr::null_mut();
        let status = unsafe { apr_sys::apr_file_open_stderr(&mut file_ptr, pool.as_mut_ptr()) };

        if status == apr_sys::APR_SUCCESS as i32 {
            Ok(File {
                raw: file_ptr,
                _no_send: std::marker::PhantomData,
            })
        } else {
            Err(Status::from(status))
        }
    }

    /// Get the raw file pointer
    pub fn as_ptr(&self) -> *const apr_sys::apr_file_t {
        self.raw
    }

    /// Get the mutable raw file pointer
    pub fn as_mut_ptr(&self) -> *mut apr_sys::apr_file_t {
        self.raw
    }

    /// Flush any buffered writes
    pub fn flush(&mut self) -> Result<(), Status> {
        let status = unsafe { apr_sys::apr_file_flush(self.raw) };

        if status == apr_sys::APR_SUCCESS as i32 {
            Ok(())
        } else {
            Err(Status::from(status))
        }
    }

    /// Close the file explicitly (automatic on drop)
    pub fn close(mut self) -> Result<(), Status> {
        let status = unsafe { apr_sys::apr_file_close(self.raw) };
        // Prevent double-close in Drop
        self.raw = std::ptr::null_mut();

        if status == apr_sys::APR_SUCCESS as i32 {
            Ok(())
        } else {
            Err(Status::from(status))
        }
    }
}

impl Drop for File {
    fn drop(&mut self) {
        if !self.raw.is_null() {
            unsafe {
                apr_sys::apr_file_close(self.raw);
            }
        }
    }
}

impl Read for File {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        if buf.is_empty() {
            return Ok(0);
        }

        let mut bytes_read: apr_sys::apr_size_t = buf.len() as apr_sys::apr_size_t;
        let status = unsafe {
            apr_sys::apr_file_read(
                self.raw,
                buf.as_mut_ptr() as *mut core::ffi::c_void,
                &mut bytes_read,
            )
        };

        match status as u32 {
            s if s == apr_sys::APR_SUCCESS => Ok(bytes_read as usize),
            s if s == apr_sys::APR_EOF => Ok(0),
            _ => Err(std::io::Error::other(Status::from(status))),
        }
    }
}

impl Write for File {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        if buf.is_empty() {
            return Ok(0);
        }

        let mut bytes_written: apr_sys::apr_size_t = buf.len() as apr_sys::apr_size_t;
        let status = unsafe {
            apr_sys::apr_file_write(
                self.raw,
                buf.as_ptr() as *const core::ffi::c_void,
                &mut bytes_written,
            )
        };

        if status == apr_sys::APR_SUCCESS as i32 {
            Ok(bytes_written as usize)
        } else {
            Err(std::io::Error::other(Status::from(status)))
        }
    }

    fn flush(&mut self) -> std::io::Result<()> {
        self.flush().map_err(std::io::Error::other)
    }
}

/// Builder pattern for File creation with fluent API
pub struct FileBuilder<'a> {
    flags: OpenFlags,
    perms: FilePerms,
    pool: Option<&'a Pool<'a>>,
}

impl<'a> FileBuilder<'a> {
    /// Create a new FileBuilder with default values
    pub fn new() -> Self {
        FileBuilder {
            flags: OpenFlags(0),
            perms: 0o644,
            pool: None,
        }
    }

    /// Set read flag
    pub fn read(mut self) -> Self {
        self.flags = OpenFlags(self.flags.0 | OpenFlags::READ.0);
        self
    }

    /// Set write flag
    pub fn write(mut self) -> Self {
        self.flags = OpenFlags(self.flags.0 | OpenFlags::WRITE.0);
        self
    }

    /// Set create flag
    pub fn create(mut self) -> Self {
        self.flags = OpenFlags(self.flags.0 | OpenFlags::CREATE.0);
        self
    }

    /// Set append flag
    pub fn append(mut self) -> Self {
        self.flags = OpenFlags(self.flags.0 | OpenFlags::APPEND.0);
        self
    }

    /// Set truncate flag
    pub fn truncate(mut self) -> Self {
        self.flags = OpenFlags(self.flags.0 | OpenFlags::TRUNCATE.0);
        self
    }

    /// Set binary flag
    pub fn binary(mut self) -> Self {
        self.flags = OpenFlags(self.flags.0 | OpenFlags::BINARY.0);
        self
    }

    /// Set exclusive flag
    pub fn exclusive(mut self) -> Self {
        self.flags = OpenFlags(self.flags.0 | OpenFlags::EXCL.0);
        self
    }

    /// Set file permissions
    pub fn permissions(mut self, perms: FilePerms) -> Self {
        self.perms = perms;
        self
    }

    /// Set the pool to use (required)
    pub fn pool(mut self, pool: &'a Pool) -> Self {
        self.pool = Some(pool);
        self
    }

    /// Open the file with the configured options
    pub fn open<P: AsRef<Path>>(self, path: P) -> Result<File, Status> {
        let pool = self
            .pool
            .ok_or_else(|| Status::from(apr_sys::APR_EINVAL as i32))?;
        File::open(path, self.flags, self.perms, pool)
    }
}

impl<'a> Default for FileBuilder<'a> {
    fn default() -> Self {
        Self::new()
    }
}

impl File {
    /// Create a FileBuilder for fluent API
    pub fn builder() -> FileBuilder<'static> {
        FileBuilder::new()
    }
}

/// High-level convenience functions for common file operations
pub mod io {
    use super::*;
    use crate::error::{ErrorContext, Result};
    use crate::pool;

    /// Read entire file to string (creates temporary pool internally)
    pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
        let path_ref = path.as_ref();
        pool::with_tmp_pool(|pool| {
            let mut file = File::open(path_ref, OpenFlags::READ, 0, pool)
                .with_context(|| format!("Failed to open file for reading: {:?}", path_ref))?;
            let mut s = String::new();
            file.read_to_string(&mut s)
                .with_context(|| format!("Failed to read file contents: {:?}", path_ref))?;
            Ok(s)
        })
    }

    /// Write string to file (creates temporary pool internally)
    pub fn write<P: AsRef<Path>>(path: P, contents: &str) -> Result<()> {
        let path_ref = path.as_ref();
        pool::with_tmp_pool(|pool| {
            let mut file = File::open(
                path_ref,
                OpenFlags::combine(&[OpenFlags::WRITE, OpenFlags::CREATE, OpenFlags::TRUNCATE]),
                0o644,
                pool,
            )
            .with_context(|| format!("Failed to open file for writing: {:?}", path_ref))?;

            file.write_all(contents.as_bytes())
                .with_context(|| format!("Failed to write to file: {:?}", path_ref))?;
            file.flush()
                .with_context(|| format!("Failed to flush file: {:?}", path_ref))?;
            Ok(())
        })
    }

    /// Copy file from source to destination (creates temporary pool internally)
    pub fn copy<P1: AsRef<Path>, P2: AsRef<Path>>(from: P1, to: P2) -> Result<()> {
        let from_ref = from.as_ref();
        let to_ref = to.as_ref();
        pool::with_tmp_pool(|pool| {
            let mut src = File::open(from_ref, OpenFlags::READ, 0, pool)
                .with_context(|| format!("Failed to open source file: {:?}", from_ref))?;
            let mut dst = File::open(
                to_ref,
                OpenFlags::combine(&[OpenFlags::WRITE, OpenFlags::CREATE, OpenFlags::TRUNCATE]),
                0o644,
                pool,
            )
            .with_context(|| format!("Failed to open destination file: {:?}", to_ref))?;

            std::io::copy(&mut src, &mut dst)
                .with_context(|| format!("Failed to copy from {:?} to {:?}", from_ref, to_ref))?;

            Ok(())
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::{Read, Write};

    #[test]
    fn test_file_open_write_read() {
        let pool = Pool::new();

        let dir = tempfile::tempdir().expect("Failed to create temp dir");
        let temp_path = dir.path().join("apr_test_file");
        let temp_path = temp_path.to_str().unwrap();

        // Write to file
        {
            let mut file = File::open(
                temp_path,
                OpenFlags::combine(&[OpenFlags::WRITE, OpenFlags::CREATE, OpenFlags::TRUNCATE]),
                apr_sys::APR_FPROT_OS_DEFAULT as i32,
                &pool,
            )
            .expect("Failed to open file for writing");

            file.write_all(b"Hello, APR!")
                .expect("Failed to write to file");
            file.flush().expect("Failed to flush file");
        }

        // Read from file
        {
            let mut file = File::open(temp_path, OpenFlags::READ, 0, &pool)
                .expect("Failed to open file for reading");

            let mut buffer = String::new();
            file.read_to_string(&mut buffer)
                .expect("Failed to read from file");
            assert_eq!(buffer, "Hello, APR!");
        }
    }

    #[test]
    fn test_temp_file() {
        let pool = Pool::new();

        let template = std::env::temp_dir().join("apr_temp_XXXXXX");
        let template = template.to_str().unwrap().replace('\\', "/");
        // Pass flags 0 to use APR defaults (CREATE|READ|WRITE|EXCL|DELONCLOSE).
        // Non-zero flags without CREATE fail on Windows where gettemp() passes
        // them directly to apr_file_open.
        let mut temp_file =
            File::temp_file(&template, OpenFlags(0), &pool).expect("Failed to create temp file");

        temp_file
            .write_all(b"Temporary data")
            .expect("Failed to write to temp file");

        // Note: temp file is automatically cleaned up when it goes out of scope
    }

    #[test]
    fn test_file_builder() {
        let pool = Pool::new();

        // Test builder pattern works
        let result = File::builder()
            .read()
            .write()
            .create()
            .truncate()
            .permissions(0o600)
            .pool(&pool)
            .open("./target/test_builder_file");

        // Don't actually create the file in the test, just verify the builder works
        // The actual file operation might fail due to permissions in test environment
        match result {
            Ok(_) => {
                // Success - clean up
                let _ = std::fs::remove_file("./target/test_builder_file");
            }
            Err(_) => {
                // Expected in restricted test environment
            }
        }
    }

    #[test]
    fn test_open_flags_combine() {
        let flags = OpenFlags::combine(&[OpenFlags::READ, OpenFlags::WRITE, OpenFlags::CREATE]);
        let expected =
            (apr_sys::APR_FOPEN_READ | apr_sys::APR_FOPEN_WRITE | apr_sys::APR_FOPEN_CREATE) as i32;
        assert_eq!(flags.0, expected);
    }
}