LCOV - code coverage report
Current view: top level - common/stream - stdio_stream.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 115 159 72.3 %
Date: 2024-10-07 21:21:43 Functions: 15 20 75.0 %

          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    26805568 : file_read(stream *restrict s, void *restrict buf, size_t elmsize, size_t cnt)
      30             : {
      31    26805568 :         FILE *fp = (FILE *) s->stream_data.p;
      32    26805568 :         size_t rc = 0;
      33             : 
      34    26805568 :         if (fp == NULL) {
      35           0 :                 mnstr_set_error(s, MNSTR_READ_ERROR, "file ended");
      36           0 :                 return -1;
      37             :         }
      38             : 
      39    26805568 :         if (elmsize && cnt) {
      40    26805567 :                 errno = 0;
      41    26805567 :                 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    26805565 :                 s->eof |= rc == 0 && feof(fp);
      46             :         }
      47    26805566 :         return (ssize_t) rc;
      48             : }
      49             : 
      50             : 
      51             : static ssize_t
      52    40302797 : file_write(stream *restrict s, const void *restrict buf, size_t elmsize, size_t cnt)
      53             : {
      54    40302797 :         FILE *fp = (FILE *) s->stream_data.p;
      55             : 
      56    40302797 :         if (fp == NULL) {
      57           0 :                 mnstr_set_error(s, MNSTR_WRITE_ERROR, "file ended");
      58           0 :                 return -1;
      59             :         }
      60             : 
      61    40302797 :         if (elmsize && cnt) {
      62    40300077 :                 size_t rc = fwrite(buf, elmsize, cnt, fp);
      63             : 
      64    40300077 :                 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    40300077 :                 return (ssize_t) rc;
      70             :         }
      71        2720 :         return (ssize_t) cnt;
      72             : }
      73             : 
      74             : 
      75             : static void
      76       54494 : file_close(stream *s)
      77             : {
      78       54494 :         FILE *fp = (FILE *) s->stream_data.p;
      79             : 
      80       54494 :         if (fp == NULL)
      81             :                 return;
      82       27399 :         if (fp != stdin && fp != stdout && fp != stderr) {
      83       26279 :                 if (s->name && *s->name == '|')
      84           0 :                         pclose(fp);
      85             :                 else
      86       26279 :                         fclose(fp);
      87        1120 :         } else if (!s->readonly)
      88         659 :                 fflush(fp);
      89       27399 :         s->stream_data.p = NULL;
      90             : }
      91             : 
      92             : 
      93             : static void
      94       27399 : file_destroy(stream *s)
      95             : {
      96       27399 :         file_close(s);
      97       27399 :         destroy_stream(s);
      98       27399 : }
      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      172551 : file_flush(stream *s, mnstr_flush_level flush_level)
     113             : {
     114      172551 :         FILE *fp = (FILE *) s->stream_data.p;
     115             : 
     116      172551 :         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          36 : file_fsync(stream *s)
     127             : {
     128             : 
     129          36 :         FILE *fp = (FILE *) s->stream_data.p;
     130             : 
     131          36 :         if (fp == NULL ||
     132          36 :             (!s->readonly
     133             : #ifdef NATIVE_WIN32
     134             :              && _commit(fileno(fp)) < 0
     135             : #else
     136             : #ifdef HAVE_FDATASYNC
     137          36 :              && 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       26433 : open_stream(const char *restrict filename, const char *restrict flags)
     235             : {
     236       26433 :         stream *s;
     237       26433 :         FILE *fp;
     238       26433 :         fpos_t pos;
     239       26433 :         char buf[UTF8BOMLENGTH + 1];
     240             : 
     241       26433 :         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       26433 :         fp = fopen(filename, flags);
     258             : #endif
     259       26433 :         if (fp == NULL) {
     260         139 :                 mnstr_set_open_error(filename, errno, "open failed");
     261         139 :                 destroy_stream(s);
     262         139 :                 return NULL;
     263             :         }
     264       26294 :         s->readonly = flags[0] == 'r';
     265       26294 :         s->binary = flags[1] == 'b';
     266       26294 :         s->read = file_read;
     267       26294 :         s->write = file_write;
     268       26294 :         s->close = file_close;
     269       26294 :         s->destroy = file_destroy;
     270       26294 :         s->clrerr = file_clrerr;
     271       26294 :         s->flush = file_flush;
     272       26294 :         s->fsync = file_fsync;
     273       26294 :         s->fgetpos = file_fgetpos;
     274       26294 :         s->fsetpos = file_fsetpos;
     275       26294 :         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       26294 :         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        1172 : file_stream(const char *name)
     295             : {
     296        1172 :         stream *s;
     297             : 
     298        1172 :         if ((s = create_stream(name)) == NULL)
     299             :                 return NULL;
     300        1172 :         s->read = file_read;
     301        1172 :         s->write = file_write;
     302        1172 :         s->close = file_close;
     303        1172 :         s->destroy = file_destroy;
     304        1172 :         s->flush = file_flush;
     305        1172 :         s->fsync = file_fsync;
     306        1172 :         s->fgetpos = file_fgetpos;
     307        1172 :         s->fsetpos = file_fsetpos;
     308        1172 :         return s;
     309             : }
     310             : 
     311             : stream *
     312         465 : file_rstream(FILE *restrict fp, bool binary, const char *restrict name)
     313             : {
     314         465 :         stream *s;
     315             : 
     316         465 :         if (fp == NULL)
     317             :                 return NULL;
     318             : #ifdef STREAM_DEBUG
     319             :         fprintf(stderr, "file_rstream %s\n", name);
     320             : #endif
     321         465 :         if ((s = file_stream(name)) == NULL)
     322             :                 return NULL;
     323         465 :         s->binary = binary;
     324         465 :         s->stream_data.p = (void *) fp;
     325         465 :         return s;
     326             : }
     327             : 
     328             : stream *
     329         707 : file_wstream(FILE *restrict fp, bool binary, const char *restrict name)
     330             : {
     331         707 :         stream *s;
     332             : 
     333         707 :         if (fp == NULL)
     334             :                 return NULL;
     335             : #ifdef STREAM_DEBUG
     336             :         fprintf(stderr, "file_wstream %s\n", name);
     337             : #endif
     338         707 :         if ((s = file_stream(name)) == NULL)
     339             :                 return NULL;
     340         707 :         s->readonly = false;
     341         707 :         s->binary = binary;
     342         707 :         s->stream_data.p = (void *) fp;
     343         707 :         return s;
     344             : }
     345             : 
     346             : 
     347             : stream *
     348         465 : stdin_rastream(void)
     349             : {
     350         465 :         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         465 :         struct stat stb;
     355         465 :         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         465 :         return file_rstream(stdin, false, name);
     375             : #endif
     376             : }
     377             : 
     378             : stream *
     379         531 : stdout_wastream(void)
     380             : {
     381         531 :         const char name[] = "<stdout>";
     382             : #ifdef _MSC_VER
     383             :         if (isatty(fileno(stdout)))
     384             :                 return win_console_out_stream(name);
     385             : #endif
     386         531 :         return file_wstream(stdout, false, name);
     387             : }
     388             : 
     389             : stream *
     390         176 : stderr_wastream(void)
     391             : {
     392         176 :         const char name[] = "<stderr>";
     393             : #ifdef _MSC_VER
     394             :         if (isatty(fileno(stderr)))
     395             :                 return win_console_out_stream(name);
     396             : #endif
     397         176 :         return file_wstream(stderr, false, name);
     398             : }
     399             : 
     400             : /* some lower-level access functions */
     401             : FILE *
     402       62861 : getFile(stream *s)
     403             : {
     404       62866 :         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       62866 :                 if (s->read == file_read)
     412       62861 :                         return (FILE *) s->stream_data.p;
     413             :         }
     414             : 
     415             :         return NULL;
     416             : }
     417             : 
     418             : int
     419         322 : getFileNo(stream *s)
     420             : {
     421         322 :         FILE *f;
     422             : 
     423         322 :         f = getFile(s);
     424         322 :         if (f == NULL)
     425             :                 return -1;
     426         322 :         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             : }

Generated by: LCOV version 1.14