/* $XTermId: ptydata.c,v 1.150 2020/10/12 18:46:28 tom Exp $ */ /* * Copyright 1999-2019,2020 by Thomas E. Dickey * * All Rights Reserved * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Except as contained in this notice, the name(s) of the above copyright * holders shall not be used in advertising or otherwise to promote the * sale, use or other dealings in this Software without prior written * authorization. */ #include #if OPT_WIDE_CHARS #include #include #endif #ifdef TEST_DRIVER #undef TRACE #define TRACE(p) if (1) printf p #undef TRACE2 #define TRACE2(p) if (0) printf p #define visibleChars(buf, len) "buffer" #endif /* * Check for both EAGAIN and EWOULDBLOCK, because some supposedly POSIX * systems are broken and return EWOULDBLOCK when they should return EAGAIN. * Note that this macro may evaluate its argument more than once. */ #if defined(EAGAIN) && defined(EWOULDBLOCK) #define E_TEST(err) ((err) == EAGAIN || (err) == EWOULDBLOCK) #else #ifdef EAGAIN #define E_TEST(err) ((err) == EAGAIN) #else #define E_TEST(err) ((err) == EWOULDBLOCK) #endif #endif #if OPT_WIDE_CHARS /* * Convert the 8-bit codes in data->buffer[] into Unicode in data->utf_data. * The number of bytes converted will be nonzero iff there is data. */ Bool decodeUtf8(TScreen *screen, PtyData *data) { int i; int length = (int) (data->last - data->next); int utf_count = 0; unsigned utf_char = 0; data->utf_size = 0; for (i = 0; i < length; i++) { unsigned c = data->next[i]; /* Combine UTF-8 into Unicode */ if (c < 0x80) { /* We received an ASCII character */ if (utf_count > 0) { data->utf_data = UCS_REPL; /* prev. sequence incomplete */ data->utf_size = i; } else { data->utf_data = (IChar) c; data->utf_size = 1; } break; } else if (screen->vt100_graphics && (c < 0x100) && (utf_count == 0) && screen->gsets[(int) screen->curgr] != nrc_ASCII) { data->utf_data = (IChar) c; data->utf_size = 1; break; } else if (c < 0xc0) { /* We received a continuation byte */ if (utf_count < 1) { /* * We received a continuation byte before receiving a sequence * state. Or an attempt to use a C1 control string. Either * way, it is mapped to the replacement character, unless * allowed by optional feature. */ data->utf_data = (IChar) (screen->c1_printable ? c : UCS_REPL); data->utf_size = (i + 1); break; } else if (screen->utf8_weblike && (utf_count == 3 && utf_char == 0x04 && c >= 0x90)) { /* The encoding would form a code point beyond U+10FFFF. */ data->utf_size = i; data->utf_data = UCS_REPL; break; } else if (screen->utf8_weblike && (utf_count == 2 && utf_char == 0x0d && c >= 0xa0)) { /* The encoding would form a surrogate code point. */ data->utf_size = i; data->utf_data = UCS_REPL; break; } else { /* Check for overlong UTF-8 sequences for which a shorter * encoding would exist and replace them with UCS_REPL. * An overlong UTF-8 sequence can have any of the following * forms: * 1100000x 10xxxxxx * 11100000 100xxxxx 10xxxxxx * 11110000 1000xxxx 10xxxxxx 10xxxxxx * 11111000 10000xxx 10xxxxxx 10xxxxxx 10xxxxxx * 11111100 100000xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ if (!utf_char && !((c & 0x7f) >> (7 - utf_count))) { if (screen->utf8_weblike) { /* overlong sequence continued */ data->utf_data = UCS_REPL; data->utf_size = i; break; } else { utf_char = UCS_REPL; } } utf_char <<= 6; utf_char |= (c & 0x3f); if ((utf_char >= 0xd800 && utf_char <= 0xdfff) || (utf_char == 0xfffe) || (utf_char == HIDDEN_CHAR)) { utf_char = UCS_REPL; } utf_count--; if (utf_count == 0) { #if !OPT_WIDER_ICHAR /* characters outside UCS-2 become UCS_REPL */ if (utf_char > NARROW_ICHAR) { TRACE(("using replacement for %#x\n", utf_char)); utf_char = UCS_REPL; } #endif data->utf_data = (IChar) utf_char; data->utf_size = (i + 1); break; } } } else { /* We received a sequence start byte */ if (utf_count > 0) { /* previous sequence is incomplete */ data->utf_data = UCS_REPL; data->utf_size = i; break; } if (screen->utf8_weblike) { if (c < 0xe0) { if (!(c & 0x1e)) { /* overlong sequence start */ data->utf_data = UCS_REPL; data->utf_size = (i + 1); break; } utf_count = 1; utf_char = (c & 0x1f); } else if (c < 0xf0) { utf_count = 2; utf_char = (c & 0x0f); } else if (c < 0xf5) { utf_count = 3; utf_char = (c & 0x07); } else { data->utf_data = UCS_REPL; data->utf_size = (i + 1); break; } } else { if (c < 0xe0) { utf_count = 1; utf_char = (c & 0x1f); if (!(c & 0x1e)) { /* overlong sequence */ utf_char = UCS_REPL; } } else if (c < 0xf0) { utf_count = 2; utf_char = (c & 0x0f); } else if (c < 0xf8) { utf_count = 3; utf_char = (c & 0x07); } else if (c < 0xfc) { utf_count = 4; utf_char = (c & 0x03); } else if (c < 0xfe) { utf_count = 5; utf_char = (c & 0x01); } else { data->utf_data = UCS_REPL; data->utf_size = (i + 1); break; } } } } #if OPT_TRACE > 1 TRACE(("UTF-8 char %04X [%d..%d]\n", data->utf_data, (int) (data->next - data->buffer), (int) (data->next - data->buffer + data->utf_size - 1))); #endif return (data->utf_size != 0); } #endif int readPtyData(XtermWidget xw, PtySelect * select_mask, PtyData *data) { TScreen *screen = TScreenOf(xw); int size = 0; #ifdef VMS if (*select_mask & pty_mask) { trimPtyData(xw, data); if (read_queue.flink != 0) { size = tt_read(data->next); if (size == 0) { Panic("input: read returned zero\n", 0); } } else { sys$hiber(); } } #else /* !VMS */ if (FD_ISSET(screen->respond, select_mask)) { int save_err; trimPtyData(xw, data); size = (int) read(screen->respond, (char *) data->last, (size_t) FRG_SIZE); save_err = errno; #if (defined(i386) && defined(SVR4) && defined(sun)) || defined(__CYGWIN__) /* * Yes, I know this is a majorly f*ugly hack, however it seems to * be necessary for Solaris x86. DWH 11/15/94 * Dunno why though.. * (and now CYGWIN, alanh@xfree86.org 08/15/01 */ if (size <= 0) { if (save_err == EIO || save_err == 0) NormalExit(); else if (!E_TEST(save_err)) Panic("input: read returned unexpected error (%d)\n", save_err); size = 0; } #else /* !f*ugly */ if (size < 0) { if (save_err == EIO) NormalExit(); else if (!E_TEST(save_err)) Panic("input: read returned unexpected error (%d)\n", save_err); size = 0; } else if (size == 0) { #if defined(__FreeBSD__) NormalExit(); #else Panic("input: read returned zero\n", 0); #endif } #endif /* f*ugly */ } #endif /* VMS */ if (size) { #if OPT_TRACE int i; TRACE(("read %d bytes from pty\n", size)); for (i = 0; i < size; i++) { if (!(i % 16)) TRACE(("%s", i ? "\n " : "READ")); TRACE((" %02X", data->last[i])); } TRACE(("\n")); #endif data->last += size; #ifdef ALLOWLOGGING TScreenOf(term)->logstart = VTbuffer->next; #endif } return (size); } /* * Return the next value from the input buffer. Note that morePtyData() is * always called before this function, so we can do the UTF-8 input conversion * in that function and simply return the result here. */ #if OPT_WIDE_CHARS IChar nextPtyData(TScreen *screen, PtyData *data) { IChar result; if (screen->utf8_inparse) { skipPtyData(data, result); } else { result = *((data)->next++); if (!screen->output_eight_bits) { result = (IChar) (result & 0x7f); } } TRACE2(("nextPtyData returns %#x\n", result)); return result; } #endif #if OPT_WIDE_CHARS /* * Called when UTF-8 mode has been turned on/off. */ void switchPtyData(TScreen *screen, int flag) { if (screen->utf8_mode != flag) { screen->utf8_mode = flag; screen->utf8_inparse = (Boolean) (flag != 0); mk_wcwidth_init(screen->utf8_mode); TRACE(("turning UTF-8 mode %s\n", BtoS(flag))); update_font_utf8_mode(); } } #endif /* * Allocate a buffer. */ void initPtyData(PtyData **result) { PtyData *data; TRACE2(("initPtyData given minBufSize %d, maxBufSize %d\n", FRG_SIZE, BUF_SIZE)); if (FRG_SIZE < 64) FRG_SIZE = 64; if (BUF_SIZE < FRG_SIZE) BUF_SIZE = FRG_SIZE; if (BUF_SIZE % FRG_SIZE) BUF_SIZE = BUF_SIZE + FRG_SIZE - (BUF_SIZE % FRG_SIZE); TRACE2(("initPtyData using minBufSize %d, maxBufSize %d\n", FRG_SIZE, BUF_SIZE)); data = TypeXtMallocX(PtyData, (BUF_SIZE + FRG_SIZE)); memset(data, 0, sizeof(*data)); data->next = data->buffer; data->last = data->buffer; *result = data; } /* * Initialize a buffer for the caller, using its data in 'next'. */ #if OPT_WIDE_CHARS PtyData * fakePtyData(PtyData *result, Char *next, Char *last) { PtyData *data = result; memset(data, 0, sizeof(*data)); data->next = next; data->last = last; return data; } #endif /* * Remove used data by shifting the buffer down, to make room for more data, * e.g., a continuation-read. */ void trimPtyData(XtermWidget xw, PtyData *data) { (void) xw; FlushLog(xw); if (data->next != data->buffer) { int i; int n = (int) (data->last - data->next); TRACE(("shifting buffer down by %d\n", n)); for (i = 0; i < n; ++i) { data->buffer[i] = data->next[i]; } data->next = data->buffer; data->last = data->next + n; } } /* * Insert new data into the input buffer so the next calls to morePtyData() * and nextPtyData() will return that. */ void fillPtyData(XtermWidget xw, PtyData *data, const char *value, int length) { int size; int n; /* remove the used portion of the buffer */ trimPtyData(xw, data); VTbuffer->last += length; size = (int) (VTbuffer->last - VTbuffer->next); /* shift the unused portion up to make room */ for (n = size; n >= length; --n) VTbuffer->next[n] = VTbuffer->next[n - length]; /* insert the new bytes to interpret */ for (n = 0; n < length; n++) VTbuffer->next[n] = CharOf(value[n]); } #if OPT_WIDE_CHARS /* * Convert an ISO-8859-1 code 'c' to UTF-8, storing the result in the target * 'lp', and returning a pointer past the converted character. */ Char * convertToUTF8(Char *lp, unsigned c) { #define CH(n) (Char)((c) >> ((n) * 8)) if (c < 0x80) { /* 0******* */ *lp++ = (Char) CH(0); } else if (c < 0x800) { /* 110***** 10****** */ *lp++ = (Char) (0xc0 | (CH(0) >> 6) | ((CH(1) & 0x07) << 2)); *lp++ = (Char) (0x80 | (CH(0) & 0x3f)); } else if (c < 0x00010000) { /* 1110**** 10****** 10****** */ *lp++ = (Char) (0xe0 | ((int) (CH(1) & 0xf0) >> 4)); *lp++ = (Char) (0x80 | (CH(0) >> 6) | ((CH(1) & 0x0f) << 2)); *lp++ = (Char) (0x80 | (CH(0) & 0x3f)); } else if (c < 0x00200000) { *lp++ = (Char) (0xf0 | ((int) (CH(2) & 0x1f) >> 2)); *lp++ = (Char) (0x80 | ((int) (CH(1) & 0xf0) >> 4) | ((int) (CH(2) & 0x03) << 4)); *lp++ = (Char) (0x80 | (CH(0) >> 6) | ((CH(1) & 0x0f) << 2)); *lp++ = (Char) (0x80 | (CH(0) & 0x3f)); } else if (c < 0x04000000) { *lp++ = (Char) (0xf8 | (CH(3) & 0x03)); *lp++ = (Char) (0x80 | (CH(2) >> 2)); *lp++ = (Char) (0x80 | ((int) (CH(1) & 0xf0) >> 4) | ((int) (CH(2) & 0x03) << 4)); *lp++ = (Char) (0x80 | (CH(0) >> 6) | ((CH(1) & 0x0f) << 2)); *lp++ = (Char) (0x80 | (CH(0) & 0x3f)); } else { *lp++ = (Char) (0xfc | ((int) (CH(3) & 0x40) >> 6)); *lp++ = (Char) (0x80 | (CH(3) & 0x3f)); *lp++ = (Char) (0x80 | (CH(2) >> 2)); *lp++ = (Char) (0x80 | (CH(1) >> 4) | ((CH(2) & 0x03) << 4)); *lp++ = (Char) (0x80 | (CH(0) >> 6) | ((CH(1) & 0x0f) << 2)); *lp++ = (Char) (0x80 | (CH(0) & 0x3f)); } return lp; #undef CH } /* * Convert a UTF-8 multibyte character to an Unicode value, returning a pointer * past the converted UTF-8 input. The first 256 values align with ISO-8859-1, * making it possible to use this to convert to Latin-1. * * If the conversion fails, return null. */ Char * convertFromUTF8(Char *lp, unsigned *cp) { int want; /* * Find the number of bytes we will need from the source. */ if ((*lp & 0x80) == 0) { want = 1; } else if ((*lp & 0xe0) == 0xc0) { want = 2; } else if ((*lp & 0xf0) == 0xe0) { want = 3; } else if ((*lp & 0xf8) == 0xf0) { want = 4; } else if ((*lp & 0xfc) == 0xf8) { want = 5; } else if ((*lp & 0xfe) == 0xfc) { want = 6; } else { want = 0; } if (want) { int have = 1; while (lp[have] != '\0') { if ((lp[have] & 0xc0) != 0x80) break; ++have; } if (want == have) { unsigned mask = 0; int j; int shift = 0; *cp = 0; switch (want) { case 1: mask = (*lp); break; case 2: mask = (*lp & 0x1f); break; case 3: mask = (*lp & 0x0f); break; case 4: mask = (*lp & 0x07); break; case 5: mask = (*lp & 0x03); break; case 6: mask = (*lp & 0x01); break; default: mask = 0; break; } for (j = 1; j < want; j++) { *cp |= (unsigned) ((lp[want - j] & 0x3f) << shift); shift += 6; } *cp |= mask << shift; lp += want; } else { *cp = BAD_ASCII; lp = NULL; } } else { *cp = BAD_ASCII; lp = NULL; } return lp; } /* * Returns true if the entire string is valid UTF-8. */ Boolean isValidUTF8(Char *lp) { Boolean result = True; while (*lp) { unsigned ch; Char *next = convertFromUTF8(lp, &ch); if (next == NULL || ch == 0) { result = False; break; } lp = next; } return result; } /* * Write data back to the PTY */ void writePtyData(int f, IChar *d, unsigned len) { unsigned n = (len << 1); if (VTbuffer->write_len <= len) { VTbuffer->write_len = n; VTbuffer->write_buf = (Char *) XtRealloc((char *) VTbuffer->write_buf, VTbuffer->write_len); } for (n = 0; n < len; n++) VTbuffer->write_buf[n] = (Char) d[n]; TRACE(("writePtyData %u:%s\n", n, visibleChars(VTbuffer->write_buf, n))); v_write(f, VTbuffer->write_buf, n); } #endif /* OPT_WIDE_CHARS */ #ifdef NO_LEAKS void noleaks_ptydata(void) { if (VTbuffer != 0) { #if OPT_WIDE_CHARS free(VTbuffer->write_buf); #endif FreeAndNull(VTbuffer); } } #endif #ifdef TEST_DRIVER #include "data.c" void NormalExit(void) { fprintf(stderr, "NormalExit!\n"); exit(EXIT_SUCCESS); } void Panic(const char *s, int a) { (void) s; (void) a; fprintf(stderr, "Panic!\n"); exit(EXIT_FAILURE); } #if OPT_WIDE_CHARS #ifdef ALLOWLOGGING void FlushLog(XtermWidget xw) { (void) xw; } #endif void v_write(int f, const Char *data, unsigned len) { (void) f; (void) data; (void) len; } void mk_wcwidth_init(int mode) { (void) mode; } void update_font_utf8_mode(void) { } static int message_level = 0; static int opt_all = 0; static int opt_illegal = 0; static int opt_convert = 0; static int opt_reverse = 0; static long total_test = 0; static long total_errs = 0; static void usage(void) { static const char *msg[] = { "Usage: test_ptydata [options] [c1[-c1b] [c2-[c2b] [...]]]", "", "Options:", " -a exercise all legal encode/decode to/from UTF-8", " -c call convertFromUTF8 rather than decodeUTF8", " -i ignore illegal UTF-8 when testing -r option", " -q quieter", " -r reverse/decode from UTF-8 byte-string to/from Unicode", " -v more verbose" }; size_t n; for (n = 0; n < sizeof(msg) / sizeof(msg[0]); ++n) { fprintf(stderr, "%s\n", msg[n]); } exit(EXIT_FAILURE); } /* * http://www.unicode.org/versions/corrigendum1.html, table 3.1B */ #define OkRange(n,lo,hi) \ if (value[n] < lo || value[n] > hi) { \ result = False; \ break; \ } static Bool is_legal_utf8(const Char *value) { Bool result = True; Char ch; while ((ch = *value) != '\0') { if (ch <= 0x7f) { ++value; } else if (ch >= 0xc2 && ch <= 0xdf) { OkRange(1, 0x80, 0xbf); value += 2; } else if (ch == 0xe0) { OkRange(1, 0xa0, 0xbf); OkRange(2, 0x80, 0xbf); value += 3; } else if (ch >= 0xe1 && ch <= 0xef) { OkRange(1, 0x80, 0xbf); OkRange(2, 0x80, 0xbf); value += 3; } else if (ch == 0xf0) { OkRange(1, 0x90, 0xbf); OkRange(2, 0x80, 0xbf); OkRange(3, 0x80, 0xbf); value += 4; } else if (ch >= 0xf1 && ch <= 0xf3) { OkRange(1, 0x80, 0xbf); OkRange(2, 0x80, 0xbf); OkRange(3, 0x80, 0xbf); value += 4; } else if (ch == 0xf4) { OkRange(1, 0x80, 0x8f); OkRange(2, 0x80, 0xbf); OkRange(3, 0x80, 0xbf); value += 4; } else { result = False; break; } } return result; } static void test_utf8_convert(void) { unsigned c_in, c_out; Char buffer[10]; Char *result; unsigned limit = 0x110000; unsigned success = 0; unsigned bucket[256]; memset(bucket, 0, sizeof(bucket)); for (c_in = 0; c_in < limit; ++c_in) { memset(buffer, 0, sizeof(buffer)); if ((result = convertToUTF8(buffer, c_in)) == 0) { TRACE(("conversion of U+%04X to UTF-8 failed\n", c_in)); } else { if ((result = convertFromUTF8(buffer, &c_out)) == 0) { TRACE(("conversion of U+%04X from UTF-8 failed\n", c_in)); } else if (c_in != c_out) { TRACE(("conversion of U+%04X to/from UTF-8 gave U+%04X\n", c_in, c_out)); } else { while (result-- != buffer) { bucket[*result]++; } ++success; } } } TRACE(("%u/%u successful\n", success, limit)); for (c_in = 0; c_in < 256; ++c_in) { if ((c_in % 8) == 0) { TRACE((" %02X:", c_in)); } TRACE((" %8X", bucket[c_in])); if (((c_in + 1) % 8) == 0) { TRACE(("\n")); } } } static int decode_one(const char *source, char **target) { int result = -1; long check; int radix = 0; if ((source[0] == 'u' || source[0] == 'U') && source[1] == '+') { source += 2; radix = 16; } else if (source[0] == '0' && source[1] == 'b') { source += 2; radix = 2; } check = strtol(source, target, radix); if (*target != NULL && *target != source) result = (int) check; return result; } static int decode_range(const char *source, int *lo, int *hi) { int result = 0; char *after1; char *after2; if ((*lo = decode_one(source, &after1)) >= 0) { after1 += strspn(after1, ":-.\t "); if ((*hi = decode_one(after1, &after2)) < 0) { *hi = *lo; } result = 1; } return result; } #define MAX_BYTES 6 static void do_range(const char *source) { int lo, hi; TScreen screen; memset(&screen, 0, sizeof(screen)); if (decode_range(source, &lo, &hi)) { while (lo <= hi) { unsigned c_in = (unsigned) lo++; PtyData *data; Char *next; Char buffer[MAX_BYTES + 1]; if (opt_reverse) { Bool skip = False; Bool first = True; int j, k; for (j = 0; j < MAX_BYTES; ++j) { unsigned long bits = ((unsigned long) c_in >> (8 * j)); if ((buffer[j] = (Char) bits) == 0) { skip = (bits != 0); break; } } if (skip) continue; initPtyData(&data); for (k = 0; k <= j; ++k) { data->buffer[k] = buffer[j - k - 1]; } if (opt_illegal && !is_legal_utf8(data->buffer)) { free(data); continue; } if (message_level > 1) { printf("TEST "); for (k = 0; k < j; ++k) { printf("%02X", data->buffer[k]); } } data->next = data->buffer; data->last = data->buffer + j; while (decodeUtf8(&screen, data)) { total_test++; if (data->utf_data == UCS_REPL) total_errs++; data->next += data->utf_size; if (message_level > 1) { printf("%s%04X", first ? " ->" : ", ", data->utf_data); } first = False; } if (!first) total_test--; if (message_level > 1) { printf("\n"); fflush(stdout); } free(data); } else if (opt_convert) { unsigned c_out; Char *result; memset(buffer, 0, sizeof(buffer)); if ((result = next = convertToUTF8(buffer, c_in)) == 0) { fprintf(stderr, "conversion of U+%04X to UTF-8 failed\n", c_in); } else if ((result = convertFromUTF8(buffer, &c_out)) == 0) { fprintf(stderr, "conversion of U+%04X from UTF-8 failed\n", c_in); total_errs++; } else if (c_in != c_out) { fprintf(stderr, "conversion of U+%04X to/from UTF-8 gave U+%04X\n", c_in, c_out); } else if (message_level > 1) { *next = '\0'; printf("TEST %04X (%d:%s) ->%04X\n", c_in, (int) (next - buffer), buffer, c_out); fflush(stdout); } } else { initPtyData(&data); next = convertToUTF8(data->buffer, c_in); *next = 0; data->next = data->buffer; data->last = next; decodeUtf8(&screen, data); if (message_level > 1) { printf("TEST %04X (%d:%s) ->%04X\n", c_in, (int) (next - data->buffer), data->buffer, data->utf_data); fflush(stdout); } if (c_in != data->utf_data) { fprintf(stderr, "Mismatch: %04X vs %04X\n", c_in, data->utf_data); total_errs++; } free(data); } total_test++; } } } int main(int argc, char **argv) { int ch; setlocale(LC_ALL, ""); while ((ch = getopt(argc, argv, "aciqrv")) != -1) { switch (ch) { case 'a': opt_all = 1; break; case 'c': opt_convert = 1; break; case 'i': opt_illegal = 1; break; case 'q': message_level--; break; case 'r': opt_reverse = 1; break; case 'v': message_level++; break; default: usage(); } } if (opt_all) { test_utf8_convert(); } else { if (optind >= argc) usage(); while (optind < argc) { do_range(argv[optind++]); } if (total_test) { printf("%ld/%ld mismatches (%.0f%%)\n", total_errs, total_test, (100.0 * (double) total_errs) / (double) total_test); } } return EXIT_SUCCESS; } #else int main(int argc, char **argv) { (void) argc; (void) argv; printf("Nothing to be done here...\n"); return EXIT_SUCCESS; } #endif /* OPT_WIDE_CHARS */ #endif