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 :
19 :
20 : /* ------------------------------------------------------------------ */
21 : /* streams working on a disk file */
22 :
23 :
24 : /* should be static but isn't because there are some other parts of the
25 : * library that have a specific fast path for stdio streams.
26 : */
27 : ssize_t
28 26533827 : file_read(stream *restrict s, void *restrict buf, size_t elmsize, size_t cnt)
29 : {
30 26533827 : FILE *fp = (FILE *) s->stream_data.p;
31 26533827 : size_t rc = 0;
32 :
33 26533827 : if (fp == NULL) {
34 0 : mnstr_set_error(s, MNSTR_READ_ERROR, "file ended");
35 0 : return -1;
36 : }
37 :
38 26533827 : if (elmsize && cnt) {
39 26533827 : if ((rc = fread(buf, elmsize, cnt, fp)) == 0 && ferror(fp)) {
40 0 : mnstr_set_error_errno(s, MNSTR_READ_ERROR, "read error");
41 0 : return -1;
42 : }
43 26533827 : s->eof |= rc == 0 && feof(fp);
44 : }
45 26533827 : return (ssize_t) rc;
46 : }
47 :
48 :
49 : static ssize_t
50 35966098 : file_write(stream *restrict s, const void *restrict buf, size_t elmsize, size_t cnt)
51 : {
52 35966098 : FILE *fp = (FILE *) s->stream_data.p;
53 :
54 35966098 : if (fp == NULL) {
55 0 : mnstr_set_error(s, MNSTR_WRITE_ERROR, "file ended");
56 0 : return -1;
57 : }
58 :
59 35966098 : if (elmsize && cnt) {
60 35963472 : size_t rc = fwrite(buf, elmsize, cnt, fp);
61 :
62 35963472 : if (rc != cnt) {
63 : // only happens if fwrite encountered an error.
64 0 : mnstr_set_error_errno(s, MNSTR_WRITE_ERROR, "write error");
65 0 : return -1;
66 : }
67 35963472 : return (ssize_t) rc;
68 : }
69 2626 : return (ssize_t) cnt;
70 : }
71 :
72 :
73 : static void
74 52708 : file_close(stream *s)
75 : {
76 52708 : FILE *fp = (FILE *) s->stream_data.p;
77 :
78 52708 : if (fp == NULL)
79 : return;
80 26507 : if (fp != stdin && fp != stdout && fp != stderr) {
81 25400 : if (s->name && *s->name == '|')
82 0 : pclose(fp);
83 : else
84 25400 : fclose(fp);
85 1107 : } else if (!s->readonly)
86 644 : fflush(fp);
87 26507 : s->stream_data.p = NULL;
88 : }
89 :
90 :
91 : static void
92 26507 : file_destroy(stream *s)
93 : {
94 26507 : file_close(s);
95 26507 : destroy_stream(s);
96 26507 : }
97 :
98 :
99 : static void
100 0 : file_clrerr(stream *s)
101 : {
102 0 : FILE *fp = (FILE *) s->stream_data.p;
103 :
104 0 : if (fp)
105 0 : clearerr(fp);
106 0 : }
107 :
108 :
109 : static int
110 167149 : file_flush(stream *s, mnstr_flush_level flush_level)
111 : {
112 167149 : FILE *fp = (FILE *) s->stream_data.p;
113 :
114 167149 : if (fp == NULL || (!s->readonly && fflush(fp) < 0)) {
115 0 : mnstr_set_error_errno(s, MNSTR_WRITE_ERROR, "flush error");
116 0 : return -1;
117 : }
118 : (void) flush_level;
119 : return 0;
120 : }
121 :
122 :
123 : static int
124 36 : file_fsync(stream *s)
125 : {
126 :
127 36 : FILE *fp = (FILE *) s->stream_data.p;
128 :
129 36 : if (fp == NULL ||
130 36 : (!s->readonly
131 : #ifdef NATIVE_WIN32
132 : && _commit(fileno(fp)) < 0
133 : #else
134 : #ifdef HAVE_FDATASYNC
135 36 : && fdatasync(fileno(fp)) < 0
136 : #else
137 : #ifdef HAVE_FSYNC
138 : && fsync(fileno(fp)) < 0
139 : #endif
140 : #endif
141 : #endif
142 : )) {
143 0 : mnstr_set_error(s, MNSTR_WRITE_ERROR, "fsync failed");
144 0 : return -1;
145 : }
146 : return 0;
147 : }
148 :
149 :
150 : static int
151 0 : file_fgetpos(stream *restrict s, fpos_t *restrict p)
152 : {
153 0 : FILE *fp = (FILE *) s->stream_data.p;
154 :
155 0 : if (fp == NULL || p == NULL)
156 : return -1;
157 0 : return fgetpos(fp, p) ? -1 : 0;
158 : }
159 :
160 :
161 : static int
162 0 : file_fsetpos(stream *restrict s, fpos_t *restrict p)
163 : {
164 0 : FILE *fp = (FILE *) s->stream_data.p;
165 :
166 0 : if (fp == NULL || p == NULL)
167 : return -1;
168 0 : return fsetpos(fp, p) ? -1 : 0;
169 : }
170 :
171 : /* convert a string from UTF-8 to wide characters; the return value is
172 : * freshly allocated */
173 : #ifdef NATIVE_WIN32
174 : static wchar_t *
175 : utf8towchar(const char *src)
176 : {
177 : wchar_t *dest;
178 : size_t i = 0;
179 : size_t j = 0;
180 : uint32_t c;
181 :
182 : /* count how many wchar_t's we need, while also checking for
183 : * correctness of the input */
184 : while (src[j]) {
185 : i++;
186 : if ((src[j+0] & 0x80) == 0) {
187 : j += 1;
188 : } else if ((src[j+0] & 0xE0) == 0xC0
189 : && (src[j+1] & 0xC0) == 0x80
190 : && (src[j+0] & 0x1E) != 0) {
191 : j += 2;
192 : } else if ((src[j+0] & 0xF0) == 0xE0
193 : && (src[j+1] & 0xC0) == 0x80
194 : && (src[j+2] & 0xC0) == 0x80
195 : && ((src[j+0] & 0x0F) != 0
196 : || (src[j+1] & 0x20) != 0)) {
197 : j += 3;
198 : } else if ((src[j+0] & 0xF8) == 0xF0
199 : && (src[j+1] & 0xC0) == 0x80
200 : && (src[j+2] & 0xC0) == 0x80
201 : && (src[j+3] & 0xC0) == 0x80) {
202 : c = (src[j+0] & 0x07) << 18
203 : | (src[j+1] & 0x3F) << 12
204 : | (src[j+2] & 0x3F) << 6
205 : | (src[j+3] & 0x3F);
206 : if (c < 0x10000
207 : || c > 0x10FFFF
208 : || (c & 0x1FF800) == 0x00D800)
209 : return NULL;
210 : #if SIZEOF_WCHAR_T == 2
211 : i++;
212 : #endif
213 : j += 4;
214 : } else {
215 : return NULL;
216 : }
217 : }
218 : dest = malloc((i + 1) * sizeof(wchar_t));
219 : if (dest == NULL)
220 : return NULL;
221 : /* go through the source string again, this time we can skip
222 : * the correctness tests */
223 : i = j = 0;
224 : while (src[j]) {
225 : if ((src[j+0] & 0x80) == 0) {
226 : dest[i++] = src[j+0];
227 : j += 1;
228 : } else if ((src[j+0] & 0xE0) == 0xC0) {
229 : dest[i++] = (src[j+0] & 0x1F) << 6
230 : | (src[j+1] & 0x3F);
231 : j += 2;
232 : } else if ((src[j+0] & 0xF0) == 0xE0) {
233 : dest[i++] = (src[j+0] & 0x0F) << 12
234 : | (src[j+1] & 0x3F) << 6
235 : | (src[j+2] & 0x3F);
236 : j += 3;
237 : } else if ((src[j+0] & 0xF8) == 0xF0) {
238 : c = (src[j+0] & 0x07) << 18
239 : | (src[j+1] & 0x3F) << 12
240 : | (src[j+2] & 0x3F) << 6
241 : | (src[j+3] & 0x3F);
242 : #if SIZEOF_WCHAR_T == 2
243 : dest[i++] = 0xD800 | ((c - 0x10000) >> 10);
244 : dest[i++] = 0xDE00 | (c & 0x3FF);
245 : #else
246 : dest[i++] = c;
247 : #endif
248 : j += 4;
249 : }
250 : }
251 : dest[i] = 0;
252 : return dest;
253 : }
254 :
255 : #else
256 :
257 : static char *
258 25528 : cvfilename(const char *filename)
259 : {
260 : #if defined(HAVE_NL_LANGINFO) && defined(HAVE_ICONV)
261 25528 : char *code_set = nl_langinfo(CODESET);
262 :
263 25528 : if (code_set != NULL && strcmp(code_set, "UTF-8") != 0) {
264 1 : iconv_t cd = iconv_open("UTF-8", code_set);
265 :
266 1 : if (cd != (iconv_t) -1) {
267 1 : size_t len = strlen(filename);
268 1 : size_t size = 4 * len;
269 1 : char *from = (char *) filename;
270 1 : char *r = malloc(size + 1);
271 1 : char *p = r;
272 :
273 1 : if (r) {
274 1 : if (iconv(cd, &from, &len, &p, &size) != (size_t) -1) {
275 1 : iconv_close(cd);
276 1 : *p = 0;
277 1 : return r;
278 : }
279 0 : free(r);
280 : }
281 0 : iconv_close(cd);
282 : }
283 : }
284 : #endif
285 : /* couldn't use iconv for whatever reason; alternative is to
286 : * use utf8towchar above to convert to a wide character string
287 : * (wcs) and convert that to the locale-specific encoding
288 : * using wcstombs or wcsrtombs (but preferably only if the
289 : * locale's encoding is not UTF-8) */
290 25527 : return strdup(filename);
291 : }
292 : #endif
293 :
294 :
295 : stream *
296 25528 : open_stream(const char *restrict filename, const char *restrict flags)
297 : {
298 25528 : stream *s;
299 25528 : FILE *fp;
300 25528 : fpos_t pos;
301 25528 : char buf[UTF8BOMLENGTH + 1];
302 :
303 25528 : if ((s = create_stream(filename)) == NULL)
304 : return NULL;
305 : #ifdef NATIVE_WIN32
306 : {
307 : wchar_t *wfname = utf8towchar(filename);
308 : wchar_t *wflags = utf8towchar(flags);
309 : if (wfname != NULL && wflags != NULL)
310 : fp = _wfopen(wfname, wflags);
311 : else
312 : fp = NULL;
313 : if (wfname)
314 : free(wfname);
315 : if (wflags)
316 : free(wflags);
317 : }
318 : #else
319 : {
320 25528 : char *fname = cvfilename(filename);
321 25528 : if (fname) {
322 25528 : fp = fopen(fname, flags);
323 25528 : free(fname);
324 : } else
325 : fp = NULL;
326 : }
327 : #endif
328 25528 : if (fp == NULL) {
329 127 : mnstr_set_open_error(filename, errno, "open failed");
330 127 : destroy_stream(s);
331 127 : return NULL;
332 : }
333 25401 : s->readonly = flags[0] == 'r';
334 25401 : s->binary = flags[1] == 'b';
335 25401 : s->read = file_read;
336 25401 : s->write = file_write;
337 25401 : s->close = file_close;
338 25401 : s->destroy = file_destroy;
339 25401 : s->clrerr = file_clrerr;
340 25401 : s->flush = file_flush;
341 25401 : s->fsync = file_fsync;
342 25401 : s->fgetpos = file_fgetpos;
343 25401 : s->fsetpos = file_fsetpos;
344 25401 : s->stream_data.p = (void *) fp;
345 : /* if a text file is opened for reading, and it starts with
346 : * the UTF-8 encoding of the Unicode Byte Order Mark, skip the
347 : * mark, and mark the stream as being a UTF-8 stream */
348 25401 : if (flags[0] == 'r' && flags[1] != 'b' && fgetpos(fp, &pos) == 0) {
349 0 : if (file_read(s, buf, 1, UTF8BOMLENGTH) == UTF8BOMLENGTH &&
350 0 : strncmp(buf, UTF8BOM, UTF8BOMLENGTH) == 0)
351 0 : s->isutf8 = true;
352 0 : else if (fsetpos(fp, &pos) != 0) {
353 : /* unlikely: we couldn't seek the file back */
354 0 : fclose(fp);
355 0 : destroy_stream(s);
356 0 : return NULL;
357 : }
358 : }
359 : return s;
360 : }
361 :
362 : stream *
363 1159 : file_stream(const char *name)
364 : {
365 1159 : stream *s;
366 :
367 1159 : if ((s = create_stream(name)) == NULL)
368 : return NULL;
369 1159 : s->read = file_read;
370 1159 : s->write = file_write;
371 1159 : s->close = file_close;
372 1159 : s->destroy = file_destroy;
373 1159 : s->flush = file_flush;
374 1159 : s->fsync = file_fsync;
375 1159 : s->fgetpos = file_fgetpos;
376 1159 : s->fsetpos = file_fsetpos;
377 1159 : return s;
378 : }
379 :
380 : stream *
381 467 : file_rstream(FILE *restrict fp, bool binary, const char *restrict name)
382 : {
383 467 : stream *s;
384 :
385 467 : if (fp == NULL)
386 : return NULL;
387 : #ifdef STREAM_DEBUG
388 : fprintf(stderr, "file_rstream %s\n", name);
389 : #endif
390 467 : if ((s = file_stream(name)) == NULL)
391 : return NULL;
392 467 : s->binary = binary;
393 467 : s->stream_data.p = (void *) fp;
394 467 : return s;
395 : }
396 :
397 : stream *
398 692 : file_wstream(FILE *restrict fp, bool binary, const char *restrict name)
399 : {
400 692 : stream *s;
401 :
402 692 : if (fp == NULL)
403 : return NULL;
404 : #ifdef STREAM_DEBUG
405 : fprintf(stderr, "file_wstream %s\n", name);
406 : #endif
407 692 : if ((s = file_stream(name)) == NULL)
408 : return NULL;
409 692 : s->readonly = false;
410 692 : s->binary = binary;
411 692 : s->stream_data.p = (void *) fp;
412 692 : return s;
413 : }
414 :
415 :
416 : stream *
417 467 : stdin_rastream(void)
418 : {
419 467 : const char *name = "<stdin>";
420 : // Make an attempt to skip a BOM marker.
421 : // It would be nice to integrate this with with the BOM removal code
422 : // in text_stream.c but that is complicated. In text_stream,
423 467 : struct stat stb;
424 467 : if (fstat(fileno(stdin), &stb) == 0 && (stb.st_mode & S_IFMT) == S_IFREG) {
425 47 : fpos_t pos;
426 47 : if (fgetpos(stdin, &pos) == 0) {
427 47 : char bytes[UTF8BOMLENGTH];
428 47 : size_t nread = fread(bytes, 1, UTF8BOMLENGTH, stdin);
429 47 : if (nread != 3 || memcmp(bytes, UTF8BOM, UTF8BOMLENGTH) != 0) {
430 : // not a BOM, rewind
431 47 : if (nread > 0 && fsetpos(stdin, &pos) != 0) {
432 : // oops, bytes have been read but we can't rewind
433 0 : mnstr_set_error_errno(NULL, MNSTR_OPEN_ERROR, "while rewinding after checking for byte order mark");
434 0 : return NULL;
435 : }
436 : }
437 : }
438 : }
439 :
440 : #ifdef _MSC_VER
441 : return win_console_in_stream(name);
442 : #else
443 467 : return file_rstream(stdin, false, name);
444 : #endif
445 : }
446 :
447 : stream *
448 526 : stdout_wastream(void)
449 : {
450 526 : const char *name = "<stdout>";
451 : #ifdef _MSC_VER
452 : if (isatty(fileno(stdout)))
453 : return win_console_out_stream(name);
454 : #endif
455 526 : return file_wstream(stdout, false, name);
456 : }
457 :
458 : stream *
459 166 : stderr_wastream(void)
460 : {
461 166 : const char *name = "<stderr>";
462 : #ifdef _MSC_VER
463 : if (isatty(fileno(stderr)))
464 : return win_console_out_stream(name);
465 : #endif
466 166 : return file_wstream(stderr, false, name);
467 : }
468 :
469 : /* some lower-level access functions */
470 : FILE *
471 68632 : getFile(stream *s)
472 : {
473 68634 : for (; s != NULL; s = s->inner) {
474 : #ifdef _MSC_VER
475 : if (s->read == console_read)
476 : return stdin;
477 : if (s->write == console_write)
478 : return stdout;
479 : #endif
480 68634 : if (s->read == file_read)
481 68632 : return (FILE *) s->stream_data.p;
482 : }
483 :
484 : return NULL;
485 : }
486 :
487 : int
488 1300 : getFileNo(stream *s)
489 : {
490 1300 : FILE *f;
491 :
492 1300 : f = getFile(s);
493 1300 : if (f == NULL)
494 : return -1;
495 1300 : return fileno(f);
496 : }
497 :
498 : size_t
499 992 : getFileSize(stream *s)
500 : {
501 992 : struct stat stb;
502 992 : int fd = getFileNo(s);
503 :
504 992 : if (fd >= 0 && fstat(fd, &stb) == 0)
505 992 : return (size_t) stb.st_size;
506 : return 0; /* unknown */
507 : }
508 :
509 : int
510 0 : file_remove(const char *filename)
511 : {
512 0 : int rc = -1;
513 :
514 : #ifdef NATIVE_WIN32
515 : wchar_t *wfname = utf8towchar(filename);
516 : if (wfname != NULL) {
517 : rc = _wremove(wfname);
518 : free(wfname);
519 : }
520 : #else
521 0 : char *fname = cvfilename(filename);
522 0 : if (fname) {
523 0 : rc = remove(fname);
524 0 : free(fname);
525 : }
526 : #endif
527 0 : return rc;
528 : }
|