1#![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#[repr(C)]
14pub struct FileHandle {
15 file: Option<File>,
16 path: String,
17 mode: FileMode,
18}
19
20#[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#[repr(C)]
32pub enum SeekFrom {
33 Start(u64),
34 End(i64),
35 Current(i64),
36}
37
38#[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#[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#[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#[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#[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#[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 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#[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#[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}