Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read wide char from a stream created with fmemopen

I'm trying to read a wide char from a stream that was created using fmemopen with a char *.

char *s = "foo bar foo";
FILE *f = fmemopen(s,strlen(s),"r");

wchar_t c = getwc(f);

getwc throws a segmentation fault, I checked using GDB.

I know this is due to opening the stream with fmemopen, because calling getwc on a stream opened normally works fine.

Is there a wide char version of fmemopen, or is there some other way to fix this problem?

like image 482
MD XF Avatar asked Aug 10 '17 22:08

MD XF


3 Answers

The second line should read FILE *f = fmemopen(s, strlen(s), "r");. As posted, fmemopen has undefined behavior and might return NULL, which causes getwc() to crash.

Changing the fmemopen() line and adding a check for NULL fixes the crash but does not meet the OPs goal.

It seems wide orientation is not supported on streams open with fmemopen(), At least for the GNU C library. Note that fmemopen is not defined in the C Standard but in POSIX.1-2008 and is not available on many systems (like OS/X).

Here is a corrected and extended version of your program:

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>

int main(void) {
    const char *s = "foo bar foo";
    FILE *f = fmemopen((void *)s, strlen(s), "r");
    wchar_t c;

    if (f == NULL) {
        printf("fmemopen failed: %s\n", strerror(errno));
        return 1;
    }
    printf("default wide orientation: %d\n", fwide(f, 0));
    printf("selected wide orientation: %d\n", fwide(f, 1));
    while ((c = getwc(f)) != WEOF) {
        printf("read %lc (%d 0x%x)\n", c, c, c);
    }
    return 0;
}

Run on linux:

default wide orientation: -1
selected wide orientation: -1

No output, WEOF is returned immediately.

Explanation for fwide(f, 0) from the linux man page:

SYNOPSIS

#include <wchar.h>
int fwide(FILE *stream, int mode);

When mode is zero, the fwide() function determines the current orientation of stream. It returns a positive value if stream is wide-character oriented, that is, if wide-character I/O is permitted but char I/O is disallowed. It returns a negative value if stream is byte oriented, i.e., if char I/O is permitted but wide-character I/O is disallowed. It returns zero if stream has no orientation yet; in this case the next I/O operation might change the orientation (to byte oriented if it is a char I/O operation, or to wide-character oriented if it is a wide-character I/O operation).

Once a stream has an orientation, it cannot be changed and persists until the stream is closed.

When mode is nonzero, the fwide() function first attempts to set stream's orientation (to wide-character oriented if mode is greater than 0, or to byte oriented if mode is less than 0). It then returns a value denoting the current orientation, as above.

The stream returned by fmemopen() is byte-oriented and cannot be changed to wide-character oriented.

like image 187
chqrlie Avatar answered Nov 11 '22 18:11

chqrlie


  1. Your second line does not use the correct number of parameters, does it? corrected

    FILE *fmemopen(void *buf, size_t size, const char *mode);

  2. glibc's fmemopen does not (fully) support wide characters AFAIK. There's also open_wmemstream(), which supports wide characters but is just for writing.

  3. Is _UNICODE defined? See wchar_t reading.
    Also, have you set the locale to an encoding that supports Unicode, for example, setlocale(LC_ALL, "en_US.UTF-8");? See here.

  4. Consider using a temporary file. Consider using fgetwc / 4 instead.

I have changed my code and adopted the code from @chqrlie since it more close to the OP code but added the locale, otherwise it fails to produce correct output for extended/Unicode characters.

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <stdlib.h>
#include <locale.h>

int main(void)
{
    setlocale(LC_ALL, "en_US.UTF-8");
    const char *s = "foo $€ bar foo";
    FILE *f = fmemopen((void *)s, strlen(s), "r");
    wchar_t c;

    if (f == NULL) {
        printf("fmemopen failed: %s\n", strerror(errno));
        return 1;
    }
    printf("default wide orientation: %d\n", fwide(f, 0));
    printf("selected wide orientation: %d\n", fwide(f, 1));
    while ((c = getwc(f)) != WEOF) {
        printf("read %lc (%d 0x%x)\n", c, c, c);
    }
    return 0;
}
like image 37
wp78de Avatar answered Nov 11 '22 19:11

wp78de


  1. You can use getwc() only on unoriented or wide-oriented stream. From getwc() man page: The stream shall not have an orientation yet, or be wide-oriented.

  2. It is not possible to change stream orientation, if the stream already has orientation. From fwide() man page: Calling this function on a stream that already has an orientation cannot change it.

  3. Stream opened with glibc's fmemopen() has an byte-orientation and therefore can't be wide-oriented in any way. As described here uClibc has fmemopen() routine without this limitation.

Conclusion: You need to use uClibc or another library or make your own fmemopen().

like image 1
vadim_hr Avatar answered Nov 11 '22 18:11

vadim_hr