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