summaryrefslogtreecommitdiff
path: root/libc/stdio/_fopen.c
blob: 5bc61cf3910915815272f915f1c4deb545672031 (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
/* 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"


/*
 * Cases:
 *  fopen64  : filename != NULL, stream == NULL, filedes == -2
 *  fopen    : filename != NULL, stream == NULL, filedes == -1
 *  freopen  : filename != NULL, stream != NULL, filedes == -1
 *  fdopen   : filename == NULL, stream == NULL, filedes valid
 *
 *  fsfopen  : filename != NULL, stream != NULL, filedes == -1
 */

#if (O_ACCMODE != 3) || (O_RDONLY != 0) || (O_WRONLY != 1) || (O_RDWR != 2)
#error Assumption violated - mode constants
#endif

#ifndef O_LARGEFILE
#define O_LARGEFILE 0
#endif

/* Internal function -- reentrant (locks open file list) */

FILE attribute_hidden *_stdio_fopen(intptr_t fname_or_mode,
				   register const char * __restrict mode,
				   register FILE * __restrict stream, int filedes)
{
	__mode_t open_mode;
	int i;

	/* Parse the specified mode. */
	open_mode = O_RDONLY;
	if (*mode != 'r') {			/* Not read... */
		open_mode = (O_WRONLY | O_CREAT | O_TRUNC);
		if (*mode != 'w') {		/* Not write (create or truncate)... */
			open_mode = (O_WRONLY | O_CREAT | O_APPEND);
			if (*mode != 'a') {	/* Not write (create or append)... */
			DO_EINVAL:
				__set_errno(EINVAL); /* So illegal mode. */
				if (stream) {
				FREE_STREAM:
					assert(!(stream->__modeflags & __FLAG_FREEBUF));
					__STDIO_STREAM_FREE_FILE(stream);
				}
				return NULL;
			}
		}
	}

	if ((mode[1] == 'b')) {		/* Binary mode (NO-OP currently). */
		++mode;
	}

	if (mode[1] == '+') {			/* Read and Write. */
		++mode;
		open_mode |= (O_RDONLY | O_WRONLY);
		open_mode += (O_RDWR - (O_RDONLY | O_WRONLY));
	}

#ifdef __UCLIBC_MJN3_ONLY__
#warning CONSIDER: Implement glibc ccs option to bind a codeset?
#warning CONSIDER: Implement glibc mmap option for readonly files?
#warning CONSIDER: Implement a text mode using custom read/write funcs?
#endif
#if defined(__UCLIBC_HAS_FOPEN_EXCLUSIVE_MODE__) || defined(__UCLIBC_HAS_FOPEN_LARGEFILE_MODE__) || \
    defined(__UCLIBC_HAS_FOPEN_CLOSEEXEC_MODE__)

	while (*++mode) {
# ifdef __UCLIBC_HAS_FOPEN_EXCLUSIVE_MODE__
		if (*mode == 'x') {	   /* Open exclusive (a glibc extension). */
			open_mode |= O_EXCL;
			continue;
		}
# endif
# ifdef __UCLIBC_HAS_FOPEN_LARGEFILE_MODE__
		if (*mode == 'F') {		/* Open as large file (uClibc extension). */
			open_mode |= O_LARGEFILE;
			continue;
		}
# endif
# ifdef __UCLIBC_HAS_FOPEN_CLOSEEXEC_MODE__
		if (*mode == 'e') {		/* Close on exec (a glibc extension). */
			open_mode |= O_CLOEXEC;
			continue;
		}
# endif
 	}

#endif

	if (!stream) {		  /* Need to allocate a FILE (not freopen). */
		if ((stream = malloc(sizeof(FILE))) == NULL) {
			return stream;
		}
		stream->__modeflags = __FLAG_FREEFILE;
#ifdef __STDIO_BUFFERS
		stream->__bufstart = NULL; /* We allocate a buffer below. */
#endif
#ifdef __UCLIBC_HAS_THREADS__
		/* We only initialize the mutex in the non-freopen case. */
		/* stream->__user_locking = _stdio_user_locking; */
		STDIO_INIT_MUTEX(stream->__lock);
#endif
	}

#ifdef __UCLIBC_MJN3_ONLY__
#warning TODO: Verify fdopen append behavior of glibc.
#endif

	if (filedes >= 0) {			/* Handle fdopen trickery. */
		stream->__filedes = filedes;
		/* NOTE: it is insufficient to just check R/W/RW agreement.
		 * We must also check largefile compatibility if applicable.
		 * Also, if append mode is desired for fdopen but O_APPEND isn't
		 * currently set, then set it as recommended by SUSv3.  However,
		 * if append mode is not specified for fdopen but O_APPEND is set,
		 * leave it set (glibc compat). */
		i = (open_mode & (O_ACCMODE|O_LARGEFILE)) + 1;

		/* NOTE: fopencookie needs changing if the basic check changes! */
		if ((i & ((int)fname_or_mode + 1)) != i) /* Basic agreement? */
			goto DO_EINVAL;
		if ((open_mode & ~(__mode_t)fname_or_mode) & O_APPEND) {
			if (fcntl(filedes, F_SETFL, O_APPEND))	/* Need O_APPEND. */
				goto DO_EINVAL;
		}
		/* For later... to reflect largefile setting in stream flags. */
		__STDIO_WHEN_LFS( open_mode |= (((__mode_t) fname_or_mode)
										& O_LARGEFILE) );
	} else {
		__STDIO_WHEN_LFS( if (filedes < -1) open_mode |= O_LARGEFILE );
		if ((stream->__filedes = open(((const char *) fname_or_mode),
									  open_mode, 0666)) < 0) {
			goto FREE_STREAM;
		}
	}

	stream->__modeflags &= __FLAG_FREEFILE;
/* 	stream->__modeflags &= ~(__FLAG_READONLY|__FLAG_WRITEONLY); */

	stream->__modeflags |=		/* Note: Makes assumptions about flag vals! */
#if (O_APPEND != __FLAG_APPEND) || ((O_LARGEFILE != __FLAG_LARGEFILE) && (O_LARGEFILE != 0))
# if (O_APPEND != __FLAG_APPEND)
		((open_mode & O_APPEND) ? __FLAG_APPEND : 0) |
# else
		(open_mode & O_APPEND) |
# endif
# if (O_LARGEFILE != __FLAG_LARGEFILE) && (O_LARGEFILE != 0)
		((open_mode & O_LARGEFILE) ? __FLAG_LARGEFILE : 0) |
# else
		(open_mode & O_LARGEFILE) |
# endif
#else
		(open_mode & (O_APPEND|O_LARGEFILE)) | /* i386 linux and elks */
#endif
		((((open_mode & O_ACCMODE) + 1) ^ 0x03) * __FLAG_WRITEONLY);

#ifdef __STDIO_BUFFERS
	if (stream->__filedes != INT_MAX) {
		/* NB: fopencookie uses bogus filedes == INT_MAX,
		 * avoiding isatty() in that case.
		 */
		i = errno; /* preserve errno against isatty call. */
		if (isatty(stream->__filedes))
			stream->__modeflags |= __FLAG_LBF;
		__set_errno(i);
	}

	if (!stream->__bufstart) {
		if ((stream->__bufstart = malloc(BUFSIZ)) != NULL) {
			stream->__bufend = stream->__bufstart + BUFSIZ;
			stream->__modeflags |= __FLAG_FREEBUF;
		} else {
# if __STDIO_BUILTIN_BUF_SIZE > 0
			stream->__bufstart = stream->__builtinbuf;
			stream->__bufend = stream->__builtinbuf + sizeof(stream->__builtinbuf);
# else  /* __STDIO_BUILTIN_BUF_SIZE > 0 */
			stream->__bufend = stream->__bufstart;
# endif /* __STDIO_BUILTIN_BUF_SIZE > 0 */
		}
	}

	__STDIO_STREAM_DISABLE_GETC(stream);
	__STDIO_STREAM_DISABLE_PUTC(stream);
	__STDIO_STREAM_INIT_BUFREAD_BUFPOS(stream);
#endif

	__STDIO_STREAM_RESET_GCS(stream);

#ifdef __UCLIBC_HAS_WCHAR__
	stream->__ungot_width[0] = 0;
#endif
#ifdef __STDIO_MBSTATE
	__INIT_MBSTATE(&(stream->__state));
#endif

#ifdef __UCLIBC_HAS_THREADS__
	/* Even in the freopen case, we reset the user locking flag. */
	stream->__user_locking = _stdio_user_locking;
	/* STDIO_INIT_MUTEX(stream->__lock); */
#endif

#ifdef __STDIO_HAS_OPENLIST
#if defined(__UCLIBC_HAS_THREADS__) && defined(__STDIO_BUFFERS)
	if (!(stream->__modeflags & __FLAG_FREEFILE))
	{
		/* An freopen call so the file was never removed from the list. */
	}
	else
#endif
	{
		/* We have to lock the del mutex in case another thread wants to fclose()
		 * the last file. */
		__STDIO_THREADLOCK_OPENLIST_DEL;
		__STDIO_THREADLOCK_OPENLIST_ADD;
		stream->__nextopen = _stdio_openlist; /* New files are inserted at */
		_stdio_openlist = stream;			  /*   the head of the list. */
		__STDIO_THREADUNLOCK_OPENLIST_ADD;
		__STDIO_THREADUNLOCK_OPENLIST_DEL;
	}
#endif

	__STDIO_STREAM_VALIDATE(stream);

	return stream;
}