summaryrefslogtreecommitdiffstats
path: root/libc/stdio/_stdio.c
blob: d8c0ae20da6fa4b9226337895ad1882821610b3e (plain)
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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/* Copyright (C) 2004       Manuel Novoa III    <mjn3@codepoet.org>
 *
 * GNU Library General Public License (LGPL) version 2 or later.
 *
 * Dedicated to Toni.  See uClibc/DEDICATION.mjn3 for details.
 */

#include "_stdio.h"

/* Experimentally off - libc_hidden_proto(memcpy) */
libc_hidden_proto(isatty)

/* This is pretty much straight from uClibc, but with one important
 * difference.
 *
 * We now initialize the locking flag to user locking instead of
 * auto locking (i.e. FSETLOCKING_BYCALLER vs FSETLOCKING_INTERNAL).
 * This greatly benefits non-threading applications linked to a
 * shared thread-enabled library.  In threading applications, we
 * walk the stdio open file list and reset the locking mode
 * appropriately when the thread subsystem is initialized.
 */

/**********************************************************************/

#ifdef __UCLIBC_HAS_WCHAR__
#define __STDIO_FILE_INIT_WUNGOT		{ 0, 0 },
#else
#define __STDIO_FILE_INIT_WUNGOT
#endif

#ifdef __STDIO_GETC_MACRO
# define __STDIO_FILE_INIT_BUFGETC(x) x,
#else
# define __STDIO_FILE_INIT_BUFGETC(x)
#endif

#ifdef __STDIO_PUTC_MACRO
# define __STDIO_FILE_INIT_BUFPUTC(x) x,
#else
# define __STDIO_FILE_INIT_BUFPUTC(x)
#endif

#ifdef __STDIO_HAS_OPENLIST
#define __STDIO_FILE_INIT_NEXT(next)	(next),
#else
#define __STDIO_FILE_INIT_NEXT(next)
#endif

#ifdef __STDIO_BUFFERS
#define __STDIO_FILE_INIT_BUFFERS(buf,bufsize) \
	(buf), (buf)+(bufsize), (buf), (buf),
#else
#define __STDIO_FILE_INIT_BUFFERS(buf,bufsize)
#endif

#ifdef __UCLIBC_HAS_GLIBC_CUSTOM_STREAMS__
#define __STDIO_FILE_INIT_CUSTOM_STREAM(stream) \
	&((stream).__filedes), { _cs_read, _cs_write, _cs_seek, _cs_close },
#else
#define __STDIO_FILE_INIT_CUSTOM_STREAM(stream)
#endif

#ifdef __STDIO_MBSTATE
#define __STDIO_FILE_INIT_MBSTATE \
	{ 0, 0 },
#else
#define __STDIO_FILE_INIT_MBSTATE
#endif

#ifdef __UCLIBC_HAS_XLOCALE__
#define __STDIO_FILE_INIT_UNUSED \
	NULL,
#else
#define __STDIO_FILE_INIT_UNUSED
#endif

#ifdef __UCLIBC_HAS_THREADS__
#define __STDIO_FILE_INIT_THREADSAFE \
	2, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
#else
#define __STDIO_FILE_INIT_THREADSAFE
#endif

#define __STDIO_INIT_FILE_STRUCT(stream, flags, filedes, next, buf, bufsize) \
	{ (flags), \
	{ 0, 0 }, /* ungot[2] (no wchar) or ungot_width[2] (wchar)*/ \
	(filedes), \
	__STDIO_FILE_INIT_BUFFERS(buf,bufsize) \
	__STDIO_FILE_INIT_BUFGETC((buf)) \
	__STDIO_FILE_INIT_BUFPUTC((buf)) \
	__STDIO_FILE_INIT_NEXT(next) \
	__STDIO_FILE_INIT_CUSTOM_STREAM(stream) \
	__STDIO_FILE_INIT_WUNGOT \
	__STDIO_FILE_INIT_MBSTATE \
	__STDIO_FILE_INIT_UNUSED \
	__STDIO_FILE_INIT_THREADSAFE \
} /* TODO: builtin buf */

/**********************************************************************/
/* First we need the standard files. */

#ifdef __STDIO_BUFFERS
static unsigned char _fixed_buffers[2 * BUFSIZ];
#endif

static FILE _stdio_streams[] = {
	__STDIO_INIT_FILE_STRUCT(_stdio_streams[0], \
							 __FLAG_LBF|__FLAG_READONLY, \
							 0, \
							 _stdio_streams + 1, \
							 _fixed_buffers, \
							 BUFSIZ ),
	__STDIO_INIT_FILE_STRUCT(_stdio_streams[1], \
							 __FLAG_LBF|__FLAG_WRITEONLY, \
							 1, \
							 _stdio_streams + 2, \
							 _fixed_buffers + BUFSIZ, \
							 BUFSIZ ),
	__STDIO_INIT_FILE_STRUCT(_stdio_streams[2], \
							 __FLAG_NBF|__FLAG_WRITEONLY, \
							 2, \
							 NULL, \
							 NULL, \
							 0 )
};

FILE *stdin  = _stdio_streams;
FILE *stdout = _stdio_streams + 1;
FILE *stderr = _stdio_streams + 2;

#ifdef __STDIO_GETC_MACRO
FILE *__stdin = _stdio_streams;		 /* For getchar() macro. */
#endif
#ifdef __STDIO_PUTC_MACRO
FILE *__stdout = _stdio_streams + 1; /* For putchar() macro. */
/* FILE *__stderr = _stdio_streams + 2; */
#endif

/**********************************************************************/
#ifdef __STDIO_HAS_OPENLIST

/* In certain configurations, we need to keep a list of open files.
 * 1) buffering enabled - We need to initialize the buffering mode
 *       (full or line buffering) of stdin and stdout.  We also
 *       need to flush all write buffers prior to normal termination.
 * 2) custom streams - Even if we aren't buffering in the library
 *       itself, we need to fclose() all custom streams when terminating
 *       so that any special cleanup is done.
 * 3) threads enabled - We need to be able to reset the locking mode
 *       of all open streams when the threading system is initialized.
 */

FILE *_stdio_openlist = _stdio_streams;

# ifdef __UCLIBC_HAS_THREADS__
__UCLIBC_MUTEX_INIT(_stdio_openlist_add_lock, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP);
#ifdef __STDIO_BUFFERS
__UCLIBC_MUTEX_INIT(_stdio_openlist_del_lock, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP);
volatile int _stdio_openlist_use_count = 0;
int _stdio_openlist_del_count = 0;
#endif
# endif

#endif
/**********************************************************************/
#ifdef __UCLIBC_HAS_THREADS__

/* 2 if threading not initialized and 0 otherwise; */
int _stdio_user_locking = 2;

void attribute_hidden __stdio_init_mutex(__UCLIBC_MUTEX_TYPE *m)
{
	const __UCLIBC_MUTEX_STATIC(__stdio_mutex_initializer,
		PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP);

	memcpy(m, &__stdio_mutex_initializer, sizeof(__stdio_mutex_initializer));
}

#endif
/**********************************************************************/

/* We assume here that we are the only remaining thread. */
void attribute_hidden _stdio_term(void)
{
#if defined(__STDIO_BUFFERS) || defined(__UCLIBC_HAS_GLIBC_CUSTOM_STREAMS__)
	register FILE *ptr;

#ifdef __UCLIBC_HAS_THREADS__
	/* First, make sure the open file list is unlocked.  If it was
	 * locked, then I suppose there is a chance that a pointer in the
	 * chain might be corrupt due to a partial store.
	 */
	__stdio_init_mutex(&_stdio_openlist_add_lock);
#warning check
#ifdef __STDIO_BUFFERS
	__stdio_init_mutex(&_stdio_openlist_del_lock);
#endif

	/* Next we need to worry about the streams themselves.  If a stream
	 * is currently locked, then it may be in an invalid state.  So we
	 * 'disable' it in case a custom stream is stacked on top of it.
	 * Then we reinitialize the locks.
	 */
	for (ptr = _stdio_openlist ; ptr ; ptr = ptr->__nextopen ) {
		if (__STDIO_ALWAYS_THREADTRYLOCK_CANCEL_UNSAFE(ptr)) {
			/* The stream is already locked, so we don't want to touch it.
			 * However, if we have custom streams, we can't just close it
			 * or leave it locked since a custom stream may be stacked
			 * on top of it.  So we do unlock it, while also disabling it.
			 */
			ptr->__modeflags = (__FLAG_READONLY|__FLAG_WRITEONLY);
			__STDIO_STREAM_DISABLE_GETC(ptr);
			__STDIO_STREAM_DISABLE_PUTC(ptr);
			__STDIO_STREAM_INIT_BUFREAD_BUFPOS(ptr);
		}

		ptr->__user_locking = 1; /* Set locking mode to "by caller". */
		__stdio_init_mutex(&ptr->__lock); /* Shouldn't be necessary, but... */
	}
#endif

	/* Finally, flush all writing streams and shut down all custom streams.
	 * NOTE: We assume that any stacking by custom streams is done on top
	 *       of streams previously allocated, and hence further down the
	 *       list.  Otherwise we have no way of knowing the order in which
	 *       to shut them down.
	 *       Remember that freopen() counts as a new allocation here, even
	 *       though the stream is reused.  That's because it moves the
	 *       stream to the head of the list.
	 */
	for (ptr = _stdio_openlist ; ptr ; ptr = ptr->__nextopen ) {
#ifdef __STDIO_BUFFERS
		/* Write any pending buffered chars. */
		if (__STDIO_STREAM_IS_WRITING(ptr)) {
			__STDIO_COMMIT_WRITE_BUFFER(ptr);
		}
#endif
#ifdef __UCLIBC_HAS_GLIBC_CUSTOM_STREAMS__
		/* Actually close all custom streams to perform any special cleanup. */
		if (ptr->__cookie != &ptr->__filedes) {
			__CLOSE(ptr);
		}
#endif
	}

#endif
}

#if defined __STDIO_BUFFERS || !defined __UCLIBC__
void attribute_hidden _stdio_init(void)
{
#ifdef __STDIO_BUFFERS
	int old_errno = errno;
	/* stdin and stdout uses line buffering when connected to a tty. */
	_stdio_streams[0].__modeflags ^= (1-isatty(0)) * __FLAG_LBF;
	_stdio_streams[1].__modeflags ^= (1-isatty(1)) * __FLAG_LBF;
	__set_errno(old_errno);
#endif
#ifndef __UCLIBC__
	/* _stdio_term is done automatically when exiting if stdio is used.
	 * See misc/internals/__uClibc_main.c and and stdlib/atexit.c. */
	atexit(_stdio_term);
#endif
}
#endif

/**********************************************************************/

#if !(__MASK_READING & __FLAG_UNGOT)
#error Assumption violated about __MASK_READING and __FLAG_UNGOT
#endif

#ifdef __UCLIBC_HAS_THREADS__
#include <pthread.h>
#endif

#ifndef NDEBUG

void _stdio_validate_FILE(const FILE *stream)
{
#ifdef __UCLIBC_HAS_THREADS__
	assert(((unsigned int)(stream->__user_locking)) <= 2);
#endif

#warning Define a constant for minimum possible valid __filedes?
	assert(stream->__filedes >= -3);

	if (stream->__filedes < 0) {
/* 		assert((stream->__filedes != -1) */
/* #ifdef __UCLIBC_HAS_GLIBC_CUSTOM_STREAMS__ */
/* 			   || (stream->__cookie == &stream->__filedes) /\* custom *\/ */
/* #endif */
/* 			   ); */
/* 		assert((stream->__filedes == -1) || __STDIO_STREAM_IS_FBF(stream)); */

		assert(!__STDIO_STREAM_IS_FAKE_VSNPRINTF(stream)
			   || __STDIO_STREAM_IS_NARROW(stream));
		assert(!__STDIO_STREAM_IS_FAKE_VSSCANF(stream)
			   || __STDIO_STREAM_IS_NARROW(stream));
#ifdef __STDIO_STREAM_IS_FAKE_VSWPRINTF
		assert(!__STDIO_STREAM_IS_FAKE_VSWPRINTF(stream)
			   || __STDIO_STREAM_IS_WIDE(stream));
#endif
#ifdef __STDIO_STREAM_IS_FAKE_VSWSCANF
		assert(!__STDIO_STREAM_IS_FAKE_VSWSCANF(stream)
			   || __STDIO_STREAM_IS_WIDE(stream));
#endif
	}

#ifdef __UCLIBC_HAS_GLIBC_CUSTOM_STREAMS__
	if (stream->__cookie != &stream->__filedes) { /* custom */
		assert(stream->__filedes == -1);
	}
#endif

	/* Can not be both narrow and wide oriented at the same time. */
	assert(!(__STDIO_STREAM_IS_NARROW(stream)
			 && __STDIO_STREAM_IS_WIDE(stream)));


	/* The following impossible case is used to disable a stream. */
	if ((stream->__modeflags & (__FLAG_READONLY|__FLAG_WRITEONLY))
		== (__FLAG_READONLY|__FLAG_WRITEONLY)
		) {
		assert(stream->__modeflags == (__FLAG_READONLY|__FLAG_WRITEONLY));
		assert(stream->__filedes == -1);
#ifdef __STDIO_BUFFERS
		assert(stream->__bufpos == stream->__bufstart);
		assert(stream->__bufread == stream->__bufstart);
# ifdef __UCLIBC_HAS_STDIO_PUTC_MACRO__
		assert(stream->__bufputc_u == stream->__bufstart);
# endif
# ifdef __UCLIBC_HAS_STDIO_GETC_MACRO__
		assert(stream->__bufgetc_u == stream->__bufstart);
# endif
#endif
	}

	if (__STDIO_STREAM_IS_READONLY(stream)) {
/* 		assert(!__STDIO_STREAM_IS_WRITEONLY(stream)); */
		assert(!__STDIO_STREAM_IS_WRITING(stream));
		if (stream->__modeflags & __FLAG_UNGOT) {
			assert(((unsigned)(stream->__ungot[1])) <= 1);
			assert(!__FEOF_UNLOCKED(stream));
		}
	}

	if (__STDIO_STREAM_IS_WRITEONLY(stream)) {
/* 		assert(!__STDIO_STREAM_IS_READONLY(stream)); */
		assert(!__STDIO_STREAM_IS_READING(stream));
		assert(!(stream->__modeflags & __FLAG_UNGOT));
	}

	if (__STDIO_STREAM_IS_NBF(stream)) {
		/* We require that all non buffered streams have no buffer. */
		assert(!__STDIO_STREAM_BUFFER_SIZE(stream));
	}

	assert((stream->__modeflags & __MASK_BUFMODE) <= __FLAG_NBF);

#ifdef __STDIO_BUFFERS
	/* Ensure __bufstart <= __bufpos <= __bufend. */
	assert(stream->__bufpos >= stream->__bufstart);
	assert(stream->__bufpos <= stream->__bufend);
	/* Ensure __bufstart <= __bufread <= __bufend. */
	assert(stream->__bufread >= stream->__bufstart);
	assert(stream->__bufread <= stream->__bufend);
#endif

	/* If EOF, then we must have no buffered readable or ungots. */
	if (__FEOF_UNLOCKED(stream)) {
#ifdef __STDIO_BUFFERS
		assert(stream->__bufpos == stream->__bufread);
#endif
		assert(!(stream->__modeflags & __FLAG_UNGOT));
	}


	if (!__STDIO_STREAM_IS_WRITING(stream)) {
#ifdef __STDIO_BUFFERS
		/* If not writing, then putc macro must be disabled. */
# ifdef __UCLIBC_HAS_STDIO_PUTC_MACRO__
		assert(stream->__bufputc_u == stream->__bufstart);
# endif
#endif
	}

	if (!__STDIO_STREAM_IS_READING(stream)) {
		/* If not reading, then can not have ungots. */
		assert(!(stream->__modeflags & __FLAG_UNGOT));
#ifdef __STDIO_BUFFERS
		/* Ensure __bufread == __bufstart. */
		assert(stream->__bufread == stream->__bufstart);
		/* If not reading, then getc macro must be disabled. */
# ifdef __UCLIBC_HAS_STDIO_GETC_MACRO__
		assert(stream->__bufgetc_u == stream->__bufstart);
# endif
#endif
	}

	if (__STDIO_STREAM_IS_READING(stream)) {
		assert(!__STDIO_STREAM_IS_WRITING(stream));
#ifdef __STDIO_BUFFERS
		/* Ensure __bufpos <= __bufread. */
		assert(stream->__bufpos <= stream->__bufread);

		/* Ensure __bufgetc_u is valid. */
# ifdef __UCLIBC_HAS_STDIO_GETC_MACRO__
		assert(stream->__bufgetc_u >= stream->__bufstart);
		assert(stream->__bufgetc_u <= stream->__bufread);
# endif

#endif
	}

	if (__STDIO_STREAM_IS_WRITING(stream)) {
		assert(!__STDIO_STREAM_IS_READING(stream));
#ifdef __STDIO_BUFFERS
# ifdef __UCLIBC_HAS_STDIO_PUTC_MACRO__
		assert(stream->__bufputc_u >= stream->__bufstart);
		assert(stream->__bufputc_u <= stream->__bufend);
# endif
#endif
	}

	/* If have an ungotten char, then getc (and putc) must be disabled. */
	/* Also, wide streams must have the getc/putc macros disabled. */
	if ((stream->__modeflags & __FLAG_UNGOT)
		|| __STDIO_STREAM_IS_WIDE(stream)
		) {
#ifdef __STDIO_BUFFERS
# ifdef __UCLIBC_HAS_STDIO_PUTC_MACRO__
		assert(stream->__bufputc_u == stream->__bufstart);
# endif
# ifdef __UCLIBC_HAS_STDIO_GETC_MACRO__
		assert(stream->__bufgetc_u == stream->__bufstart);
# endif
#endif
	}

	/* TODO -- filepos?  ungot_width?  filedes?  nextopen? */
}

#endif