Line data Source code
1 : /*
2 : * SPDX-License-Identifier: MPL-2.0
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 : *
8 : * Copyright 2024 MonetDB Foundation;
9 : * Copyright August 2008 - 2023 MonetDB B.V.;
10 : * Copyright 1997 - July 2008 CWI.
11 : */
12 :
13 : /* streams working on a disk file */
14 :
15 : #include "monetdb_config.h"
16 : #include "stream.h"
17 : #include "stream_internal.h"
18 : #include "mutf8.h"
19 :
20 :
21 : /* ------------------------------------------------------------------ */
22 : /* streams working on a disk file */
23 :
24 :
25 : /* should be static but isn't because there are some other parts of the
26 : * library that have a specific fast path for stdio streams.
27 : */
28 : ssize_t
29 26819232 : file_read(stream *restrict s, void *restrict buf, size_t elmsize, size_t cnt)
30 : {
31 26819232 : FILE *fp = (FILE *) s->stream_data.p;
32 26819232 : size_t rc = 0;
33 :
34 26819232 : if (fp == NULL) {
35 0 : mnstr_set_error(s, MNSTR_READ_ERROR, "file ended");
36 0 : return -1;
37 : }
38 :
39 26819232 : if (elmsize && cnt) {
40 26819231 : errno = 0;
41 26819231 : if ((rc = fread(buf, elmsize, cnt, fp)) == 0 && ferror(fp)) {
42 0 : mnstr_set_error_errno(s, errno == EINTR ? MNSTR_INTERRUPT : MNSTR_READ_ERROR, "read error");
43 0 : return -1;
44 : }
45 26819231 : s->eof |= rc == 0 && feof(fp);
46 : }
47 26819232 : return (ssize_t) rc;
48 : }
49 :
50 :
51 : static ssize_t
52 40298845 : file_write(stream *restrict s, const void *restrict buf, size_t elmsize, size_t cnt)
53 : {
54 40298845 : FILE *fp = (FILE *) s->stream_data.p;
55 :
56 40298845 : if (fp == NULL) {
57 0 : mnstr_set_error(s, MNSTR_WRITE_ERROR, "file ended");
58 0 : return -1;
59 : }
60 :
61 40298845 : if (elmsize && cnt) {
62 40296093 : size_t rc = fwrite(buf, elmsize, cnt, fp);
63 :
64 40296093 : if (rc != cnt) {
65 : // only happens if fwrite encountered an error.
66 0 : mnstr_set_error_errno(s, MNSTR_WRITE_ERROR, "write error");
67 0 : return -1;
68 : }
69 40296093 : return (ssize_t) rc;
70 : }
71 2752 : return (ssize_t) cnt;
72 : }
73 :
74 :
75 : static void
76 54752 : file_close(stream *s)
77 : {
78 54752 : FILE *fp = (FILE *) s->stream_data.p;
79 :
80 54752 : if (fp == NULL)
81 : return;
82 27527 : if (fp != stdin && fp != stdout && fp != stderr) {
83 26365 : if (s->name && *s->name == '|')
84 0 : pclose(fp);
85 : else
86 26365 : fclose(fp);
87 1162 : } else if (!s->readonly)
88 679 : fflush(fp);
89 27527 : s->stream_data.p = NULL;
90 : }
91 :
92 :
93 : static void
94 27527 : file_destroy(stream *s)
95 : {
96 27527 : file_close(s);
97 27527 : destroy_stream(s);
98 27528 : }
99 :
100 :
101 : static void
102 0 : file_clrerr(stream *s)
103 : {
104 0 : FILE *fp = (FILE *) s->stream_data.p;
105 :
106 0 : if (fp)
107 0 : clearerr(fp);
108 0 : }
109 :
110 :
111 : static int
112 172878 : file_flush(stream *s, mnstr_flush_level flush_level)
113 : {
114 172878 : FILE *fp = (FILE *) s->stream_data.p;
115 :
116 172878 : if (fp == NULL || (!s->readonly && fflush(fp) < 0)) {
117 0 : mnstr_set_error_errno(s, MNSTR_WRITE_ERROR, "flush error");
118 0 : return -1;
119 : }
120 : (void) flush_level;
121 : return 0;
122 : }
123 :
124 :
125 : static int
126 33 : file_fsync(stream *s)
127 : {
128 :
129 33 : FILE *fp = (FILE *) s->stream_data.p;
130 :
131 33 : if (fp == NULL ||
132 33 : (!s->readonly
133 : #ifdef NATIVE_WIN32
134 : && _commit(fileno(fp)) < 0
135 : #else
136 : #ifdef HAVE_FDATASYNC
137 33 : && fdatasync(fileno(fp)) < 0
138 : #else
139 : #ifdef HAVE_FSYNC
140 : && fsync(fileno(fp)) < 0
141 : #endif
142 : #endif
143 : #endif
144 : )) {
145 0 : mnstr_set_error(s, MNSTR_WRITE_ERROR, "fsync failed");
146 0 : return -1;
147 : }
148 : return 0;
149 : }
150 :
151 :
152 : static int
153 0 : file_fgetpos(stream *restrict s, fpos_t *restrict p)
154 : {
155 0 : FILE *fp = (FILE *) s->stream_data.p;
156 :
157 0 : if (fp == NULL || p == NULL)
158 : return -1;
159 0 : return fgetpos(fp, p) ? -1 : 0;
160 : }
161 :
162 :
163 : static int
164 0 : file_fsetpos(stream *restrict s, fpos_t *restrict p)
165 : {
166 0 : FILE *fp = (FILE *) s->stream_data.p;
167 :
168 0 : if (fp == NULL || p == NULL)
169 : return -1;
170 0 : return fsetpos(fp, p) ? -1 : 0;
171 : }
172 :
173 : /* convert a string from UTF-8 to wide characters; the return value is
174 : * freshly allocated */
175 : #ifdef NATIVE_WIN32
176 : static wchar_t *
177 : utf8towchar(const char *src)
178 : {
179 : wchar_t *dest;
180 : size_t i = 0;
181 : uint32_t state = 0, codepoint = 0;
182 :
183 : /* count how many wchar_t's we need, while also checking for
184 : * correctness of the input */
185 : for (size_t j = 0; src[j]; j++) {
186 : switch (decode(&state, &codepoint, (uint8_t) src[j])) {
187 : case UTF8_ACCEPT:
188 : i++;
189 : #if SIZEOF_WCHAR_T == 2
190 : i += (codepoint > 0xFFFF);
191 : #endif
192 : break;
193 : case UTF8_REJECT:
194 : return NULL;
195 : default:
196 : break;
197 : }
198 : }
199 : dest = malloc((i + 1) * sizeof(wchar_t));
200 : if (dest == NULL)
201 : return NULL;
202 : /* go through the source string again, this time we can skip
203 : * the correctness tests */
204 : i = 0;
205 : for (size_t j = 0; src[j]; j++) {
206 : switch (decode(&state, &codepoint, (uint8_t) src[j])) {
207 : case UTF8_ACCEPT:
208 : #if SIZEOF_WCHAR_T == 2
209 : if (codepoint <= 0xFFFF) {
210 : dest[i++] = (wchar_t) codepoint;
211 : } else {
212 : dest[i++] = (wchar_t) (0xD7C0 + (codepoint >> 10));
213 : dest[i++] = (wchar_t) (0xDC00 + (codepoint & 0x3FF));
214 : }
215 : #else
216 : dest[i++] = (wchar_t) codepoint;
217 : #endif
218 : break;
219 : case UTF8_REJECT:
220 : /* cannot happen because of first loop */
221 : free(dest);
222 : return NULL;
223 : default:
224 : break;
225 : }
226 : }
227 : dest[i] = 0;
228 : return dest;
229 : }
230 : #endif
231 :
232 :
233 : stream *
234 26540 : open_stream(const char *restrict filename, const char *restrict flags)
235 : {
236 26540 : stream *s;
237 26540 : FILE *fp;
238 26540 : fpos_t pos;
239 26540 : char buf[UTF8BOMLENGTH + 1];
240 :
241 26540 : if ((s = create_stream(filename)) == NULL)
242 : return NULL;
243 : #ifdef NATIVE_WIN32
244 : {
245 : wchar_t *wfname = utf8towchar(filename);
246 : wchar_t *wflags = utf8towchar(flags);
247 : if (wfname != NULL && wflags != NULL)
248 : fp = _wfopen(wfname, wflags);
249 : else
250 : fp = NULL;
251 : if (wfname)
252 : free(wfname);
253 : if (wflags)
254 : free(wflags);
255 : }
256 : #else
257 26540 : fp = fopen(filename, flags);
258 : #endif
259 26540 : if (fp == NULL) {
260 159 : mnstr_set_open_error(filename, errno, "open failed");
261 159 : destroy_stream(s);
262 159 : return NULL;
263 : }
264 26381 : s->readonly = flags[0] == 'r';
265 26381 : s->binary = flags[1] == 'b';
266 26381 : s->read = file_read;
267 26381 : s->write = file_write;
268 26381 : s->close = file_close;
269 26381 : s->destroy = file_destroy;
270 26381 : s->clrerr = file_clrerr;
271 26381 : s->flush = file_flush;
272 26381 : s->fsync = file_fsync;
273 26381 : s->fgetpos = file_fgetpos;
274 26381 : s->fsetpos = file_fsetpos;
275 26381 : s->stream_data.p = (void *) fp;
276 : /* if a text file is opened for reading, and it starts with
277 : * the UTF-8 encoding of the Unicode Byte Order Mark, skip the
278 : * mark, and mark the stream as being a UTF-8 stream */
279 26381 : if (flags[0] == 'r' && flags[1] != 'b' && fgetpos(fp, &pos) == 0) {
280 0 : if (file_read(s, buf, 1, UTF8BOMLENGTH) == UTF8BOMLENGTH &&
281 0 : strncmp(buf, UTF8BOM, UTF8BOMLENGTH) == 0)
282 0 : s->isutf8 = true;
283 0 : else if (fsetpos(fp, &pos) != 0) {
284 : /* unlikely: we couldn't seek the file back */
285 0 : fclose(fp);
286 0 : destroy_stream(s);
287 0 : return NULL;
288 : }
289 : }
290 : return s;
291 : }
292 :
293 : stream *
294 1214 : file_stream(const char *name)
295 : {
296 1214 : stream *s;
297 :
298 1214 : if ((s = create_stream(name)) == NULL)
299 : return NULL;
300 1214 : s->read = file_read;
301 1214 : s->write = file_write;
302 1214 : s->close = file_close;
303 1214 : s->destroy = file_destroy;
304 1214 : s->flush = file_flush;
305 1214 : s->fsync = file_fsync;
306 1214 : s->fgetpos = file_fgetpos;
307 1214 : s->fsetpos = file_fsetpos;
308 1214 : return s;
309 : }
310 :
311 : stream *
312 487 : file_rstream(FILE *restrict fp, bool binary, const char *restrict name)
313 : {
314 487 : stream *s;
315 :
316 487 : if (fp == NULL)
317 : return NULL;
318 : #ifdef STREAM_DEBUG
319 : fprintf(stderr, "file_rstream %s\n", name);
320 : #endif
321 487 : if ((s = file_stream(name)) == NULL)
322 : return NULL;
323 487 : s->binary = binary;
324 487 : s->stream_data.p = (void *) fp;
325 487 : return s;
326 : }
327 :
328 : stream *
329 727 : file_wstream(FILE *restrict fp, bool binary, const char *restrict name)
330 : {
331 727 : stream *s;
332 :
333 727 : if (fp == NULL)
334 : return NULL;
335 : #ifdef STREAM_DEBUG
336 : fprintf(stderr, "file_wstream %s\n", name);
337 : #endif
338 727 : if ((s = file_stream(name)) == NULL)
339 : return NULL;
340 727 : s->readonly = false;
341 727 : s->binary = binary;
342 727 : s->stream_data.p = (void *) fp;
343 727 : return s;
344 : }
345 :
346 :
347 : stream *
348 487 : stdin_rastream(void)
349 : {
350 487 : const char name[] = "<stdin>";
351 : // Make an attempt to skip a BOM marker.
352 : // It would be nice to integrate this with with the BOM removal code
353 : // in text_stream.c but that is complicated. In text_stream,
354 487 : struct stat stb;
355 487 : if (fstat(fileno(stdin), &stb) == 0 && (stb.st_mode & S_IFMT) == S_IFREG) {
356 47 : fpos_t pos;
357 47 : if (fgetpos(stdin, &pos) == 0) {
358 47 : char bytes[UTF8BOMLENGTH];
359 47 : size_t nread = fread(bytes, 1, UTF8BOMLENGTH, stdin);
360 47 : if (nread != 3 || memcmp(bytes, UTF8BOM, UTF8BOMLENGTH) != 0) {
361 : // not a BOM, rewind
362 47 : if (nread > 0 && fsetpos(stdin, &pos) != 0) {
363 : // oops, bytes have been read but we can't rewind
364 0 : mnstr_set_error_errno(NULL, MNSTR_OPEN_ERROR, "while rewinding after checking for byte order mark");
365 0 : return NULL;
366 : }
367 : }
368 : }
369 : }
370 :
371 : #ifdef _MSC_VER
372 : return win_console_in_stream(name);
373 : #else
374 487 : return file_rstream(stdin, false, name);
375 : #endif
376 : }
377 :
378 : stream *
379 552 : stdout_wastream(void)
380 : {
381 552 : const char name[] = "<stdout>";
382 : #ifdef _MSC_VER
383 : if (isatty(fileno(stdout)))
384 : return win_console_out_stream(name);
385 : #endif
386 552 : return file_wstream(stdout, false, name);
387 : }
388 :
389 : stream *
390 175 : stderr_wastream(void)
391 : {
392 175 : const char name[] = "<stderr>";
393 : #ifdef _MSC_VER
394 : if (isatty(fileno(stderr)))
395 : return win_console_out_stream(name);
396 : #endif
397 175 : return file_wstream(stderr, false, name);
398 : }
399 :
400 : /* some lower-level access functions */
401 : FILE *
402 65017 : getFile(stream *s)
403 : {
404 65022 : for (; s != NULL; s = s->inner) {
405 : #ifdef _MSC_VER
406 : if (s->read == console_read)
407 : return stdin;
408 : if (s->write == console_write)
409 : return stdout;
410 : #endif
411 65022 : if (s->read == file_read)
412 65017 : return (FILE *) s->stream_data.p;
413 : }
414 :
415 : return NULL;
416 : }
417 :
418 : int
419 351 : getFileNo(stream *s)
420 : {
421 351 : FILE *f;
422 :
423 351 : f = getFile(s);
424 351 : if (f == NULL)
425 : return -1;
426 351 : return fileno(f);
427 : }
428 :
429 : size_t
430 0 : getFileSize(stream *s)
431 : {
432 0 : struct stat stb;
433 0 : int fd = getFileNo(s);
434 :
435 0 : if (fd >= 0 && fstat(fd, &stb) == 0)
436 0 : return (size_t) stb.st_size;
437 : return 0; /* unknown */
438 : }
439 :
440 : int
441 0 : file_remove(const char *filename)
442 : {
443 0 : int rc = -1;
444 :
445 : #ifdef NATIVE_WIN32
446 : wchar_t *wfname = utf8towchar(filename);
447 : if (wfname != NULL) {
448 : rc = _wremove(wfname);
449 : free(wfname);
450 : }
451 : #else
452 0 : rc = remove(filename);
453 : #endif
454 0 : return rc;
455 : }
|