1286432Sbapt/*
2286432Sbapt * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
3286432Sbapt * Copyright 2012 Garrett D'Amore <garrett@damore.org>  All rights reserved.
4286432Sbapt * Copyright 2015 John Marino <draco@marino.st>
5286432Sbapt *
6286432Sbapt * This source code is derived from the illumos localedef command, and
7286432Sbapt * provided under BSD-style license terms by Nexenta Systems, Inc.
8286432Sbapt *
9286432Sbapt * Redistribution and use in source and binary forms, with or without
10286432Sbapt * modification, are permitted provided that the following conditions
11286432Sbapt * are met:
12286432Sbapt *
13286432Sbapt * 1. Redistributions of source code must retain the above copyright
14286432Sbapt *    notice, this list of conditions and the following disclaimer.
15286432Sbapt * 2. Redistributions in binary form must reproduce the above copyright
16286432Sbapt *    notice, this list of conditions and the following disclaimer in the
17286432Sbapt *    documentation and/or other materials provided with the distribution.
18286432Sbapt *
19286432Sbapt * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20286432Sbapt * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21286432Sbapt * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22286432Sbapt * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23286432Sbapt * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24286432Sbapt * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25286432Sbapt * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26286432Sbapt * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27286432Sbapt * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28286432Sbapt * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29286432Sbapt * POSSIBILITY OF SUCH DAMAGE.
30286432Sbapt */
31286432Sbapt
32286432Sbapt/*
33286432Sbapt * LC_CTYPE database generation routines for localedef.
34286432Sbapt */
35286432Sbapt#include <sys/cdefs.h>
36286432Sbapt__FBSDID("$FreeBSD: stable/11/usr.bin/localedef/ctype.c 341631 2018-12-06 11:52:07Z yuripv $");
37286432Sbapt
38286481Sbapt#include <sys/tree.h>
39286432Sbapt
40286432Sbapt#include <stdio.h>
41286432Sbapt#include <stdlib.h>
42286432Sbapt#include <stddef.h>
43286432Sbapt#include <string.h>
44286432Sbapt#include <sys/types.h>
45286432Sbapt#include <wchar.h>
46286432Sbapt#include <ctype.h>
47286432Sbapt#include <wctype.h>
48286432Sbapt#include <unistd.h>
49286432Sbapt#include "localedef.h"
50286432Sbapt#include "parser.h"
51286432Sbapt#include "runefile.h"
52286432Sbapt
53286432Sbapt
54289263Sbapt/* Needed for bootstrapping, _CTYPE_N */
55289263Sbapt#ifndef _CTYPE_N
56289263Sbapt#define _CTYPE_N       0x00400000L
57289263Sbapt#endif
58289263Sbapt
59286432Sbapt#define _ISUPPER	_CTYPE_U
60286432Sbapt#define _ISLOWER	_CTYPE_L
61286432Sbapt#define	_ISDIGIT	_CTYPE_D
62286432Sbapt#define	_ISXDIGIT	_CTYPE_X
63286432Sbapt#define	_ISSPACE	_CTYPE_S
64286432Sbapt#define	_ISBLANK	_CTYPE_B
65286432Sbapt#define	_ISALPHA	_CTYPE_A
66286432Sbapt#define	_ISPUNCT	_CTYPE_P
67286432Sbapt#define	_ISGRAPH	_CTYPE_G
68286432Sbapt#define	_ISPRINT	_CTYPE_R
69286432Sbapt#define	_ISCNTRL	_CTYPE_C
70286432Sbapt#define	_E1		_CTYPE_Q
71286432Sbapt#define	_E2		_CTYPE_I
72286432Sbapt#define	_E3		0
73289263Sbapt#define	_E4		_CTYPE_N
74286432Sbapt#define	_E5		_CTYPE_T
75286432Sbapt
76286432Sbaptstatic wchar_t		last_ctype;
77286481Sbaptstatic int ctype_compare(const void *n1, const void *n2);
78286432Sbapt
79286432Sbapttypedef struct ctype_node {
80286432Sbapt	wchar_t wc;
81286432Sbapt	int32_t	ctype;
82286432Sbapt	int32_t	toupper;
83286432Sbapt	int32_t	tolower;
84286481Sbapt	RB_ENTRY(ctype_node) entry;
85286432Sbapt} ctype_node_t;
86286432Sbapt
87286484Sbaptstatic RB_HEAD(ctypes, ctype_node) ctypes;
88286482SbaptRB_GENERATE_STATIC(ctypes, ctype_node, entry, ctype_compare);
89286432Sbapt
90286432Sbaptstatic int
91286432Sbaptctype_compare(const void *n1, const void *n2)
92286432Sbapt{
93286432Sbapt	const ctype_node_t *c1 = n1;
94286432Sbapt	const ctype_node_t *c2 = n2;
95286432Sbapt
96286432Sbapt	return (c1->wc < c2->wc ? -1 : c1->wc > c2->wc ? 1 : 0);
97286432Sbapt}
98286432Sbapt
99286432Sbaptvoid
100286432Sbaptinit_ctype(void)
101286432Sbapt{
102286481Sbapt	RB_INIT(&ctypes);
103286432Sbapt}
104286432Sbapt
105286432Sbapt
106286432Sbaptstatic void
107286432Sbaptadd_ctype_impl(ctype_node_t *ctn)
108286432Sbapt{
109286432Sbapt	switch (last_kw) {
110286432Sbapt	case T_ISUPPER:
111286432Sbapt		ctn->ctype |= (_ISUPPER | _ISALPHA | _ISGRAPH | _ISPRINT);
112286432Sbapt		break;
113286432Sbapt	case T_ISLOWER:
114286432Sbapt		ctn->ctype |= (_ISLOWER | _ISALPHA | _ISGRAPH | _ISPRINT);
115286432Sbapt		break;
116286432Sbapt	case T_ISALPHA:
117286432Sbapt		ctn->ctype |= (_ISALPHA | _ISGRAPH | _ISPRINT);
118286432Sbapt		break;
119286432Sbapt	case T_ISDIGIT:
120289586Sbapt		ctn->ctype |= (_ISDIGIT | _ISGRAPH | _ISPRINT | _ISXDIGIT | _E4);
121286432Sbapt		break;
122286432Sbapt	case T_ISSPACE:
123341631Syuripv		/*
124341631Syuripv		 * This can be troublesome as <form-feed>, <newline>,
125341631Syuripv		 * <carriage-return>, <tab>, and <vertical-tab> are defined both
126341631Syuripv		 * as space and cntrl, and POSIX doesn't allow cntrl/print
127341631Syuripv		 * combination.  We will take care of this in dump_ctype().
128341631Syuripv		 */
129341631Syuripv		ctn->ctype |= (_ISSPACE | _ISPRINT);
130286432Sbapt		break;
131286432Sbapt	case T_ISCNTRL:
132286432Sbapt		ctn->ctype |= _ISCNTRL;
133286432Sbapt		break;
134286432Sbapt	case T_ISGRAPH:
135286432Sbapt		ctn->ctype |= (_ISGRAPH | _ISPRINT);
136286432Sbapt		break;
137286432Sbapt	case T_ISPRINT:
138286432Sbapt		ctn->ctype |= _ISPRINT;
139286432Sbapt		break;
140286432Sbapt	case T_ISPUNCT:
141286432Sbapt		ctn->ctype |= (_ISPUNCT | _ISGRAPH | _ISPRINT);
142286432Sbapt		break;
143286432Sbapt	case T_ISXDIGIT:
144289590Sbapt		ctn->ctype |= (_ISXDIGIT | _ISPRINT);
145286432Sbapt		break;
146286432Sbapt	case T_ISBLANK:
147286432Sbapt		ctn->ctype |= (_ISBLANK | _ISSPACE);
148286432Sbapt		break;
149286432Sbapt	case T_ISPHONOGRAM:
150286432Sbapt		ctn->ctype |= (_E1 | _ISPRINT | _ISGRAPH);
151286432Sbapt		break;
152286432Sbapt	case T_ISIDEOGRAM:
153286432Sbapt		ctn->ctype |= (_E2 | _ISPRINT | _ISGRAPH);
154286432Sbapt		break;
155286432Sbapt	case T_ISENGLISH:
156286432Sbapt		ctn->ctype |= (_E3 | _ISPRINT | _ISGRAPH);
157286432Sbapt		break;
158286432Sbapt	case T_ISNUMBER:
159286432Sbapt		ctn->ctype |= (_E4 | _ISPRINT | _ISGRAPH);
160286432Sbapt		break;
161286432Sbapt	case T_ISSPECIAL:
162286432Sbapt		ctn->ctype |= (_E5 | _ISPRINT | _ISGRAPH);
163286432Sbapt		break;
164286432Sbapt	case T_ISALNUM:
165286432Sbapt		/*
166286432Sbapt		 * We can't do anything with this.  The character
167286432Sbapt		 * should already be specified as a digit or alpha.
168286432Sbapt		 */
169286432Sbapt		break;
170286432Sbapt	default:
171286432Sbapt		errf("not a valid character class");
172286432Sbapt	}
173286432Sbapt}
174286432Sbapt
175286432Sbaptstatic ctype_node_t *
176286432Sbaptget_ctype(wchar_t wc)
177286432Sbapt{
178286432Sbapt	ctype_node_t	srch;
179286432Sbapt	ctype_node_t	*ctn;
180286432Sbapt
181286432Sbapt	srch.wc = wc;
182286481Sbapt	if ((ctn = RB_FIND(ctypes, &ctypes, &srch)) == NULL) {
183286432Sbapt		if ((ctn = calloc(1, sizeof (*ctn))) == NULL) {
184286432Sbapt			errf("out of memory");
185286432Sbapt			return (NULL);
186286432Sbapt		}
187286432Sbapt		ctn->wc = wc;
188286432Sbapt
189286481Sbapt		RB_INSERT(ctypes, &ctypes, ctn);
190286432Sbapt	}
191286432Sbapt	return (ctn);
192286432Sbapt}
193286432Sbapt
194286432Sbaptvoid
195286432Sbaptadd_ctype(int val)
196286432Sbapt{
197286432Sbapt	ctype_node_t	*ctn;
198286432Sbapt
199286432Sbapt	if ((ctn = get_ctype(val)) == NULL) {
200286432Sbapt		INTERR;
201286432Sbapt		return;
202286432Sbapt	}
203286432Sbapt	add_ctype_impl(ctn);
204286432Sbapt	last_ctype = ctn->wc;
205286432Sbapt}
206286432Sbapt
207286432Sbaptvoid
208290517Sbaptadd_ctype_range(wchar_t end)
209286432Sbapt{
210286432Sbapt	ctype_node_t	*ctn;
211286432Sbapt	wchar_t		cur;
212286432Sbapt
213286432Sbapt	if (end < last_ctype) {
214286432Sbapt		errf("malformed character range (%u ... %u))",
215286432Sbapt		    last_ctype, end);
216286432Sbapt		return;
217286432Sbapt	}
218286432Sbapt	for (cur = last_ctype + 1; cur <= end; cur++) {
219286432Sbapt		if ((ctn = get_ctype(cur)) == NULL) {
220286432Sbapt			INTERR;
221286432Sbapt			return;
222286432Sbapt		}
223286432Sbapt		add_ctype_impl(ctn);
224286432Sbapt	}
225286432Sbapt	last_ctype = end;
226286432Sbapt
227286432Sbapt}
228286432Sbapt
229286432Sbapt/*
230286432Sbapt * A word about widths: if the width mask is specified, then libc
231286432Sbapt * unconditionally honors it.  Otherwise, it assumes printable
232286432Sbapt * characters have width 1, and non-printable characters have width
233286432Sbapt * -1 (except for NULL which is special with with 0).  Hence, we have
234286432Sbapt * no need to inject defaults here -- the "default" unset value of 0
235286432Sbapt * indicates that libc should use its own logic in wcwidth as described.
236286432Sbapt */
237286432Sbaptvoid
238286432Sbaptadd_width(int wc, int width)
239286432Sbapt{
240286432Sbapt	ctype_node_t	*ctn;
241286432Sbapt
242286432Sbapt	if ((ctn = get_ctype(wc)) == NULL) {
243286432Sbapt		INTERR;
244286432Sbapt		return;
245286432Sbapt	}
246286432Sbapt	ctn->ctype &= ~(_CTYPE_SWM);
247286432Sbapt	switch (width) {
248286432Sbapt	case 0:
249286432Sbapt		ctn->ctype |= _CTYPE_SW0;
250286432Sbapt		break;
251286432Sbapt	case 1:
252286432Sbapt		ctn->ctype |= _CTYPE_SW1;
253286432Sbapt		break;
254286432Sbapt	case 2:
255286432Sbapt		ctn->ctype |= _CTYPE_SW2;
256286432Sbapt		break;
257286432Sbapt	case 3:
258286432Sbapt		ctn->ctype |= _CTYPE_SW3;
259286432Sbapt		break;
260286432Sbapt	}
261286432Sbapt}
262286432Sbapt
263286432Sbaptvoid
264286432Sbaptadd_width_range(int start, int end, int width)
265286432Sbapt{
266286432Sbapt	for (; start <= end; start++) {
267286432Sbapt		add_width(start, width);
268286432Sbapt	}
269286432Sbapt}
270286432Sbapt
271286432Sbaptvoid
272286432Sbaptadd_caseconv(int val, int wc)
273286432Sbapt{
274286432Sbapt	ctype_node_t	*ctn;
275286432Sbapt
276286432Sbapt	ctn = get_ctype(val);
277286432Sbapt	if (ctn == NULL) {
278286432Sbapt		INTERR;
279286432Sbapt		return;
280286432Sbapt	}
281286432Sbapt
282286432Sbapt	switch (last_kw) {
283286432Sbapt	case T_TOUPPER:
284286432Sbapt		ctn->toupper = wc;
285286432Sbapt		break;
286286432Sbapt	case T_TOLOWER:
287286432Sbapt		ctn->tolower = wc;
288286432Sbapt		break;
289286432Sbapt	default:
290286432Sbapt		INTERR;
291286432Sbapt		break;
292286432Sbapt	}
293286432Sbapt}
294286432Sbapt
295286432Sbaptvoid
296286432Sbaptdump_ctype(void)
297286432Sbapt{
298286432Sbapt	FILE		*f;
299286432Sbapt	_FileRuneLocale	rl;
300286432Sbapt	ctype_node_t	*ctn, *last_ct, *last_lo, *last_up;
301286432Sbapt	_FileRuneEntry	*ct = NULL;
302286432Sbapt	_FileRuneEntry	*lo = NULL;
303286432Sbapt	_FileRuneEntry	*up = NULL;
304286432Sbapt	wchar_t		wc;
305286432Sbapt
306286432Sbapt	(void) memset(&rl, 0, sizeof (rl));
307286432Sbapt	last_ct = NULL;
308286432Sbapt	last_lo = NULL;
309286432Sbapt	last_up = NULL;
310286432Sbapt
311286432Sbapt	if ((f = open_category()) == NULL)
312286432Sbapt		return;
313286432Sbapt
314286432Sbapt	(void) memcpy(rl.magic, _FILE_RUNE_MAGIC_1, 8);
315315224Spfg	(void) strlcpy(rl.encoding, get_wide_encoding(), sizeof (rl.encoding));
316286432Sbapt
317286432Sbapt	/*
318286432Sbapt	 * Initialize the identity map.
319286432Sbapt	 */
320286432Sbapt	for (wc = 0; (unsigned)wc < _CACHED_RUNES; wc++) {
321286432Sbapt		rl.maplower[wc] = wc;
322286432Sbapt		rl.mapupper[wc] = wc;
323286432Sbapt	}
324286432Sbapt
325286481Sbapt	RB_FOREACH(ctn, ctypes, &ctypes) {
326286432Sbapt		int conflict = 0;
327286432Sbapt
328286432Sbapt		wc = ctn->wc;
329286432Sbapt
330286432Sbapt		/*
331286432Sbapt		 * POSIX requires certain portable characters have
332286432Sbapt		 * certain types.  Add them if they are missing.
333286432Sbapt		 */
334286432Sbapt		if ((wc >= 1) && (wc <= 127)) {
335286432Sbapt			if ((wc >= 'A') && (wc <= 'Z'))
336286432Sbapt				ctn->ctype |= _ISUPPER;
337286432Sbapt			if ((wc >= 'a') && (wc <= 'z'))
338286432Sbapt				ctn->ctype |= _ISLOWER;
339286432Sbapt			if ((wc >= '0') && (wc <= '9'))
340286432Sbapt				ctn->ctype |= _ISDIGIT;
341297057Spfg			if (wc == ' ')
342297057Spfg				ctn->ctype |= _ISPRINT;
343286432Sbapt			if (strchr(" \f\n\r\t\v", (char)wc) != NULL)
344286432Sbapt				ctn->ctype |= _ISSPACE;
345286432Sbapt			if (strchr("0123456789ABCDEFabcdef", (char)wc) != NULL)
346286432Sbapt				ctn->ctype |= _ISXDIGIT;
347286432Sbapt			if (strchr(" \t", (char)wc))
348286432Sbapt				ctn->ctype |= _ISBLANK;
349286432Sbapt
350286432Sbapt			/*
351286432Sbapt			 * Technically these settings are only
352286432Sbapt			 * required for the C locale.  However, it
353286432Sbapt			 * turns out that because of the historical
354286432Sbapt			 * version of isprint(), we need them for all
355286432Sbapt			 * locales as well.  Note that these are not
356286432Sbapt			 * necessarily valid punctation characters in
357286432Sbapt			 * the current language, but ispunct() needs
358286432Sbapt			 * to return TRUE for them.
359286432Sbapt			 */
360286432Sbapt			if (strchr("!\"'#$%&()*+,-./:;<=>?@[\\]^_`{|}~",
361286432Sbapt			    (char)wc))
362286432Sbapt				ctn->ctype |= _ISPUNCT;
363286432Sbapt		}
364286432Sbapt
365286432Sbapt		/*
366286432Sbapt		 * POSIX also requires that certain types imply
367286432Sbapt		 * others.  Add any inferred types here.
368286432Sbapt		 */
369286432Sbapt		if (ctn->ctype & (_ISUPPER |_ISLOWER))
370286432Sbapt			ctn->ctype |= _ISALPHA;
371286432Sbapt		if (ctn->ctype & _ISDIGIT)
372286432Sbapt			ctn->ctype |= _ISXDIGIT;
373286432Sbapt		if (ctn->ctype & _ISBLANK)
374286432Sbapt			ctn->ctype |= _ISSPACE;
375286432Sbapt		if (ctn->ctype & (_ISALPHA|_ISDIGIT|_ISXDIGIT))
376286432Sbapt			ctn->ctype |= _ISGRAPH;
377286432Sbapt		if (ctn->ctype & _ISGRAPH)
378286432Sbapt			ctn->ctype |= _ISPRINT;
379286432Sbapt
380286432Sbapt		/*
381341631Syuripv		 * POSIX requires that certain combinations are invalid.
382341631Syuripv		 * Try fixing the cases we know about (see add_ctype_impl()).
383286432Sbapt		 */
384341631Syuripv		if ((ctn->ctype & (_ISSPACE|_ISCNTRL)) == (_ISSPACE|_ISCNTRL))
385341631Syuripv			ctn->ctype &= ~_ISPRINT;
386341631Syuripv
387341631Syuripv		/*
388341631Syuripv		 * Finally, don't flag remaining cases as a fatal error,
389341631Syuripv		 * and just warn about them.
390341631Syuripv		 */
391286432Sbapt		if ((ctn->ctype & _ISALPHA) &&
392286432Sbapt		    (ctn->ctype & (_ISPUNCT|_ISDIGIT)))
393286432Sbapt			conflict++;
394315224Spfg		if ((ctn->ctype & _ISPUNCT) &&
395286432Sbapt		    (ctn->ctype & (_ISDIGIT|_ISALPHA|_ISXDIGIT)))
396286432Sbapt			conflict++;
397286432Sbapt		if ((ctn->ctype & _ISSPACE) && (ctn->ctype & _ISGRAPH))
398286432Sbapt			conflict++;
399315224Spfg		if ((ctn->ctype & _ISCNTRL) && (ctn->ctype & _ISPRINT))
400286432Sbapt			conflict++;
401286432Sbapt		if ((wc == ' ') && (ctn->ctype & (_ISPUNCT|_ISGRAPH)))
402286432Sbapt			conflict++;
403286432Sbapt
404286432Sbapt		if (conflict) {
405286432Sbapt			warn("conflicting classes for character 0x%x (%x)",
406286432Sbapt			    wc, ctn->ctype);
407286432Sbapt		}
408286432Sbapt		/*
409286432Sbapt		 * Handle the lower 256 characters using the simple
410286432Sbapt		 * optimization.  Note that if we have not defined the
411286432Sbapt		 * upper/lower case, then we identity map it.
412286432Sbapt		 */
413286432Sbapt		if ((unsigned)wc < _CACHED_RUNES) {
414286432Sbapt			rl.runetype[wc] = ctn->ctype;
415286432Sbapt			if (ctn->tolower)
416286432Sbapt				rl.maplower[wc] = ctn->tolower;
417286432Sbapt			if (ctn->toupper)
418286432Sbapt				rl.mapupper[wc] = ctn->toupper;
419286432Sbapt			continue;
420286432Sbapt		}
421286432Sbapt
422308330Sbapt		if ((last_ct != NULL) && (last_ct->ctype == ctn->ctype) &&
423308330Sbapt		    (last_ct->wc + 1 == wc)) {
424286432Sbapt			ct[rl.runetype_ext_nranges-1].max = wc;
425286432Sbapt		} else {
426286432Sbapt			rl.runetype_ext_nranges++;
427286432Sbapt			ct = realloc(ct,
428286432Sbapt			    sizeof (*ct) * rl.runetype_ext_nranges);
429286432Sbapt			ct[rl.runetype_ext_nranges - 1].min = wc;
430286432Sbapt			ct[rl.runetype_ext_nranges - 1].max = wc;
431286432Sbapt			ct[rl.runetype_ext_nranges - 1].map = ctn->ctype;
432286432Sbapt		}
433308330Sbapt		last_ct = ctn;
434286432Sbapt		if (ctn->tolower == 0) {
435286432Sbapt			last_lo = NULL;
436286432Sbapt		} else if ((last_lo != NULL) &&
437286432Sbapt		    (last_lo->tolower + 1 == ctn->tolower)) {
438286432Sbapt			lo[rl.maplower_ext_nranges-1].max = wc;
439286432Sbapt			last_lo = ctn;
440286432Sbapt		} else {
441286432Sbapt			rl.maplower_ext_nranges++;
442286432Sbapt			lo = realloc(lo,
443286432Sbapt			    sizeof (*lo) * rl.maplower_ext_nranges);
444286432Sbapt			lo[rl.maplower_ext_nranges - 1].min = wc;
445286432Sbapt			lo[rl.maplower_ext_nranges - 1].max = wc;
446286432Sbapt			lo[rl.maplower_ext_nranges - 1].map = ctn->tolower;
447286432Sbapt			last_lo = ctn;
448286432Sbapt		}
449286432Sbapt
450286432Sbapt		if (ctn->toupper == 0) {
451286432Sbapt			last_up = NULL;
452286432Sbapt		} else if ((last_up != NULL) &&
453286432Sbapt		    (last_up->toupper + 1 == ctn->toupper)) {
454286432Sbapt			up[rl.mapupper_ext_nranges-1].max = wc;
455286432Sbapt			last_up = ctn;
456286432Sbapt		} else {
457286432Sbapt			rl.mapupper_ext_nranges++;
458286432Sbapt			up = realloc(up,
459286432Sbapt			    sizeof (*up) * rl.mapupper_ext_nranges);
460286432Sbapt			up[rl.mapupper_ext_nranges - 1].min = wc;
461286432Sbapt			up[rl.mapupper_ext_nranges - 1].max = wc;
462286432Sbapt			up[rl.mapupper_ext_nranges - 1].map = ctn->toupper;
463286432Sbapt			last_up = ctn;
464286432Sbapt		}
465286432Sbapt	}
466286432Sbapt
467286432Sbapt	if ((wr_category(&rl, sizeof (rl), f) < 0) ||
468286432Sbapt	    (wr_category(ct, sizeof (*ct) * rl.runetype_ext_nranges, f) < 0) ||
469286432Sbapt	    (wr_category(lo, sizeof (*lo) * rl.maplower_ext_nranges, f) < 0) ||
470286432Sbapt	    (wr_category(up, sizeof (*up) * rl.mapupper_ext_nranges, f) < 0)) {
471286432Sbapt		return;
472286432Sbapt	}
473286432Sbapt
474286432Sbapt	close_category(f);
475286432Sbapt}
476