Skip to main content

palladium/runtime/
io.rs

1// Runtime support for I/O operations
2// "Bridging Palladium to the system"
3
4#![allow(clippy::not_unsafe_ptr_arg_deref)]
5
6use std::fs::{File, OpenOptions};
7use std::io::{self, Read, Write, Seek, SeekFrom as StdSeekFrom};
8use std::path::Path;
9use std::os::unix::fs::PermissionsExt;
10use std::time::SystemTime;
11
12/// File handle wrapper
13#[repr(C)]
14pub struct FileHandle {
15    file: Option<File>,
16    path: String,
17    mode: FileMode,
18}
19
20/// File mode
21#[repr(C)]
22#[derive(Debug, Clone, Copy)]
23pub enum FileMode {
24    Read = 0,
25    Write = 1,
26    Append = 2,
27    ReadWrite = 3,
28}
29
30/// Seek position
31#[repr(C)]
32pub enum SeekFrom {
33    Start(u64),
34    End(i64),
35    Current(i64),
36}
37
38/// I/O error codes
39#[repr(C)]
40#[derive(Debug)]
41pub enum IoErrorCode {
42    NotFound = 0,
43    PermissionDenied = 1,
44    AlreadyExists = 2,
45    InvalidInput = 3,
46    UnexpectedEof = 4,
47    Other = 5,
48}
49
50/// Convert Rust io::Error to our error code
51#[allow(dead_code)]
52fn io_error_to_code(err: &io::Error) -> IoErrorCode {
53    match err.kind() {
54        io::ErrorKind::NotFound => IoErrorCode::NotFound,
55        io::ErrorKind::PermissionDenied => IoErrorCode::PermissionDenied,
56        io::ErrorKind::AlreadyExists => IoErrorCode::AlreadyExists,
57        io::ErrorKind::InvalidInput => IoErrorCode::InvalidInput,
58        io::ErrorKind::UnexpectedEof => IoErrorCode::UnexpectedEof,
59        _ => IoErrorCode::Other,
60    }
61}
62
63// File operations - C-compatible exports
64
65#[no_mangle]
66pub extern "C" fn pd_file_open(path: *const u8, path_len: usize, mode: FileMode) -> *mut FileHandle {
67    unsafe {
68        let path_slice = std::slice::from_raw_parts(path, path_len);
69        let path_str = match std::str::from_utf8(path_slice) {
70            Ok(s) => s,
71            Err(_) => return std::ptr::null_mut(),
72        };
73
74        let file = match mode {
75            FileMode::Read => File::open(path_str),
76            FileMode::Write => File::create(path_str),
77            FileMode::Append => OpenOptions::new().append(true).open(path_str),
78            FileMode::ReadWrite => OpenOptions::new().read(true).write(true).open(path_str),
79        };
80
81        match file {
82            Ok(f) => {
83                let handle = Box::new(FileHandle {
84                    file: Some(f),
85                    path: path_str.to_string(),
86                    mode,
87                });
88                Box::into_raw(handle)
89            }
90            Err(_) => std::ptr::null_mut(),
91        }
92    }
93}
94
95#[no_mangle]
96pub extern "C" fn pd_file_close(handle: *mut FileHandle) -> i32 {
97    if handle.is_null() {
98        return -1;
99    }
100    
101    unsafe {
102        let _ = Box::from_raw(handle);
103        0
104    }
105}
106
107#[no_mangle]
108pub extern "C" fn pd_file_read(handle: *mut FileHandle, buffer: *mut u8, len: usize) -> i64 {
109    if handle.is_null() || buffer.is_null() {
110        return -1;
111    }
112
113    unsafe {
114        let handle = &mut *handle;
115        if let Some(ref mut file) = handle.file {
116            let buffer_slice = std::slice::from_raw_parts_mut(buffer, len);
117            match file.read(buffer_slice) {
118                Ok(n) => n as i64,
119                Err(_) => -1,
120            }
121        } else {
122            -1
123        }
124    }
125}
126
127#[no_mangle]
128pub extern "C" fn pd_file_write(handle: *mut FileHandle, buffer: *const u8, len: usize) -> i64 {
129    if handle.is_null() || buffer.is_null() {
130        return -1;
131    }
132
133    unsafe {
134        let handle = &mut *handle;
135        if let Some(ref mut file) = handle.file {
136            let buffer_slice = std::slice::from_raw_parts(buffer, len);
137            match file.write(buffer_slice) {
138                Ok(n) => n as i64,
139                Err(_) => -1,
140            }
141        } else {
142            -1
143        }
144    }
145}
146
147#[no_mangle]
148pub extern "C" fn pd_file_seek(handle: *mut FileHandle, whence: u8, offset: i64) -> i64 {
149    if handle.is_null() {
150        return -1;
151    }
152
153    unsafe {
154        let handle = &mut *handle;
155        if let Some(ref mut file) = handle.file {
156            let pos = match whence {
157                0 => StdSeekFrom::Start(offset as u64),
158                1 => StdSeekFrom::Current(offset),
159                2 => StdSeekFrom::End(offset),
160                _ => return -1,
161            };
162            
163            match file.seek(pos) {
164                Ok(n) => n as i64,
165                Err(_) => -1,
166            }
167        } else {
168            -1
169        }
170    }
171}
172
173#[no_mangle]
174pub extern "C" fn pd_file_flush(handle: *mut FileHandle) -> i32 {
175    if handle.is_null() {
176        return -1;
177    }
178
179    unsafe {
180        let handle = &mut *handle;
181        if let Some(ref mut file) = handle.file {
182            match file.flush() {
183                Ok(_) => 0,
184                Err(_) => -1,
185            }
186        } else {
187            -1
188        }
189    }
190}
191
192// Path operations
193
194#[no_mangle]
195pub extern "C" fn pd_path_exists(path: *const u8, path_len: usize) -> i32 {
196    unsafe {
197        let path_slice = std::slice::from_raw_parts(path, path_len);
198        let path_str = match std::str::from_utf8(path_slice) {
199            Ok(s) => s,
200            Err(_) => return 0,
201        };
202        
203        if Path::new(path_str).exists() { 1 } else { 0 }
204    }
205}
206
207#[no_mangle]
208pub extern "C" fn pd_path_is_file(path: *const u8, path_len: usize) -> i32 {
209    unsafe {
210        let path_slice = std::slice::from_raw_parts(path, path_len);
211        let path_str = match std::str::from_utf8(path_slice) {
212            Ok(s) => s,
213            Err(_) => return 0,
214        };
215        
216        if Path::new(path_str).is_file() { 1 } else { 0 }
217    }
218}
219
220#[no_mangle]
221pub extern "C" fn pd_path_is_dir(path: *const u8, path_len: usize) -> i32 {
222    unsafe {
223        let path_slice = std::slice::from_raw_parts(path, path_len);
224        let path_str = match std::str::from_utf8(path_slice) {
225            Ok(s) => s,
226            Err(_) => return 0,
227        };
228        
229        if Path::new(path_str).is_dir() { 1 } else { 0 }
230    }
231}
232
233// Directory operations
234
235#[no_mangle]
236pub extern "C" fn pd_create_dir(path: *const u8, path_len: usize) -> i32 {
237    unsafe {
238        let path_slice = std::slice::from_raw_parts(path, path_len);
239        let path_str = match std::str::from_utf8(path_slice) {
240            Ok(s) => s,
241            Err(_) => return -1,
242        };
243        
244        match std::fs::create_dir(path_str) {
245            Ok(_) => 0,
246            Err(_) => -1,
247        }
248    }
249}
250
251#[no_mangle]
252pub extern "C" fn pd_create_dir_all(path: *const u8, path_len: usize) -> i32 {
253    unsafe {
254        let path_slice = std::slice::from_raw_parts(path, path_len);
255        let path_str = match std::str::from_utf8(path_slice) {
256            Ok(s) => s,
257            Err(_) => return -1,
258        };
259        
260        match std::fs::create_dir_all(path_str) {
261            Ok(_) => 0,
262            Err(_) => -1,
263        }
264    }
265}
266
267#[no_mangle]
268pub extern "C" fn pd_remove_dir(path: *const u8, path_len: usize) -> i32 {
269    unsafe {
270        let path_slice = std::slice::from_raw_parts(path, path_len);
271        let path_str = match std::str::from_utf8(path_slice) {
272            Ok(s) => s,
273            Err(_) => return -1,
274        };
275        
276        match std::fs::remove_dir(path_str) {
277            Ok(_) => 0,
278            Err(_) => -1,
279        }
280    }
281}
282
283#[no_mangle]
284pub extern "C" fn pd_remove_dir_all(path: *const u8, path_len: usize) -> i32 {
285    unsafe {
286        let path_slice = std::slice::from_raw_parts(path, path_len);
287        let path_str = match std::str::from_utf8(path_slice) {
288            Ok(s) => s,
289            Err(_) => return -1,
290        };
291        
292        match std::fs::remove_dir_all(path_str) {
293            Ok(_) => 0,
294            Err(_) => -1,
295        }
296    }
297}
298
299#[no_mangle]
300pub extern "C" fn pd_remove_file(path: *const u8, path_len: usize) -> i32 {
301    unsafe {
302        let path_slice = std::slice::from_raw_parts(path, path_len);
303        let path_str = match std::str::from_utf8(path_slice) {
304            Ok(s) => s,
305            Err(_) => return -1,
306        };
307        
308        match std::fs::remove_file(path_str) {
309            Ok(_) => 0,
310            Err(_) => -1,
311        }
312    }
313}
314
315// File metadata
316
317#[repr(C)]
318pub struct FileMetadata {
319    size: u64,
320    is_file: u8,
321    is_dir: u8,
322    is_symlink: u8,
323    readonly: u8,
324    mode: u32,
325    modified_secs: i64,
326    accessed_secs: i64,
327    created_secs: i64,
328}
329
330#[no_mangle]
331pub extern "C" fn pd_file_metadata(path: *const u8, path_len: usize, metadata: *mut FileMetadata) -> i32 {
332    if metadata.is_null() {
333        return -1;
334    }
335
336    unsafe {
337        let path_slice = std::slice::from_raw_parts(path, path_len);
338        let path_str = match std::str::from_utf8(path_slice) {
339            Ok(s) => s,
340            Err(_) => return -1,
341        };
342        
343        match std::fs::metadata(path_str) {
344            Ok(meta) => {
345                let metadata = &mut *metadata;
346                metadata.size = meta.len();
347                metadata.is_file = if meta.is_file() { 1 } else { 0 };
348                metadata.is_dir = if meta.is_dir() { 1 } else { 0 };
349                metadata.is_symlink = if meta.file_type().is_symlink() { 1 } else { 0 };
350                metadata.readonly = if meta.permissions().readonly() { 1 } else { 0 };
351                metadata.mode = meta.permissions().mode();
352                
353                // Time handling
354                metadata.modified_secs = meta.modified()
355                    .ok()
356                    .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
357                    .map(|d| d.as_secs() as i64)
358                    .unwrap_or(0);
359                    
360                metadata.accessed_secs = meta.accessed()
361                    .ok()
362                    .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
363                    .map(|d| d.as_secs() as i64)
364                    .unwrap_or(0);
365                    
366                metadata.created_secs = meta.created()
367                    .ok()
368                    .and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
369                    .map(|d| d.as_secs() as i64)
370                    .unwrap_or(0);
371                
372                0
373            }
374            Err(_) => -1,
375        }
376    }
377}
378
379// Directory listing
380
381#[repr(C)]
382pub struct DirEntry {
383    name: *mut u8,
384    name_len: usize,
385    is_file: u8,
386    is_dir: u8,
387}
388
389#[no_mangle]
390pub extern "C" fn pd_read_dir(path: *const u8, path_len: usize, entries: *mut *mut DirEntry, count: *mut usize) -> i32 {
391    if entries.is_null() || count.is_null() {
392        return -1;
393    }
394
395    unsafe {
396        let path_slice = std::slice::from_raw_parts(path, path_len);
397        let path_str = match std::str::from_utf8(path_slice) {
398            Ok(s) => s,
399            Err(_) => return -1,
400        };
401        
402        match std::fs::read_dir(path_str) {
403            Ok(dir) => {
404                let mut entry_vec = Vec::new();
405                
406                for entry in dir.flatten() {
407                    if let Some(name) = entry.file_name().to_str() {
408                        let name_bytes = name.as_bytes();
409                        let name_copy = name_bytes.to_vec().into_boxed_slice();
410                        let name_ptr = Box::into_raw(name_copy) as *mut u8;
411                        
412                        let file_type = entry.file_type().ok();
413                        let de = DirEntry {
414                            name: name_ptr,
415                            name_len: name_bytes.len(),
416                            is_file: file_type.map(|t| if t.is_file() { 1 } else { 0 }).unwrap_or(0),
417                            is_dir: file_type.map(|t| if t.is_dir() { 1 } else { 0 }).unwrap_or(0),
418                        };
419                        entry_vec.push(de);
420                    }
421                }
422                
423                *count = entry_vec.len();
424                let entries_array = entry_vec.into_boxed_slice();
425                *entries = Box::into_raw(entries_array) as *mut DirEntry;
426                
427                0
428            }
429            Err(_) => -1,
430        }
431    }
432}
433
434#[no_mangle]
435pub extern "C" fn pd_free_dir_entries(entries: *mut DirEntry, count: usize) {
436    if entries.is_null() {
437        return;
438    }
439    
440    unsafe {
441        let entries_slice = std::slice::from_raw_parts_mut(entries, count);
442        for entry in &mut *entries_slice {
443            if !entry.name.is_null() {
444                let _ = Box::from_raw(std::slice::from_raw_parts_mut(entry.name, entry.name_len));
445            }
446        }
447        let _ = Box::from_raw(entries);
448    }
449}
450
451// Convenience functions
452
453#[no_mangle]
454pub extern "C" fn pd_read_file_to_string(path: *const u8, path_len: usize, out_str: *mut *mut u8, out_len: *mut usize) -> i32 {
455    if out_str.is_null() || out_len.is_null() {
456        return -1;
457    }
458
459    unsafe {
460        let path_slice = std::slice::from_raw_parts(path, path_len);
461        let path_str = match std::str::from_utf8(path_slice) {
462            Ok(s) => s,
463            Err(_) => return -1,
464        };
465        
466        match std::fs::read_to_string(path_str) {
467            Ok(contents) => {
468                let bytes = contents.into_bytes().into_boxed_slice();
469                *out_len = bytes.len();
470                *out_str = Box::into_raw(bytes) as *mut u8;
471                0
472            }
473            Err(_) => -1,
474        }
475    }
476}
477
478#[no_mangle]
479pub extern "C" fn pd_write_string_to_file(path: *const u8, path_len: usize, data: *const u8, data_len: usize) -> i32 {
480    unsafe {
481        let path_slice = std::slice::from_raw_parts(path, path_len);
482        let path_str = match std::str::from_utf8(path_slice) {
483            Ok(s) => s,
484            Err(_) => return -1,
485        };
486        
487        let data_slice = std::slice::from_raw_parts(data, data_len);
488        
489        match std::fs::write(path_str, data_slice) {
490            Ok(_) => 0,
491            Err(_) => -1,
492        }
493    }
494}
495
496#[no_mangle]
497pub extern "C" fn pd_free_string(str: *mut u8, len: usize) {
498    if str.is_null() {
499        return;
500    }
501    
502    unsafe {
503        let _ = Box::from_raw(std::slice::from_raw_parts_mut(str, len));
504    }
505}