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
|
.TH MPU_FOPENCOOKIE 3 "September 2026" "libmpuio" "LIBMPUIO Programmer's Manual"
.SH NAME
mpu_fopencookie \- open an arbitrary callback object as a LIBMPUIO stream
.SH SYNOPSIS
.nf
#include <libmpuio.h>
typedef ssize_t mpu_cookie_read_function_t( void *cookie, char *buf,
size_t size );
typedef ssize_t mpu_cookie_write_function_t( void *cookie, const char *buf,
size_t size );
typedef int mpu_cookie_seek_function_t( void *cookie, off_t *position,
int whence );
typedef int mpu_cookie_close_function_t( void *cookie );
typedef struct mpu_cookie_io_functions_t
{
mpu_cookie_read_function_t *read;
mpu_cookie_write_function_t *write;
mpu_cookie_seek_function_t *seek;
mpu_cookie_close_function_t *close;
} mpu_cookie_io_functions_t;
mpu_FILE *mpu_fopencookie( void *cookie, const char *mode,
mpu_cookie_io_functions_t io_functions );
.fi
.SH DESCRIPTION
.B mpu_fopencookie
creates a LIBMPUIO stream whose external byte transport is supplied by caller
callbacks. The opaque
.I cookie
pointer is stored by the stream and passed unchanged to every callback.
.PP
The cookie backend is a third, independent LIBMPUIO backend. It does not
replace either the descriptor-backed file backend or the native UCS-2 string
backend used by
.BR mpu_fmemopen (3)
and
.BR mpu_open_memstream (3).
The three backends share the common
.B mpu_FILE
formatted, buffering, locking, and status machinery through the internal
FILE_plus jump-table abstraction.
.PP
The
.I mode
syntax is the same strict r/w/a, optional +, optional b grammar accepted by
.BR mpu_fopen (3).
A readable mode requires a non-NULL
.I read
callback and a writable mode requires a non-NULL
.I write
callback. Append mode additionally requires
.I seek
so LIBMPUIO can place each newly entered output sequence at the logical end.
.SH CALLBACKS
The
.I read
callback copies at most
.I size
external bytes to
.I buf
and returns the number of bytes produced. Zero denotes end of input. A
negative return denotes failure and should leave a useful value in
.BR errno .
Returning more bytes than requested is treated as an I/O error.
.PP
The
.I write
callback consumes at most
.I size
bytes from
.I buf
and returns the number accepted. A negative return denotes failure. A zero
return for a nonzero request, or a return larger than the request, is treated
as
.BR EIO .
LIBMPUIO may call the callback repeatedly to complete buffered output.
.PP
The
.I seek
callback receives the requested position in
.I *position
and one of
.BR MPU_SEEK_SET ,
.BR MPU_SEEK_CUR ,
or
.BR MPU_SEEK_END .
On success it stores the new external-byte position back into
.I *position
and returns zero. A nonzero result denotes failure. If no seek callback was
supplied, positioning operations fail with
.BR ESPIPE .
.PP
The optional
.I close
callback is invoked once by
.BR mpu_fclose (3).
A zero return denotes success; a nonzero return makes close fail.
The user cookie itself remains caller-defined: LIBMPUIO frees only its private
callback descriptor, not the object pointed to by
.IR cookie .
.SH TEXT AND RAW BYTE MODEL
Cookie callbacks are byte oriented. They operate at the same external
representation boundary as the file backend. Consequently text functions
such as
.BR mpu_fgetc (3),
.BR mpu_fputs (3),
and formatted I/O decode or encode UTF-8 between the callbacks and strict
UCS-2
.BR __mpu_char16_t .
Invalid UTF-8, surrogate values, and scalar values above U+FFFF retain the
normal LIBMPUIO
.B EILSEQ
semantics.
.PP
.BR mpu_fread (3)
and
.BR mpu_fwrite (3)
operate directly on the callback byte stream and do not perform text
conversion.
.SH BUFFERING
Cookie streams use the ordinary LIBMPUIO buffering machinery and support
.BR mpu_setvbuf (3),
.BR mpu_setbuf (3),
and
.BR mpu_setlinebuf (3).
The callback transfer sizes are therefore not required to match individual
public read/write calls.
.PP
When a readable update stream has unread buffered input, switching to output
requires the seek callback so the underlying object can be restored to the
logical position before writing.
.SH POSITIONING
If
.I seek
is supplied,
.BR mpu_fseek (3),
.BR mpu_fseeko (3),
.BR mpu_ftell (3),
.BR mpu_ftello (3),
.BR mpu_fgetpos (3),
and
.BR mpu_fsetpos (3)
operate in external byte positions. Buffered read-ahead, pending output, and
UCS-2 pushback are accounted for by the same logical-position rules used by
real UTF-8 files.
.PP
Cookie streams have no POSIX file descriptor; therefore
.BR mpu_fileno (3)
fails with
.BR EBADF .
.SH RETURN VALUE
.B mpu_fopencookie
returns a new stream on success and NULL on failure.
.SH ERRORS
.B EINVAL
is reported for an invalid mode or for callback sets that cannot satisfy the
requested access mode. Append mode without a seek callback is also rejected.
.B ESPIPE
is reported when positioning is requested on a nonseekable cookie.
.B EIO
is used for impossible callback transfer counts. Callback failures otherwise
preserve the callback's
.B errno
value and set the stream error indicator where appropriate.
.PP
A callback that reports failure is responsible for setting
.B errno
to the reason for that failure. In particular, a failing close callback's
errno value is preserved by mpu_fclose() when it is the first close-path error.
.SH THREAD SAFETY
Ordinary operations on a cookie stream acquire the normal recursive stream
mutex. This serializes LIBMPUIO access to the callback set for one stream; it
does not make the caller's cookie object safe against unrelated direct access
or against access through another stream.
.SH NOTES
Unlike an implementation that builds memory streams on top of a public cookie
API, LIBMPUIO deliberately keeps three independent backends:
.IP 1. 3
file backend - native external UTF-8 file I/O;
.IP 2. 3
string backend - native UCS-2 memory I/O;
.IP 3. 3
cookie backend - arbitrary user-defined byte I/O.
.PP
In particular,
.B mpu_fmemopen
is not implemented through
.BR mpu_fopencookie .
Both are clients of the lower-level internal FILE_plus/jump-table abstraction.
.SH CALLBACK REENTRANCY
LIBMPUIO does not hold its global stream-list lock while invoking cookie
read, write, seek, or close callbacks. A callback may therefore create or
close other LIBMPUIO streams without deadlocking global all-stream traversal.
Streams already captured by such a traversal are lifetime-pinned until that
traversal moves past them.
.PP
A callback must not recursively close or otherwise re-enter the same cookie
stream whose backend operation is currently executing.
.SH EXAMPLE
The following small program builds a write-only cookie stream that forwards
its external bytes to standard output while counting them. It is useful for
showing the LIBMPUIO text boundary: the formatter counts UCS-2 characters,
while the cookie callback sees the encoded UTF-8 bytes.
.PP
.nf
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <libmpuio.h>
struct counting_sink
{
size_t bytes;
};
static ssize_t
counting_write( void *cookie, const char *buf, size_t size )
{
struct counting_sink *sink = cookie;
size_t done = 0;
while( done < size )
{
ssize_t n = write(STDOUT_FILENO, buf + done, size - done);
if( n < 0 )
{
if (errno == EINTR)
continue;
return -1;
}
if( n == 0 )
{
errno = EIO;
return -1;
}
done += (size_t)n;
}
sink->bytes += done;
return (ssize_t)done;
}
int
main(void)
{
struct counting_sink sink = { 0 };
mpu_cookie_io_functions_t io = {
.read = NULL,
.write = counting_write,
.seek = NULL,
.close = NULL
};
mpu_FILE *fp;
int nchars;
__mpu_init();
fp = mpu_fopencookie( &sink, "w", io );
if( fp == NULL )
return 1;
nchars = mpu_fprintf( fp,
MPU_UCS2("value = %d, pi = %c\\n"),
42, (int)0x03c0 );
if( nchars < 0 || mpu_fclose(fp) != 0 )
return 1;
mpu_printf( MPU_UCS2("UCS-2 chars: %d, UTF-8 bytes: %ju\\n"),
nchars, (uintmax_t)sink.bytes );
__mpu_free_context();
return 0;
}
.fi
.PP
For the formatted line above, the character count is 19 while the callback
observes 20 external bytes because U+03C0 is encoded as a two-byte UTF-8
sequence. The example deliberately uses a write-only nonseekable cookie; no
seek callback is needed for mode
.BR "w" .
.SH SEE ALSO
.BR libmpuio (3),
.BR mpu_fopen (3),
.BR mpu_fmemopen (3),
.BR mpu_fread (3),
.BR mpu_fseek (3),
.BR mpu_printf (3),
.BR mpu_scanf (3)
|