xref: /illumos-gate/usr/src/lib/libc/port/i18n/gettext_util.c (revision 8c69cc8fbe729fa7b091e901c4b50508ccc6bb33)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include "lint.h"
28 #include "mtlib.h"
29 #include <ctype.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/types.h>
34 #include <unistd.h>
35 #include <sys/mman.h>
36 #include <langinfo.h>
37 #include "libc.h"
38 #include "_loc_path.h"
39 #include "msgfmt.h"
40 #include "gettext.h"
41 
42 #ifdef GETTEXT_DEBUG
43 #include "plural_parser.h"
44 #include <stdarg.h>
45 #endif
46 
47 static const char	*category_name[] = {
48 	"LC_CTYPE",
49 	"LC_NUMERIC",
50 	"LC_TIME",
51 	"LC_COLLATE",
52 	"LC_MONETARY",
53 	"LC_MESSAGES"
54 };
55 
56 static const int	category_name_len[] = {
57 	8,
58 	10,
59 	7,
60 	10,
61 	11,
62 	11
63 };
64 
65 /*
66  * mk_msgfile
67  *
68  * INPUT
69  * mp -	uses the following members:
70  * 	msgfile  - buffer to store the pathname to the message file
71  *	binding  - directory pathname bound to specified domain
72  *	cblen    - length of binding
73  *	locale   - locale name
74  *	domain   - domain name
75  *	category - category
76  *	domain_len - length of domain name
77  *
78  * OUTPUT
79  * mp->msgfile - pathname to the message file is stored
80  *
81  * RETURN
82  * mp->msgfile is returned
83  */
84 char *
85 mk_msgfile(struct msg_pack *mp)
86 {
87 	const char *q;
88 	char	*p;
89 	const char	*catstr;
90 	uint32_t	cblen, loclen, catlen, totallen;
91 
92 #ifdef GETTEXT_DEBUG
93 	gprintf(0, "*************** mk_msgfile(0x%p)\n", (void *)mp);
94 	printmp(mp, 1);
95 #endif
96 
97 	p = mp->msgfile;
98 	q = mp->binding;
99 	while (*p = *q++)
100 		p++;
101 	cblen = (uint32_t)(p - mp->msgfile);
102 	if (*(p - 1) != '/') {
103 		/*
104 		 * if the last character of binding
105 		 * isn't a '/', adding '/'.
106 		 */
107 		if (cblen + 1 >= MAXPATHLEN) {
108 			/* MAXPATHLEN includes a null termination */
109 			return (NULL);
110 		}
111 		*p++ = '/';
112 		cblen++;
113 	}
114 
115 	loclen = strlen(mp->locale);
116 	catstr = category_name[mp->category];
117 	catlen = (uint32_t)category_name_len[mp->category];
118 	/*
119 	 * totallen is the length of the msgfile
120 	 * pathname excluding a null termination.
121 	 */
122 
123 	totallen = cblen + loclen + 1 + catlen + 1 +
124 	    mp->domain_len + MSGFILESUFFIXLEN;
125 	if (totallen >= MAXPATHLEN)
126 		return (NULL);
127 
128 	q = mp->locale;
129 	while (*p++ = *q++)
130 		;
131 	*(p - 1) = '/';
132 	while (*p++ = *catstr++)
133 		;
134 	*(p - 1) = '/';
135 	q = mp->domain;
136 	while (*p = *q++)
137 		p++;
138 	q = MSGFILESUFFIX;
139 	while (*p++ = *q++)
140 		;
141 
142 #ifdef GETTEXT_DEBUG
143 	gprintf(0, "*************** Exiting mk_msgfile\n");
144 	gprintf(0, "mp->msgfile: \"%s\"\n", mp->msgfile);
145 #endif
146 
147 	return (mp->msgfile);
148 }
149 
150 /*
151  * check_cache
152  *
153  * INPUT
154  * mp - may use the following members:
155  *	msgfile - pathname to the message catalog file
156  *	hash_domain - hash id of this domain
157  *
158  * RETURN
159  * non-NULL
160  *	pointer to the Msg_node object of the current message catalog
161  *	found in the cache
162  *
163  * NULL
164  *	this message catalog does not exist in the cache
165  */
166 Msg_node *
167 check_cache(struct msg_pack *mp)
168 {
169 	Msg_node	*cur_msg, *mnp;
170 	Gettext_t	*gt = global_gt;
171 
172 #ifdef GETTEXT_DEBUG
173 	gprintf(0, "*************** check_cache(0x%p)\n", mp);
174 	printmp(mp, 1);
175 #endif
176 
177 	cur_msg = gt->c_m_node;		/* current Msg_node */
178 	if (cur_msg &&
179 	    cur_msg->hashid == mp->hash_domain &&
180 	    strcmp(cur_msg->path, mp->msgfile) == 0) {
181 		/*
182 		 * msgfile is the same as the previous message file
183 		 */
184 #ifdef GETTEXT_DEBUG
185 		gprintf(0, "*** cache found\n");
186 		gprintf(0, "************* exiting check_cache\n");
187 		printmnp(cur_msg, 1);
188 #endif
189 		return (cur_msg);
190 	}
191 	mnp = gt->m_node;
192 	while (mnp) {
193 #ifdef GETTEXT_DEBUG
194 		gprintf(0, "========== descending the list\n");
195 		gprintf(0, "  hashid: %d, hash_domain: %d\n",
196 		    mnp->hashid, mp->hash_domain);
197 		printmnp(mnp, 1);
198 #endif
199 		if (mnp->hashid == mp->hash_domain &&
200 		    strcmp(mnp->path, mp->msgfile) == 0) {
201 #ifdef GETTEXT_DEBUG
202 			gprintf(0, "*** cache found\n");
203 			gprintf(0, "******* exiting check_cache\n");
204 			printmnp(mnp, 1);
205 #endif
206 			gt->c_m_node = mnp;
207 			return (mnp);
208 		}
209 		mnp = mnp->next;
210 	}
211 
212 #ifdef GETTEXT_DEBUG
213 	gprintf(0, "*** cache not found\n");
214 	gprintf(0, "******* exiting check_cache\n");
215 #endif
216 	return (NULL);
217 }
218 
219 char *
220 get_codeset(const char *domain)
221 {
222 	char	*codeset;
223 
224 #ifdef GETTEXT_DEBUG
225 	gprintf(0, "*************** get_codeset(\"%s\")\n",
226 	    domain ? domain : "(null)");
227 #endif
228 
229 	codeset = _real_bindtextdomain_u(domain, NULL, TP_CODESET);
230 	if (codeset == NULL) {
231 		/* no codeset is bound to this domain */
232 		codeset = nl_langinfo(CODESET);
233 	}
234 #ifdef GETTEXT_DEBUG
235 	gprintf(0, "*************** existing get_codeset(\"%s\")\n",
236 	    domain ? domain : "(null)");
237 	gprintf(0, "                = \"%s\"\n", codeset);
238 #endif
239 
240 	return (codeset);
241 }
242 
243 /*
244  * get_hashid (hashpjw)
245  *
246  * Calculates the hash value from the specified string.
247  * Actual hashid will be mod(hash value, PRIME_NUMBER).
248  *
249  * Ref: Compilers - Principles, Techniques, and Tools
250  * Aho, Sethi, and Ullman
251  */
252 uint32_t
253 get_hashid(const char *str, uint32_t *len)
254 {
255 	const unsigned char	*p = (unsigned char *)str;
256 	uint32_t	h = 0;
257 	uint32_t	g;
258 
259 	for (; *p; p++) {
260 		h = (h << 4) + *p;
261 		g = h & 0xf0000000;
262 		if (g) {
263 			h = h ^ (g >> 24);
264 			h = h ^ g;
265 		}
266 	}
267 
268 	if (len)
269 		*len = (uint32_t)(p - (unsigned char *)str);
270 	return (h);
271 }
272 
273 uint32_t
274 doswap32(uint32_t n)
275 {
276 	uint32_t	r;
277 
278 	r = (n << 24) | ((n & 0xff00) << 8) |
279 	    ((n >> 8) & 0xff00) | (n >> 24);
280 	return (r);
281 }
282 
283 #ifdef GETTEXT_DEBUG
284 static uint32_t
285 search_msg(Msg_g_node *p, const char *id, uint32_t hash_val,
286     struct gnu_msg_ent *m)
287 {
288 	char	*base = (char *)p->msg_file_info;
289 	uint32_t	hash_size, num_of_str, i, idx, inc;
290 	char	*ms;
291 
292 	num_of_str = p->num_of_str;
293 	hash_size = p->hash_size;
294 	idx = hash_val % hash_size;
295 	inc = 1 + (hash_val % (hash_size - 2));
296 
297 	while ((i = p->hash_table[idx]) != 0) {
298 		ms = (i <= num_of_str) ?
299 		    base + SWAP(p, m[i-1].offset) :
300 		    p->mchunk + p->d_msg[MSGID][i-num_of_str-1].offset;
301 		if (strcmp(id, ms) == 0) {
302 			/* found */
303 			return (i);
304 		}
305 		idx = (idx + inc) % hash_size;
306 	}
307 	/* not found */
308 	return (0);
309 }
310 
311 void
312 print_rev1_info(Msg_g_node *p)
313 {
314 	char	*base = (char *)p->msg_file_info;
315 	struct gnu_msg_info	*header = p->msg_file_info;
316 	struct gnu_msg_ent	*m;
317 	uint32_t	hv, hidx;
318 	char	*ms;
319 	enum gnu_msgidstr	v;
320 	int	x;
321 
322 #ifdef	GETTEXT_DEBUG_DYMMSG
323 	gprintf(0, "******** dynamic msgid/msgstr\n");
324 	for (v = MSGID; v <= MSGSTR; v++) {
325 		for (x = 0; x < p->num_of_d_str; x++) {
326 			gprintf(0, "len: %u\n", p->d_msg[v][x].len);
327 			gprintf(0, "str: \"%s\"\n",
328 			    p->mchunk + p->d_msg[v][x].offset);
329 		}
330 	}
331 #endif
332 #ifdef	GETTEXT_DEBUG_HASHTBL
333 	gprintf(0, "******** dynamic hash table\n");
334 	for (x = 0; x < p->hash_size; x++) {
335 		gprintf(0, "%d: %u\n", x, p->hash_table[x]);
336 	}
337 #endif
338 #ifdef	GETTEXT_DEBUG_CHECK_STMSGID
339 	gprintf(0, "******** sanity check of static msgid\n");
340 	m = (struct gnu_msg_ent *)(uintptr_t)
341 	    (base + SWAP(p, header->off_msgid_tbl));
342 	for (x = 0; x < p->num_of_str; x++) {
343 		ms = base + SWAP(p, m[x].offset);
344 		gprintf(0, "\"%s\"\n", ms);
345 		hv = get_hashid(ms, NULL);
346 		hidx = search_msg(p, ms, hv, m);
347 		if (hidx == 0) {
348 			gprintf(0,
349 			    "failed to find this msg in the hash table\n");
350 		} else {
351 			if (hidx != x + 1) {
352 				gprintf(0, "hash table mismatch\n");
353 			}
354 		}
355 	}
356 #endif
357 #ifdef	GETTEXT_DEBUG_CHECK_DYMMSGID
358 	gprintf(0, "******* sanity check of dynamic msgid\n");
359 	m = (struct gnu_msg_ent *)(uintptr_t)
360 	    (base + SWAP(p, header->off_msgid_tbl));
361 	for (x = 0; x < p->num_of_d_str; x++) {
362 		ms = p->mchunk + p->d_msg[MSGID][x].offset;
363 		gprintf(0, "\"%s\"\n", ms);
364 		hv = get_hashid(ms, NULL);
365 		hidx = search_msg(p, ms, hv, m);
366 		if (hidx == 0) {
367 			gprintf(0,
368 			    "failed to find this msg in the hash table\n");
369 		} else {
370 			if (hidx != x + p->num_of_str + 1) {
371 				gprintf(0, "hash table mismatch\n");
372 			}
373 		}
374 	}
375 #endif
376 }
377 
378 void
379 gprintf(int level, const char *format, ...)
380 {
381 	va_list	ap;
382 
383 	va_start(ap, format);
384 
385 	while (level-- > 0) {
386 		(void) fputs("   ", stdout);
387 	}
388 	(void) vprintf(format, ap);
389 	va_end(ap);
390 
391 	(void) fflush(stdout);
392 }
393 
394 void
395 printlist(void)
396 {
397 	struct domain_binding	*ppp;
398 	Gettext_t	*gt = global_gt;
399 
400 	gprintf(0, "=== Printing default list and regural list\n");
401 	gprintf(0, "   Default domain=<%s>, binding=<%s>\n",
402 	    DEFAULT_DOMAIN, defaultbind);
403 
404 	ppp = FIRSTBIND(gt);
405 	while (ppp) {
406 		gprintf(0, "   domain=<%s>, binding=<%s>, codeset=<%s>\n",
407 		    ppp->domain ? ppp->domain : "(null)",
408 		    ppp->binding ? ppp->binding : "(null)",
409 		    ppp->codeset ? ppp->codeset : "(null)");
410 		ppp = ppp->next;
411 	}
412 	(void) fflush(stdout);
413 }
414 
415 void
416 printmp(struct msg_pack *mp, int level)
417 {
418 	gprintf(level, "=== mp ===\n");
419 	gprintf(level, "   msgid1: \"%s\"\n",
420 	    mp->msgid1 ? mp->msgid1 : "(null)");
421 	gprintf(level, "   msgid2: \"%s\"\n",
422 	    mp->msgid2 ? mp->msgid2 : "(null)");
423 	gprintf(level, "   msgfile: \"%s\"\n",
424 	    mp->msgfile ? mp->msgfile : "(null)");
425 	gprintf(level, "   domain: \"%s\"\n",
426 	    mp->domain ? mp->domain : "(null)");
427 	gprintf(level, "   binding: \"%s\"\n",
428 	    mp->binding ? mp->binding : "(null)");
429 	gprintf(level, "   locale: \"%s\"\n",
430 	    mp->locale ? mp->locale : "(null)");
431 	gprintf(level, "   language: \"%s\"\n",
432 	    mp->language ? mp->language : "(null)");
433 	gprintf(level, "   addr: 0x%p\n", mp->addr);
434 	gprintf(level, "   fsz: %d\n", mp->fsz);
435 	gprintf(level, "   hash_domain: %d\n", mp->hash_domain);
436 	gprintf(level, "   domain_len: %d\n", mp->domain_len);
437 	gprintf(level, "   n: %d\n", mp->n);
438 	gprintf(level, "   category: \"%s\"\n",
439 	    category_name[mp->category]);
440 	gprintf(level, "   plural: %d\n", mp->plural);
441 	gprintf(level, "   nlsp: %d\n", mp->nlsp);
442 	gprintf(level, "   trusted: %d\n", mp->trusted);
443 	gprintf(level, "   status: %d\n", mp->status);
444 }
445 
446 void
447 printsunmsg(Msg_s_node *smnp, int level)
448 {
449 	gprintf(level, "=== sunmsg ===\n");
450 	gprintf(level, "   msg_file_info: 0x%p\n",
451 	    (void *)smnp->msg_file_info);
452 	gprintf(level, "      msg_mid: %d\n",
453 	    smnp->msg_file_info->msg_mid);
454 	gprintf(level, "      msg_count: %d\n",
455 	    smnp->msg_file_info->msg_count);
456 	gprintf(level, "      str_count_msgid: %d\n",
457 	    smnp->msg_file_info->str_count_msgid);
458 	gprintf(level, "      str_count_msgstr: %d\n",
459 	    smnp->msg_file_info->str_count_msgstr);
460 	gprintf(level, "      msg_struct_size: %d\n",
461 	    smnp->msg_file_info->msg_struct_size);
462 	gprintf(level, "   msg_list: 0x%p\n",
463 	    (void *)smnp->msg_list);
464 	gprintf(level, "   msg_ids: 0x%p\n",
465 	    (void *)smnp->msg_ids);
466 	gprintf(level, "   msg_strs: 0x%p\n",
467 	    (void *)smnp->msg_strs);
468 }
469 
470 void
471 printgnumsg(Msg_g_node *gmnp, int level)
472 {
473 	gprintf(level, "=== gnumsg ===\n");
474 	gprintf(level, "   msg_file_info: 0x%p\n", gmnp->msg_file_info);
475 	gprintf(level, "      magic: 0x%x\n",
476 	    gmnp->msg_file_info->magic);
477 	gprintf(level, "      revision: %d\n",
478 	    SWAP(gmnp, gmnp->msg_file_info->revision));
479 	gprintf(level, "      num_of_str: %d\n",
480 	    SWAP(gmnp, gmnp->msg_file_info->num_of_str));
481 	gprintf(level, "      off_msgid_tbl: %d\n",
482 	    SWAP(gmnp, gmnp->msg_file_info->off_msgid_tbl));
483 	gprintf(level, "      off_msgstr_tbl: %d\n",
484 	    SWAP(gmnp, gmnp->msg_file_info->off_msgstr_tbl));
485 	gprintf(level, "      sz_hashtbl: %d\n",
486 	    SWAP(gmnp, gmnp->msg_file_info->sz_hashtbl));
487 	gprintf(level, "      off_hashtbl: %d\n",
488 	    SWAP(gmnp, gmnp->msg_file_info->off_hashtbl));
489 	if (gmnp->flag & ST_REV1) {
490 		struct gnu_msg_rev1_info	*a =
491 		    (struct gnu_msg_rev1_info *)(uintptr_t)
492 		    ((char *)gmnp->msg_file_info +
493 		    sizeof (struct gnu_msg_info));
494 		gprintf(level, "      num_of_dynamic_macro: %d\n",
495 		    SWAP(gmnp, a->num_of_dynamic_macro));
496 		gprintf(level, "      off_dynamic_macro: %d\n",
497 		    SWAP(gmnp, a->off_dynamic_macro));
498 		gprintf(level, "      num_of_dynamic_str: %d\n",
499 		    SWAP(gmnp, a->num_of_dynamic_str));
500 		gprintf(level, "      off_dynamic_msgid_tbl: %d\n",
501 		    SWAP(gmnp, a->off_dynamic_msgid_tbl));
502 		gprintf(level, "      off_dynamic_msgstr_tbl: %d\n",
503 		    SWAP(gmnp, a->off_dynamic_msgstr_tbl));
504 	}
505 	gprintf(level, "   fsize: %lu\n", gmnp->fsize);
506 	gprintf(level, "   flag: %08x\n", gmnp->flag);
507 	gprintf(level, "   num_of_str: %u\n", gmnp->num_of_str);
508 	gprintf(level, "   num_of_d_str: %u\n", gmnp->num_of_d_str);
509 	gprintf(level, "   hash_size: %u\n", gmnp->hash_size);
510 	gprintf(level, "   hash_table: 0x%p\n", (void *)gmnp->hash_table);
511 	gprintf(level, "   d_msgid: 0x%p\n", (void *)gmnp->d_msg[MSGID]);
512 	gprintf(level, "   d_msgstr: 0x%p\n", (void *)gmnp->d_msg[MSGSTR]);
513 	gprintf(level, "   mchunk: 0x%p\n", (void *)gmnp->mchunk);
514 
515 	gprintf(level, "   src_encoding: \"%s\"\n",
516 	    gmnp->src_encoding ? gmnp->src_encoding : "(null)");
517 	gprintf(level, "   dst_encoding: \"%s\"\n",
518 	    gmnp->dst_encoding ? gmnp->dst_encoding : "(null)");
519 	gprintf(level, "   nplurals: %d\n",
520 	    gmnp->nplurals);
521 	gprintf(level, "   plural: 0x%p\n",
522 	    (void *)gmnp->plural);
523 	if (gmnp->plural)
524 		printexpr(gmnp->plural, level+1);
525 	gprintf(level, "   fd: 0x%p\n", (void *)gmnp->fd);
526 	gprintf(level, "   conv_msgstr: 0x%p\n",
527 	    (void *)gmnp->conv_msgstr);
528 }
529 
530 void
531 printexpr(struct expr *e, int level)
532 {
533 	static const char	*op_name[] = {
534 	    "NULL", "INIT", "EXP",
535 	    "NUM", "VAR", "?", ":", "||",
536 	    "&&", "==", "!=", ">", "<",
537 	    ">=", "<=", "+", "-", "*", "/",
538 	    "%", "!", "(", ")", "ERR"
539 	};
540 	switch (GETOPNUM(e->op)) {
541 	case 0:
542 		switch (GETTYPE(e->op)) {
543 		case T_NUM:
544 			gprintf(level, "NUM(%d)\n", e->num);
545 			break;
546 		case T_VAR:
547 			gprintf(level, "VAR(n)\n");
548 			break;
549 		}
550 		break;
551 	case 1:
552 		gprintf(level, "OP: !\n");
553 		printexpr(e->nodes[0], level+1);
554 		break;
555 	case 2:
556 		gprintf(level, "OP: %s\n", op_name[GETTYPE(e->op)]);
557 		printexpr(e->nodes[0], level+1);
558 		printexpr(e->nodes[1], level+1);
559 		break;
560 	case 3:
561 		gprintf(level, "OP: ?\n");
562 
563 		printexpr(e->nodes[0], level+1);
564 		printexpr(e->nodes[1], level+1);
565 		printexpr(e->nodes[2], level+1);
566 		break;
567 	}
568 }
569 
570 
571 void
572 printmnp(Msg_node *mnp, int level)
573 {
574 	gprintf(level, "=== mnp ===\n");
575 
576 	gprintf(level, "   hashid: %d\n", mnp->hashid);
577 	gprintf(level, "   type: \"%s\"\n",
578 	    mnp->type == T_ILL_MO ? "T_ILL_MO" :
579 	    mnp->type == T_SUN_MO ? "T_SUN_MO" :
580 	    mnp->type == T_GNU_MO ? "T_GNU_MO" :
581 	    "UNKNOWN TYPE");
582 	gprintf(level, "   path: \"%s\"\n",
583 	    mnp->path ? mnp->path : "(null)");
584 	gprintf(level, "   msg_file_trusted: %d\n",
585 	    mnp->trusted);
586 	if (mnp->type == T_SUN_MO)
587 		printsunmsg(mnp->msg.sunmsg, level+1);
588 	else if (mnp->type == T_GNU_MO)
589 		printgnumsg(mnp->msg.gnumsg, level+1);
590 	gprintf(level, "   next: 0x%p\n", (void *)mnp->next);
591 }
592 
593 void
594 printnls(Nls_node *n, int level)
595 {
596 	gprintf(level, "=== nls ===\n");
597 	gprintf(level, "   domain: \"%s\"\n", n->domain ? n->domain : "NULL");
598 	gprintf(level, "   locale: \"%s\"\n", n->locale ? n->locale : "NULL");
599 	gprintf(level, "   nlspath: \"%s\"\n", n->nlspath ? n->nlspath :
600 	    "NULL");
601 	gprintf(level, "   next: 0x%p\n", n->next);
602 }
603 
604 void
605 printdbind(Dbinding *d, int level)
606 {
607 	gprintf(level, "=== dbind ===\n");
608 	gprintf(level, "   domain: \"%s\"\n", d->domain ? d->domain : "NULL");
609 	gprintf(level, "   binding: \"%s\"\n", d->binding ? d->binding :
610 	    "NULL");
611 	gprintf(level, "   codeset: \"%s\"\n", d->codeset ? d->codeset :
612 	    "NULL");
613 	gprintf(level, "   next: 0x%p\n", d->next);
614 }
615 
616 void
617 printgt(Gettext_t *gt, int level)
618 {
619 	gprintf(level, "=== gt ===\n");
620 	gprintf(level, "   cur_domain: \"%s\"\n", gt->cur_domain);
621 	if (gt->dbind) {
622 		printdbind(gt->dbind, level+1);
623 	} else {
624 		gprintf(level, "   dbind: NULL\n");
625 	}
626 	if (gt->m_node) {
627 		printmnp(gt->m_node, level + 1);
628 	} else {
629 		gprintf(level, "   m_node: NULL\n");
630 	}
631 	if (gt->n_node) {
632 		printnls(gt->n_node, level + 1);
633 	} else {
634 		gprintf(level, "   n_node: NULL\n");
635 	}
636 	if (gt->c_m_node) {
637 		printmnp(gt->c_m_node, level + 1);
638 	} else {
639 		gprintf(level, "   c_m_node: NULL\n");
640 	}
641 	if (gt->c_n_node) {
642 		printnls(gt->c_n_node, level + 1);
643 	} else {
644 		gprintf(level, "   c_n_node: NULL\n");
645 	}
646 }
647 
648 #endif
649