Skip to main content

c/
stdio_extra.rs

1// Copyright (c) 2026 Meng Zhuo
2// SPDX-License-Identifier: BSD-3-Clause
3
4// fopencookie, open_wmemstream, strfmon and getdate.
5
6// ============================================================
7// fopencookie
8// ============================================================
9
10#[repr(C)]
11#[derive(Copy, Clone)]
12pub struct CookieIo {
13    pub read: usize,
14    pub write: usize,
15    pub seek: usize,
16    pub close: usize,
17}
18
19#[repr(C)]
20struct CookieFile {
21    cookie: *mut c_void,
22    io: CookieIo,
23}
24
25type CookieReadFn = unsafe extern "C" fn(*mut c_void, *mut c_char, usize) -> isize;
26type CookieWriteFn = unsafe extern "C" fn(*mut c_void, *const c_char, usize) -> isize;
27type CookieSeekFn = unsafe extern "C" fn(*mut c_void, *mut i64, c_int) -> c_int;
28type CookieCloseFn = unsafe extern "C" fn(*mut c_void) -> c_int;
29
30unsafe extern "C" fn cookie_read(f: *mut FILE, buf: *mut u8, len: usize) -> usize {
31    let cf = (*f).cookie as *mut CookieFile;
32    if cf.is_null() || (*cf).io.read == 0 {
33        (*f).flags |= F_ERR;
34        (*f)._err = 1;
35        return 0;
36    }
37    let func: CookieReadFn = core::mem::transmute((*cf).io.read);
38    let n = func((*cf).cookie, buf as *mut c_char, len);
39    if n <= 0 {
40        if n == 0 {
41            (*f).flags |= F_EOF;
42        } else {
43            (*f).flags |= F_ERR;
44            (*f)._err = 1;
45        }
46        return 0;
47    }
48    n as usize
49}
50
51unsafe extern "C" fn cookie_write(f: *mut FILE, buf: *const u8, len: usize) -> usize {
52    let cf = (*f).cookie as *mut CookieFile;
53    if cf.is_null() || (*cf).io.write == 0 { return len; }
54    let func: CookieWriteFn = core::mem::transmute((*cf).io.write);
55    let buffered = if (*f).wbase.is_null() { 0 } else { (*f).wpos as usize - (*f).wbase as usize };
56    if buffered > 0 {
57        let n = func((*cf).cookie, (*f).wbase as *const c_char, buffered);
58        if n != buffered as isize {
59            (*f).wpos = core::ptr::null_mut();
60            (*f).wbase = core::ptr::null_mut();
61            (*f).wend = core::ptr::null_mut();
62            (*f).flags |= F_ERR;
63            (*f).flags |= F_ERR;
64            return 0;
65        }
66        (*f).wbase = (*f).buf;
67        (*f).wpos = (*f).buf;
68        (*f).wend = (*f).buf.add((*f).buf_size);
69    }
70    if len > 0 {
71        let n = func((*cf).cookie, buf as *const c_char, len);
72        if n < 0 { (*f).flags |= F_ERR; return 0; }
73        return n as usize;
74    }
75    0
76}
77
78unsafe extern "C" fn cookie_seek(f: *mut FILE, off: i64, whence: c_int) -> i64 {
79    let cf = (*f).cookie as *mut CookieFile;
80    if cf.is_null() || (*cf).io.seek == 0 {
81        ERRNO!() = 95; // ENOTSUP
82        return -1;
83    }
84    if whence < 0 || whence > 2 {
85        ERRNO!() = EINVAL;
86        return -1;
87    }
88    let func: CookieSeekFn = core::mem::transmute((*cf).io.seek);
89    let mut o = off;
90    let r = func((*cf).cookie, &mut o as *mut i64, whence);
91    if r < 0 { return -1; }
92    o
93}
94
95unsafe extern "C" fn cookie_close(f: *mut FILE) -> c_int {
96    let cf = (*f).cookie as *mut CookieFile;
97    if cf.is_null() { return 0; }
98    let mut r = 0;
99    if (*cf).io.close != 0 {
100        let func: CookieCloseFn = core::mem::transmute((*cf).io.close);
101        r = func((*cf).cookie);
102    }
103    crate::free(cf as *mut c_void);
104    (*f).cookie = core::ptr::null_mut();
105    r
106}
107
108#[no_mangle]
109pub unsafe extern "C" fn fopencookie(cookie: *mut c_void, mode: *const c_char, io_funcs: CookieIo) -> *mut FILE {
110    if mode.is_null() { ERRNO!() = EINVAL; return core::ptr::null_mut(); }
111    let m = *mode;
112    if m != b'r' as c_char && m != b'w' as c_char && m != b'a' as c_char {
113        ERRNO!() = EINVAL;
114        return core::ptr::null_mut();
115    }
116    let f = crate::calloc(1, core::mem::size_of::<FILE>() + UNGET + BUFSIZ) as *mut FILE;
117    if f.is_null() { ERRNO!() = ENOMEM; return core::ptr::null_mut(); }
118    let cf = crate::calloc(1, core::mem::size_of::<CookieFile>()) as *mut CookieFile;
119    if cf.is_null() { crate::free(f as *mut c_void); ERRNO!() = ENOMEM; return core::ptr::null_mut(); }
120    (*cf).cookie = cookie;
121    (*cf).io = io_funcs;
122
123    (*f).fd = -1;
124    (*f).cookie = cf as *mut c_void;
125    (*f).buf = buf_ptr(f);
126    (*f).buf_size = BUFSIZ;
127    (*f).lbf = -1;
128    (*f).read_fn = Some(cookie_read);
129    (*f).write_fn = Some(cookie_write);
130    (*f).seek_fn = Some(cookie_seek);
131    (*f).close = Some(cookie_close);
132    if !crate::strchr(mode as *const u8, b'+' as c_int).is_null() {
133        (*f).flags = 0;
134    } else if m == b'r' as c_char {
135        (*f).flags = F_NOWR;
136    } else {
137        (*f).flags = F_NORD;
138    }
139    if m == b'a' as c_char { (*f).flags |= F_APP; }
140    f
141}
142
143// ============================================================
144// open_wmemstream
145// ============================================================
146
147#[repr(C)]
148struct WmsCookie {
149    bufp: *mut *mut wchar_t,
150    sizep: *mut usize,
151    pos: usize,
152    buf: *mut wchar_t,
153    len: usize,
154    space: usize,
155    // Incremental UTF-8 decoder.
156    need: u8,
157    have: u8,
158    cp: u32,
159}
160
161unsafe fn wms_emit(c: *mut WmsCookie, wc: u32) {
162    if (*c).pos >= (*c).space {
163        let len2 = ((*c).space * 2 + 1) | ((*c).pos + 1);
164        if len2 > (isize::MAX as usize) / 4 { return; }
165        let newbuf = crate::realloc((*c).buf as *mut c_void, len2 * 4) as *mut wchar_t;
166        if newbuf.is_null() { ERRNO!() = ENOMEM; return; }
167        core::ptr::write_bytes((newbuf as *mut u8).add((*c).space * 4), 0, (len2 - (*c).space) * 4);
168        (*c).buf = newbuf;
169        (*c).space = len2;
170        if !(*c).bufp.is_null() { *(*c).bufp = newbuf; }
171    }
172    *(*c).buf.add((*c).pos) = wc as wchar_t;
173    (*c).pos += 1;
174    if (*c).pos > (*c).len { (*c).len = (*c).pos; }
175    *(*c).buf.add((*c).pos) = 0;
176    if !(*c).sizep.is_null() { *(*c).sizep = (*c).pos; }
177}
178
179unsafe extern "C" fn wms_write(f: *mut FILE, buf: *const u8, len: usize) -> usize {
180    let c = (*f).cookie as *mut WmsCookie;
181    if c.is_null() { return 0; }
182    for i in 0..len {
183        let b = *buf.add(i);
184        if (*c).need == 0 {
185            if b < 0x80 {
186                wms_emit(c, b as u32);
187            } else if b >= 0xc2 && b <= 0xdf {
188                (*c).need = 1;
189                (*c).have = 0;
190                (*c).cp = (b & 0x1f) as u32;
191            } else if b >= 0xe0 && b <= 0xef {
192                (*c).need = 2;
193                (*c).have = 0;
194                (*c).cp = (b & 0x0f) as u32;
195            } else if b >= 0xf0 && b <= 0xf4 {
196                (*c).need = 3;
197                (*c).have = 0;
198                (*c).cp = (b & 0x07) as u32;
199            } else {
200                wms_emit(c, 0xfffd);
201            }
202        } else if b & 0xc0 == 0x80 {
203            (*c).cp = ((*c).cp << 6) | (b & 0x3f) as u32;
204            (*c).have += 1;
205            if (*c).have == (*c).need {
206                wms_emit(c, (*c).cp);
207                (*c).need = 0;
208                (*c).have = 0;
209                (*c).cp = 0;
210            }
211        } else {
212            // Invalid continuation: emit a replacement and treat this byte as
213            // the start of a new sequence.
214            wms_emit(c, 0xfffd);
215            (*c).need = 0;
216            (*c).have = 0;
217            (*c).cp = 0;
218            if b < 0x80 {
219                wms_emit(c, b as u32);
220            } else if b >= 0xc2 && b <= 0xdf {
221                (*c).need = 1;
222                (*c).cp = (b & 0x1f) as u32;
223            } else if b >= 0xe0 && b <= 0xef {
224                (*c).need = 2;
225                (*c).cp = (b & 0x0f) as u32;
226            } else if b >= 0xf0 && b <= 0xf4 {
227                (*c).need = 3;
228                (*c).cp = (b & 0x07) as u32;
229            } else {
230                wms_emit(c, 0xfffd);
231            }
232        }
233    }
234    len
235}
236
237unsafe extern "C" fn wms_seek(f: *mut FILE, off: i64, whence: c_int) -> i64 {
238    let c = (*f).cookie as *mut WmsCookie;
239    if c.is_null() || whence < 0 || whence > 2 {
240        ERRNO!() = EINVAL;
241        return -1;
242    }
243    let base: i64 = match whence {
244        0 => 0,
245        1 => (*c).pos as i64,
246        _ => (*c).len as i64,
247    };
248    if off < -base || off > (isize::MAX as i64) / 4 - base {
249        ERRNO!() = EINVAL;
250        return -1;
251    }
252    (*c).pos = (base + off) as usize;
253    (*c).need = 0;
254    (*c).have = 0;
255    (*c).cp = 0;
256    (*c).pos as i64
257}
258
259unsafe extern "C" fn wms_close(_f: *mut FILE) -> c_int {
260    0
261}
262
263#[no_mangle]
264pub unsafe extern "C" fn open_wmemstream(bufp: *mut *mut wchar_t, sizep: *mut usize) -> *mut FILE {
265    if bufp.is_null() || sizep.is_null() { ERRNO!() = EINVAL; return core::ptr::null_mut(); }
266    let f = crate::calloc(1, core::mem::size_of::<FILE>() + 1) as *mut FILE;
267    if f.is_null() { ERRNO!() = ENOMEM; return core::ptr::null_mut(); }
268    let c = crate::calloc(1, core::mem::size_of::<WmsCookie>()) as *mut WmsCookie;
269    if c.is_null() { crate::free(f as *mut c_void); ERRNO!() = ENOMEM; return core::ptr::null_mut(); }
270    let buf = crate::calloc(1, 4) as *mut wchar_t;
271    if buf.is_null() {
272        crate::free(c as *mut c_void);
273        crate::free(f as *mut c_void);
274        ERRNO!() = ENOMEM;
275        return core::ptr::null_mut();
276    }
277    (*c).bufp = bufp;
278    (*c).sizep = sizep;
279    (*c).buf = buf;
280    (*c).space = 1;
281    *bufp = buf;
282    *sizep = 0;
283    (*f).fd = -1;
284    (*f).cookie = c as *mut c_void;
285    let base = (f as *mut u8).add(core::mem::size_of::<FILE>());
286    (*f).buf = base;
287    (*f).buf_size = 0;
288    (*f).lbf = -1;
289    (*f).flags = F_NORD;
290    (*f).write_fn = Some(wms_write);
291    (*f).seek_fn = Some(wms_seek);
292    (*f).close = Some(wms_close);
293    (*f).mode = 1;
294    f
295}
296
297// ============================================================
298// strfmon / strfmon_l
299// ============================================================
300
301unsafe fn strfmon_impl(mut s: *mut c_char, n: usize, fmt: *const c_char, mut ap: VaList) -> isize {
302    let start = s;
303    let mut cap = n;
304    let mut f = fmt;
305    while cap > 0 && *f != 0 {
306        if *f as u8 != b'%' {
307            *s = *f;
308            s = s.add(1);
309            f = f.add(1);
310            cap -= 1;
311            continue;
312        }
313        f = f.add(1);
314        if *f as u8 == b'%' {
315            *s = *f;
316            s = s.add(1);
317            f = f.add(1);
318            cap -= 1;
319            continue;
320        }
321        let mut fill = b' ' as c_char;
322        let mut nogrp = true; // C locale has no digit grouping
323        let mut negpar = false;
324        let mut nosym = false;
325        let mut left = false;
326        let mut plus = false;
327        loop {
328            match *f as u8 {
329                b'=' => { f = f.add(1); fill = *f; f = f.add(1); }
330                b'^' => { nogrp = true; f = f.add(1); }
331                b'(' => { negpar = true; f = f.add(1); }
332                b'+' => { plus = true; f = f.add(1); }
333                b'!' => { nosym = true; f = f.add(1); }
334                b'-' => { left = true; f = f.add(1); }
335                _ => break,
336            }
337        }
338        let mut fw = 0usize;
339        while *f as u8 >= b'0' && *f as u8 <= b'9' {
340            fw = fw * 10 + (*f as u8 - b'0') as usize;
341            f = f.add(1);
342        }
343        let mut lp = 1usize;
344        if *f as u8 == b'#' {
345            f = f.add(1);
346            lp = 0;
347            while *f as u8 >= b'0' && *f as u8 <= b'9' {
348                lp = lp * 10 + (*f as u8 - b'0') as usize;
349                f = f.add(1);
350            }
351            if lp == 0 { lp = 1; }
352        }
353        let mut rp = 2usize;
354        if *f as u8 == b'.' {
355            rp = 0;
356            f = f.add(1);
357            while *f as u8 >= b'0' && *f as u8 <= b'9' {
358                rp = rp * 10 + (*f as u8 - b'0') as usize;
359                f = f.add(1);
360            }
361        }
362        let _intl = *f as u8 == b'i';
363        if *f != 0 { f = f.add(1); }
364        let x = ap.next_arg::<f64>();
365        let neg = x < 0.0;
366        let ax = if neg { -x } else { x };
367
368        // Render the absolute number without width.
369        let mut num = [0 as c_char; 256];
370        let rn = crate::snprintf(
371            num.as_mut_ptr(),
372            num.len(),
373            b"%.*f\0".as_ptr() as *const c_char,
374            rp as c_int,
375            ax,
376        );
377        let mut nlen = core::cmp::min(rn.max(0) as usize, num.len() - 1);
378
379        // Split integer/fraction parts.
380        let mut dot = nlen;
381        for i in 0..nlen {
382            if num[i] as u8 == b'.' { dot = i; break; }
383        }
384        let mut int_digits = dot;
385        // Minimum integer digits requested with '#<n>'.
386        let zero_pad = if int_digits < lp { lp - int_digits } else { 0 };
387
388        // Group integer digits (only when explicitly enabled; C locale has none).
389        let mut grouped = [0 as c_char; 320];
390        let mut glen = 0usize;
391        if !nogrp && int_digits > 3 {
392            let first = int_digits % 3;
393            let mut i = 0usize;
394            while i < int_digits {
395                if i > 0 && (i - first) % 3 == 0 { grouped[glen] = b',' as c_char; glen += 1; }
396                grouped[glen] = num[i]; glen += 1; i += 1;
397            }
398        } else {
399            for i in 0..int_digits { grouped[glen] = num[i]; glen += 1; }
400        }
401        let _ = &mut int_digits;
402        let _ = &mut nlen;
403
404        // Compose sign/parens + zeros + grouped integer + fraction.
405        let mut core = [0 as c_char; 640];
406        let mut clen = 0usize;
407        let parens = negpar && neg;
408        if parens { core[clen] = b'(' as c_char; clen += 1; }
409        else if neg { core[clen] = b'-' as c_char; clen += 1; }
410        else if plus { core[clen] = b'+' as c_char; clen += 1; }
411        for _ in 0..zero_pad {
412            core[clen] = b'0' as c_char; clen += 1;
413        }
414        for i in 0..glen { core[clen] = grouped[i]; clen += 1; }
415        for i in dot..nlen { core[clen] = num[i]; clen += 1; }
416        if parens { core[clen] = b')' as c_char; clen += 1; }
417
418        let width = if fw > clen { fw } else { clen };
419        let pad = width - clen;
420        if left {
421            for i in 0..clen {
422                if cap <= 1 { break; }
423                *s = core[i]; s = s.add(1); cap -= 1;
424            }
425            for _ in 0..pad {
426                if cap <= 1 { break; }
427                *s = b' ' as c_char; s = s.add(1); cap -= 1;
428            }
429        } else {
430            for _ in 0..pad {
431                if cap <= 1 { break; }
432                *s = fill; s = s.add(1); cap -= 1;
433            }
434            for i in 0..clen {
435                if cap <= 1 { break; }
436                *s = core[i]; s = s.add(1); cap -= 1;
437            }
438        }
439        let _ = nosym;
440    }
441    *s = 0;
442    (s as usize - start as usize) as isize
443}
444
445#[no_mangle]
446pub unsafe extern "C" fn strfmon(s: *mut c_char, n: usize, fmt: *const c_char, args: ...) -> isize {
447    strfmon_impl(s, n, fmt, args)
448}
449
450#[no_mangle]
451pub unsafe extern "C" fn strfmon_l(
452    s: *mut c_char,
453    n: usize,
454    _loc: locale_t,
455    fmt: *const c_char,
456    args: ...,
457) -> isize {
458    strfmon_impl(s, n, fmt, args)
459}
460
461// ============================================================
462// getdate
463// ============================================================
464
465#[no_mangle]
466pub static mut getdate_err: c_int = 0;
467
468static mut GETDATE_TM: crate::tm = crate::tm {
469    tm_sec: 0,
470    tm_min: 0,
471    tm_hour: 0,
472    tm_mday: 0,
473    tm_mon: 0,
474    tm_year: 0,
475    tm_wday: 0,
476    tm_yday: 0,
477    tm_isdst: 0,
478    tm_gmtoff: 0,
479    tm_zone: core::ptr::null(),
480};
481
482#[no_mangle]
483pub unsafe extern "C" fn getdate(s: *const c_char) -> *mut crate::tm {
484    if s.is_null() {
485        getdate_err = 1;
486        return core::ptr::null_mut();
487    }
488    let datemsk = crate::getenv(b"DATEMSK\0".as_ptr() as *const c_char);
489    if datemsk.is_null() {
490        getdate_err = 1;
491        return core::ptr::null_mut();
492    }
493    let f = crate::fopen(datemsk, b"r\0".as_ptr() as *const c_char);
494    if f.is_null() {
495        getdate_err = if ERRNO!() == ENOMEM { 6 } else { 2 };
496        return core::ptr::null_mut();
497    }
498    let mut fmt = [0 as c_char; 128];
499    let mut found = false;
500    while !crate::fgets(fmt.as_mut_ptr(), fmt.len() as c_int, f).is_null() {
501        let mut end: *mut c_char = core::ptr::null_mut();
502        let p = crate::strptime(s, fmt.as_ptr(), &raw mut GETDATE_TM);
503        if !p.is_null() && *p == 0 {
504            found = true;
505            break;
506        }
507        let _ = &mut end;
508    }
509    if !found && crate::ferror(f) != 0 { getdate_err = 5; }
510    crate::fclose(f);
511    if found {
512        getdate_err = 0;
513        return &raw mut GETDATE_TM;
514    }
515    if getdate_err == 0 { getdate_err = 7; }
516    core::ptr::null_mut()
517}