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