Skip to main content

relibc/header/stdio/
getdelim.rs

1// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getline.html>.
2
3use alloc::vec::Vec;
4use core::{intrinsics::unlikely, ptr};
5
6use crate::{
7    header::{
8        errno::{EINVAL, ENOMEM, EOVERFLOW},
9        stdio::FILE,
10        stdlib,
11    },
12    io::BufRead,
13    platform::types::{c_char, c_int, c_void, size_t, ssize_t},
14};
15
16use crate::{
17    header::stdio::{F_EOF, F_ERR, feof, ferror},
18    platform::ERRNO,
19};
20
21/// see getdelim (getline is a special case of getdelim with delim == '\n')
22#[unsafe(no_mangle)]
23pub unsafe extern "C" fn getline(
24    lineptr: *mut *mut c_char,
25    n: *mut size_t,
26    stream: *mut FILE,
27) -> ssize_t {
28    unsafe { getdelim(lineptr, n, c_int::from(b'\n'), stream) }
29}
30
31// One *could* read the standard as 'getdelim sets the stream error flag on *any* error, though
32// since glibc doesn't seem to do this, I won't either
33
34/// <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getline.html>
35///
36/// # Safety
37/// - `lineptr, *lineptr, `n`, `stream` pointers must be valid and have to be aligned.
38/// - `stream` has to be a valid file handle returned by fopen and likes.
39///
40/// # Deviation from POSIX
41/// - **EINVAL is set on stream being NULL or delim not fitting into char**
42///   (POSIX allows UB)
43/// - **`*n` can contain invalid data.** The buffer size `n` is not read,
44///   instead realloc is called each time. That is in principle inefficent
45///   since the buffer is reallocated in memory for every call, but if `n` is
46///   by mistake bigger than the number of bytes allocated for the buffer,
47///   there can be no out-of-bounds write.
48/// - On non-stream-related errors, the error indicator of the stream is *not*
49///   set. Posix states "If an error occurs, the error indicator for the stream
50///   shall be set, and the function shall return -1 and set errno to indicate
51///   the error." but in cases that produce EINVAL even glibc doesn't seem to
52///   set the error indicator, so we also don't.
53#[unsafe(no_mangle)]
54pub unsafe extern "C" fn getdelim(
55    lineptr: *mut *mut c_char,
56    n: *mut size_t,
57    delim: c_int,
58    stream: *mut FILE,
59) -> ssize_t {
60    let (lineptr, n, stream) = if let (Some(ptr), Some(n), Some(file)) =
61        (unsafe { lineptr.as_mut() }, unsafe { n.as_mut() }, unsafe {
62            stream.as_mut()
63        }) {
64        (ptr, n, file)
65    } else {
66        ERRNO.set(EINVAL);
67        return -1 as ssize_t;
68    };
69
70    if unsafe { feof(stream) } != 0 || unsafe { ferror(stream) } != 0 {
71        return -1 as ssize_t;
72    }
73
74    // POSIX specifies UB but we test anyway
75    // returning EINVAL in that case
76    let delim: u8 = if let Ok(delim) = delim.try_into() {
77        delim
78    } else {
79        ERRNO.set(EINVAL);
80        return -1;
81    };
82
83    //TODO: More efficient algorithm using lineptr and n instead of this vec
84    let mut buf = Vec::new();
85    let count = {
86        let mut stream = (*stream).lock();
87        match stream.read_until(delim, &mut buf) {
88            Ok(ok) => ok,
89            Err(err) => {
90                stream.flags &= F_ERR;
91                return -1;
92            }
93        }
94    };
95
96    // "[EOVERFLOW]
97    // The number of bytes to be written into the buffer, including the delimiter character (if encountered), would exceed {SSIZE_MAX}."
98    if unlikely(count > ssize_t::MAX as usize) {
99        ERRNO.set(EOVERFLOW);
100        return -1;
101    }
102
103    // we reached EOF if either
104    // - we have no last elem (because vec is empty), or
105    // - the last elem doesn't match the delimiter
106    let eof_reached = if let Some(last) = buf.last() {
107        *last == delim
108    } else {
109        true
110    };
111
112    // "If the end-of-file indicator for the stream is set, or if no characters were read and the
113    // stream is at end-of-file, the end-of-file indicator for the stream shall be set and the
114    // function shall return -1."
115    if eof_reached {
116        stream.flags &= F_EOF;
117        if count == 0 {
118            return -1;
119        }
120    }
121
122    //TODO: Check errors and improve safety
123    {
124        // Allocate lineptr to size of buf plus NUL byte and set n to size of lineptr
125        *n = count + 1;
126        // The advantage in always realloc'ing is that even if the user supplies a wrong n, this
127        // doesn't break
128        *lineptr = unsafe { stdlib::realloc((*lineptr).cast::<c_void>(), *n) }.cast::<c_char>();
129        if unlikely(lineptr.is_null() && *n != 0usize) {
130            // memory error; realloc returns NULL on alloc'ing 0 bytes
131            ERRNO.set(ENOMEM);
132            return -1;
133        }
134
135        // Copy buf to lineptr
136        unsafe { ptr::copy(buf.as_ptr(), (*lineptr).cast::<u8>(), count) };
137
138        // NUL terminate lineptr
139        unsafe { *lineptr.add(count) = 0 };
140
141        // TODO remove
142        /*eprintln!(
143            "[DBG]{}: {}, {:?}, {:?}, {:?}", line!(),
144            String::from_utf8(buf).unwrap(), count, *n, *lineptr
145        );*/
146        // Return allocated size
147        count as ssize_t
148    }
149}