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.
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.
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]
23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
27 #pragma ident "%Z%%M% %I% %E% SMI"
36 #include <sys/types.h>
38 #include <sys/param.h>
46 #include "_loc_path.h"
49 #include "nlspath_checks.h"
51 static int process_nlspath(const char *, const char *,
52 const char *, char **);
53 static char *replace_nls_option(char *, const char *, char *,
54 char *, char *, char *, char *);
57 _real_gettext_u(const char *domain
, const char *msgid1
, const char *msgid2
,
58 unsigned long int ln
, int category
, int plural
)
60 char msgfile
[MAXPATHLEN
]; /* 1024 */
61 char mydomain
[TEXTDOMAINMAX
+ 1]; /* 256 + 1 */
62 char *cur_binding
; /* points to current binding in list */
63 char *cur_locale
, *cur_domain
, *result
, *nlspath
;
64 char *msgloc
, *cb
, *cur_domain_binding
;
66 unsigned int n
= (unsigned int)ln
; /* we don't need long for n */
67 uint32_t cur_domain_len
, cblen
;
69 struct msg_pack
*mp
, omp
;
72 gprintf(0, "*************** _real_gettext_u(\"%s\", \"%s\", "
73 "\"%s\", %d, %d, %d)\n",
74 domain
? domain
: "NULL", msgid1
? msgid1
: "NULL",
75 msgid2
? msgid2
: "NULL", n
, category
, plural
);
76 gprintf(0, "***************** global_gt: 0x%p\n", global_gt
);
77 printgt(global_gt
, 1);
83 mp
= memset(&omp
, 0, sizeof (omp
)); /* msg pack */
86 * category may be LC_MESSAGES or LC_TIME
87 * locale contains the value of 'category'
89 cur_locale
= setlocale(category
, NULL
);
91 language
= getenv("LANGUAGE"); /* for GNU */
93 if (!*language
|| strchr(language
, '/') != NULL
) {
95 * LANGUAGE is an empty string or
96 * LANGUAGE contains '/'.
104 * Query the current domain if domain argument is NULL pointer
107 if (domain
== NULL
) {
109 * if NULL is specified for domainname,
110 * use the currently bound domain.
112 cur_domain
= _textdomain_u(NULL
, mydomain
);
113 } else if (!*domain
) {
115 * if an empty string is specified
117 cur_domain
= DEFAULT_DOMAIN
;
119 cur_domain
= (char *)domain
;
122 hash_domain
= get_hashid(cur_domain
, &cur_domain_len
);
123 if (cur_domain_len
> TEXTDOMAINMAX
) {
124 /* domain is invalid, return msg_id */
125 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
129 nlspath
= getenv("NLSPATH"); /* get the content of NLSPATH */
130 if (nlspath
== NULL
|| !*nlspath
) {
131 /* no NLSPATH is defined in the environ */
132 if ((*cur_locale
== 'C') && (*(cur_locale
+ 1) == '\0')) {
135 * return the original msgid immediately.
137 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
145 msgloc
= setlocale(LC_MESSAGES
, NULL
);
147 ret
= process_nlspath(cur_domain
, msgloc
,
148 (const char *)nlspath
, &cur_binding
);
151 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
153 } else if (ret
== 0) {
158 cur_domain_binding
= _real_bindtextdomain_u(cur_domain
,
160 if (cur_domain_binding
== NULL
) {
161 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
167 mp
->msgfile
= msgfile
;
168 mp
->domain
= cur_domain
;
169 mp
->binding
= cur_domain_binding
;
170 mp
->locale
= cur_locale
;
171 mp
->language
= language
;
172 mp
->domain_len
= cur_domain_len
;
174 mp
->category
= category
;
176 mp
->hash_domain
= hash_domain
;
179 * Spec1170 requires that we use NLSPATH if it's defined, to
180 * override any system default variables. If NLSPATH is not
181 * defined or if a message catalog is not found in any of the
182 * components (bindings) specified by NLSPATH, dcgettext_u() will
183 * search for the message catalog in either a) the binding path set
184 * by any previous application calls to bindtextdomain() or
185 * b) the default binding path (/usr/lib/locale). Save the original
186 * binding path so that we can search it if the message catalog
187 * is not found via NLSPATH. The original binding is restored before
188 * returning from this routine because the gettext routines should
189 * not change the binding set by the application. This allows
190 * bindtextdomain() to be called once for all gettext() calls in the
195 * First, examine NLSPATH
199 * NLSPATH binding has been successfully built
202 gprintf(0, "************************** examining NLSPATH\n");
203 gprintf(0, " cur_binding: \"%s\"\n",
204 cur_binding
? cur_binding
: "(null)");
209 * cur_binding always ends with ':' before a null
212 while (*cur_binding
) {
214 while (*cur_binding
!= ':')
216 cblen
= cur_binding
- cb
;
218 if (cblen
>= MAXPATHLEN
) {
219 /* cur_binding too long */
220 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
224 (void) memcpy(mp
->msgfile
, cb
, cblen
);
225 *(mp
->msgfile
+ cblen
) = '\0';
228 gprintf(0, "*******************"
229 "********************* \n");
230 gprintf(0, " msgfile: \"%s\"\n",
231 msgfile
? msgfile
: "(null)");
232 gprintf(0, "*******************"
233 "********************* \n");
235 result
= handle_mo(mp
);
243 mp
->binding
= cur_domain_binding
;
245 * Next, examine LANGUAGE
249 ret_msg
= handle_lang(mp
);
250 if (ret_msg
!= NULL
) {
251 /* valid msg found in GNU MO */
255 * handle_lang() may have overridden locale
257 mp
->locale
= cur_locale
;
262 * Finally, handle a single binding
267 if (mk_msgfile(mp
) == NULL
) {
268 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
272 result
= handle_mo(mp
);
276 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
278 } /* _real_gettext_u */
281 free_all(nlstmp, nnp, pathname, ppaths, lang)
284 free_all(Nlstmp
*nlstmp
, Nls_node
*nnp
, char *pathname
,
285 char *ppaths
, char *lang
)
309 * process_nlspath(): process the NLSPATH environment variable.
311 * this routine looks at NLSPATH in the environment,
312 * and will try to build up the binding list based
313 * on the settings of NLSPATH.
317 * 0: No error, but no binding list has been built
318 * 1: No error, and a binding list has been built
322 process_nlspath(const char *cur_domain
, const char *cur_msgloc
,
323 const char *nlspath
, char **binding
)
325 char *s
; /* generic string ptr */
326 char *territory
; /* our current territory element */
327 char *codeset
; /* our current codeset element */
328 char *s1
; /* for handling territory */
329 char *s2
; /* for handling codeset */
330 char *lang
= NULL
; /* our current language element */
331 char *ppaths
= NULL
; /* ptr to all of the templates */
332 char *pathname
= NULL
; /* the full pathname to the file */
333 size_t nlspath_len
, domain_len
, locale_len
, path_len
;
334 size_t ppaths_len
= 0;
335 Nlstmp
*nlstmp
= NULL
;
336 Nlstmp
*pnlstmp
, *qnlstmp
;
337 Nls_node
*cur_nls
, *nnp
;
338 Gettext_t
*gt
= global_gt
;
341 gprintf(0, "*************** process_nlspath(%s, %s, "
342 "%s, 0x%p)\n", cur_domain
,
343 cur_msgloc
, nlspath
, (void *)binding
);
346 cur_nls
= gt
->c_n_node
;
348 (strcmp(cur_nls
->domain
, cur_domain
) == 0 &&
349 strcmp(cur_nls
->locale
, cur_msgloc
) == 0 &&
350 strcmp(cur_nls
->nlspath
, nlspath
) == 0)) {
351 *binding
= cur_nls
->ppaths
;
357 if (strcmp(nnp
->domain
, cur_domain
) == 0 &&
358 strcmp(nnp
->locale
, cur_msgloc
) == 0 &&
359 strcmp(nnp
->nlspath
, nlspath
) == 0) {
362 *binding
= nnp
->ppaths
;
369 nnp
= calloc(1, sizeof (Nls_node
));
375 nlspath_len
= strlen(nlspath
);
376 locale_len
= strlen(cur_msgloc
);
377 domain_len
= strlen(cur_domain
);
379 lang
= s
= strdup(cur_msgloc
);
389 } else if (*s
== '.') {
399 * now that we have the name (domain), we first look through NLSPATH,
400 * in an attempt to get the locale. A locale may be completely
401 * specified as "language_territory.codeset". NLSPATH consists
402 * of templates separated by ":" characters. The following are
403 * the substitution values within NLSPATH:
404 * %N = DEFAULT_DOMAIN
405 * %L = The value of the LC_MESSAGES category.
406 * %I = The language element from the LC_MESSAGES category.
407 * %t = The territory element from the LC_MESSAGES category.
408 * %c = The codeset element from the LC_MESSAGES category.
409 * %% = A single character.
410 * if we find one of these characters, we will carry out the
411 * appropriate substitution.
413 pathname
= malloc(MAXPATHLEN
);
414 if (pathname
== NULL
) {
418 s
= (char *)nlspath
; /* s has a content of NLSPATH */
419 while (*s
) { /* march through NLSPATH */
420 (void) memset(pathname
, 0, MAXPATHLEN
);
423 * this loop only occurs if we have to replace
424 * ":" by "name". replace_nls_option() below
425 * will handle the subsequent ":"'s.
427 pnlstmp
= malloc(sizeof (Nlstmp
));
428 if (pnlstmp
== NULL
) {
433 (void) memcpy(pnlstmp
->pathname
, cur_domain
,
435 pnlstmp
->len
= domain_len
;
436 ppaths_len
+= domain_len
+ 1; /* 1 for ':' */
439 pnlstmp
->next
= NULL
;
441 if (nlstmp
== NULL
) {
445 qnlstmp
->next
= pnlstmp
;
452 /* replace Substitution field */
453 s
= replace_nls_option(s
, cur_domain
, pathname
,
454 (char *)cur_msgloc
, lang
, territory
, codeset
);
461 /* if we've found a valid file: */
463 /* add template to end of chain of pathnames: */
464 pnlstmp
= malloc(sizeof (Nlstmp
));
465 if (pnlstmp
== NULL
) {
470 path_len
= strlen(pathname
);
471 (void) memcpy(pnlstmp
->pathname
, pathname
,
473 pnlstmp
->len
= path_len
;
474 ppaths_len
+= path_len
+ 1; /* 1 for ':' */
476 pnlstmp
->next
= NULL
;
478 if (nlstmp
== NULL
) {
482 qnlstmp
->next
= pnlstmp
;
491 * now that we've handled the pathname templates, concatenate them
492 * all into the form "template1:template2:..." for _bindtextdomain_u()
495 if (ppaths_len
!= 0) {
496 ppaths
= malloc(ppaths_len
+ 1);
497 if (ppaths
== NULL
) {
508 * extract the path templates (fifo), and concatenate them
509 * all into a ":" separated string for _bindtextdomain_u()
514 (void) memcpy(s
, pnlstmp
->pathname
, pnlstmp
->len
);
517 qnlstmp
= pnlstmp
->next
;
524 nnp
->domain
= malloc(domain_len
+ 1);
525 if (nnp
->domain
== NULL
) {
529 (void) memcpy(nnp
->domain
, cur_domain
, domain_len
+ 1);
531 nnp
->locale
= malloc(locale_len
+ 1);
532 if (nnp
->locale
== NULL
) {
536 (void) memcpy(nnp
->locale
, cur_msgloc
, locale_len
+ 1);
538 nnp
->nlspath
= malloc(nlspath_len
+ 1);
539 if (nnp
->nlspath
== NULL
) {
543 (void) memcpy(nnp
->nlspath
, nlspath
, nlspath_len
+ 1);
545 nnp
->ppaths
= ppaths
;
547 nnp
->next
= gt
->n_node
;
554 gprintf(0, "*************** existing process_nlspath with success\n");
555 gprintf(0, " binding: \"%s\"\n", ppaths
);
563 * This routine will replace substitution parameters in NLSPATH
564 * with appropiate values.
567 replace_nls_option(char *s
, const char *name
, char *pathname
,
568 char *locale
, char *lang
, char *territory
, char *codeset
)
574 limit
= pathname
+ MAXPATHLEN
- 1;
576 while (*s
&& *s
!= ':') {
579 * %% is considered a single % character (XPG).
580 * %L : LC_MESSAGES (XPG4) LANG(XPG3)
581 * %l : The language element from the current locale.
586 else if (*++s
== 'N') {
589 while (*u
&& (t
< limit
))
592 } else if (*s
== 'L') {
595 while (*u
&& (t
< limit
))
598 } else if (*s
== 'l') {
601 while (*u
&& (*u
!= '_') &&
605 } else if (*s
== 't') {
608 while (*u
&& (*u
!= '.') &&
612 } else if (*s
== 'c') {
615 while (*u
&& (t
< limit
))
623 /* too long pathname */
634 _real_bindtextdomain_u(const char *domain
, const char *binding
,
637 struct domain_binding
*bind
, *prev
;
638 Gettext_t
*gt
= global_gt
;
642 gprintf(0, "*************** _real_bindtextdomain_u(\"%s\", "
644 (domain
? domain
: ""),
645 (binding
? binding
: ""),
646 (type
== TP_BINDING
) ? "TP_BINDING" : "TP_CODESET");
650 * If domain is a NULL pointer, no change will occur regardless
651 * of binding value. Just return NULL.
653 if (domain
== NULL
) {
658 * Global Binding is not supported any more.
659 * Just return NULL if domain is NULL string.
661 if (*domain
== '\0') {
665 /* linear search for binding, rebind if found, add if not */
666 bind
= FIRSTBIND(gt
);
667 prev
= NULL
; /* Two pointers needed for pointer operations */
670 if (strcmp(domain
, bind
->domain
) == 0) {
674 binding_addr
= (type
== TP_BINDING
) ? &(bind
->binding
) :
676 if (binding
== NULL
) {
678 * if binding is null, then query
680 return (*binding_addr
);
682 /* replace existing binding with new binding */
686 if ((*binding_addr
= strdup(binding
)) == NULL
) {
692 return (*binding_addr
);
698 /* domain has not been found in the list at this point */
701 * domain is not found, but binding is not NULL.
702 * Then add a new node to the end of linked list.
705 if ((bind
= malloc(sizeof (Dbinding
))) == NULL
) {
708 if ((bind
->domain
= strdup(domain
)) == NULL
) {
712 bind
->binding
= NULL
;
713 bind
->codeset
= NULL
;
714 binding_addr
= (type
== TP_BINDING
) ? &(bind
->binding
) :
716 if ((*binding_addr
= strdup(binding
)) == NULL
) {
724 /* reached the end of list */
728 FIRSTBIND(gt
) = bind
;
734 return (*binding_addr
);
737 * Query of domain which is not found in the list
738 * for bindtextdomain, returns defaultbind
739 * for bind_textdomain_codeset, returns NULL
741 if (type
== TP_BINDING
) {
742 return ((char *)defaultbind
);
748 /* Must not reach here */
750 } /* _real_bindtextdomain_u */
754 _textdomain_u(const char *domain
, char *result
)
758 Gettext_t
*gt
= global_gt
;
761 gprintf(0, "*************** _textdomain_u(\"%s\", 0x%p)\n",
762 (domain
? domain
: ""), (void *)result
);
765 /* Query is performed for NULL domain pointer */
766 if (domain
== NULL
) {
767 (void) strcpy(result
, CURRENT_DOMAIN(gt
));
771 /* check for error. */
773 * domain is limited to TEXTDOMAINMAX bytes
774 * excluding a null termination.
776 domain_len
= strlen(domain
);
777 if (domain_len
> TEXTDOMAINMAX
) {
783 * Calling textdomain() with a null domain string sets
784 * the domain to the default domain.
785 * If non-null string is passwd, current domain is changed
789 /* actually this if clause should be protected from signals */
790 if (*domain
== '\0') {
791 if (CURRENT_DOMAIN(gt
) != default_domain
) {
792 free(CURRENT_DOMAIN(gt
));
793 CURRENT_DOMAIN(gt
) = (char *)default_domain
;
796 p
= malloc(domain_len
+ 1);
799 (void) strcpy(p
, domain
);
800 if (CURRENT_DOMAIN(gt
) != default_domain
)
801 free(CURRENT_DOMAIN(gt
));
802 CURRENT_DOMAIN(gt
) = p
;
805 (void) strcpy(result
, CURRENT_DOMAIN(gt
));
807 } /* _textdomain_u */
810 * key_2_text() translates msd_id into target string.
813 key_2_text(Msg_s_node
*messages
, const char *key_string
)
817 unsigned char kc
= *(unsigned char *)key_string
;
818 struct msg_struct
*check_msg_list
;
821 gprintf(0, "*************** key_2_text(0x%p, \"%s\")\n",
822 (void *)messages
, key_string
? key_string
: "(null)");
823 printsunmsg(messages
, 1);
826 check_msg_list
= messages
->msg_list
+
827 messages
->msg_file_info
->msg_mid
;
829 msg_id_str
= messages
->msg_ids
+
830 check_msg_list
->msgid_offset
;
832 * To maintain the compatibility with Zeus mo file,
833 * msg_id's are stored in descending order.
834 * If the ascending order is desired, change "msgfmt.c"
835 * and switch msg_id_str and key_string in the following
836 * strcmp() statement.
838 val
= *(unsigned char *)msg_id_str
- kc
;
840 (val
= strcmp(msg_id_str
, key_string
)) == 0) {
841 return (messages
->msg_strs
842 + check_msg_list
->msgstr_offset
);
843 } else if (val
< 0) {
844 if (check_msg_list
->less
!= LEAFINDICATOR
) {
845 check_msg_list
= messages
->msg_list
+
846 check_msg_list
->less
;
849 return ((char *)key_string
);
852 if (check_msg_list
->more
!= LEAFINDICATOR
) {
853 check_msg_list
= messages
->msg_list
+
854 check_msg_list
->more
;
857 return ((char *)key_string
);
867 * addr - address to the mmapped file
868 * size - size of the file
871 * 0 - either T_SUN_MO or T_ILL_MO has been set
872 * 1 - not a valid sun mo file
876 sun_setmsg(Msg_node
*mnp
, char *addr
, size_t size
)
878 struct msg_info
*sun_header
;
880 uint32_t first_4bytes
;
882 int struct_size
, struct_size_old
;
885 if (size
< sizeof (struct msg_info
)) {
886 /* invalid mo file */
887 mnp
->type
= T_ILL_MO
;
889 gprintf(0, "********* exiting sun_setmsg\n");
895 first_4bytes
= *((uint32_t *)(uintptr_t)addr
);
896 if (first_4bytes
> INT_MAX
) {
898 * Not a valid sun mo file
903 /* candidate for sun mo */
905 sun_header
= (struct msg_info
*)(uintptr_t)addr
;
906 mid
= sun_header
->msg_mid
;
907 count
= sun_header
->msg_count
;
908 msg_struct_size
= sun_header
->msg_struct_size
;
909 struct_size_old
= (int)(OLD_MSG_STRUCT_SIZE
* count
);
910 struct_size
= (int)(MSG_STRUCT_SIZE
* count
);
912 if ((((count
- 1) / 2) != mid
) ||
913 ((msg_struct_size
!= struct_size_old
) &&
914 (msg_struct_size
!= struct_size
))) {
915 /* invalid mo file */
916 mnp
->type
= T_ILL_MO
;
918 gprintf(0, "********* exiting sun_setmsg\n");
923 /* valid sun mo file */
925 p
= malloc(sizeof (Msg_s_node
));
930 p
->msg_file_info
= sun_header
;
931 p
->msg_list
= (struct msg_struct
*)(uintptr_t)
932 (addr
+ sizeof (struct msg_info
));
933 p
->msg_ids
= (char *)(addr
+ sizeof (struct msg_info
) +
935 p
->msg_strs
= (char *)(addr
+ sizeof (struct msg_info
) +
936 struct_size
+ sun_header
->str_count_msgid
);
939 mnp
->type
= T_SUN_MO
;
941 gprintf(0, "******** exiting sun_setmsg\n");
952 * addr - address to the mmapped file
953 * size - size of the file
960 setmsg(Msg_node
*mnp
, char *addr
, size_t size
)
963 if ((ret
= sun_setmsg(mnp
, addr
, size
)) <= 0)
966 return (gnu_setmsg(mnp
, addr
, size
));
970 handle_type_mo(Msg_node
*mnp
, struct msg_pack
*mp
)
980 mp
->status
|= ST_SUN_MO_FOUND
;
984 * *ngettext is called against
987 int exp
= (mp
->n
== 1);
988 result
= (char *)mp
->msgid1
;
990 result
= (char *)mp
->msgid2
;
993 result
= key_2_text(mnp
->msg
.sunmsg
, mp
->msgid1
);
995 result
= check_format(mp
->msgid1
, result
, 0);
1000 mp
->status
|= ST_GNU_MO_FOUND
;
1002 result
= gnu_key_2_text(mnp
->msg
.gnumsg
,
1003 get_codeset(mp
->domain
), mp
);
1005 if (result
== mp
->msgid1
|| result
== mp
->msgid2
) {
1006 /* no valid msg found */
1010 /* valid msg found */
1011 mp
->status
|= ST_GNU_MSG_FOUND
;
1013 if (!mnp
->trusted
) {
1014 result
= check_format(mp
->msgid1
, result
, 0);
1015 if (result
== mp
->msgid1
) {
1016 DFLTMSG(result
, mp
->msgid1
, mp
->msgid2
,
1022 /* this should never happen */
1023 DFLTMSG(result
, mp
->msgid1
, mp
->msgid2
, mp
->n
, mp
->plural
);
1030 * handle_mo() returns NULL if invalid MO found.
1033 handle_mo(struct msg_pack
*mp
)
1037 struct stat64 statbuf
;
1039 Gettext_t
*gt
= global_gt
;
1041 #define CONNECT_ENTRY \
1042 mnp->next = gt->m_node; \
1046 #ifdef GETTEXT_DEBUG
1047 gprintf(0, "*************** handle_mo(0x%p)\n", (void *)mp
);
1051 mnp
= check_cache(mp
);
1055 return (handle_type_mo(mnp
, mp
));
1059 * Valid entry not found in the cache
1061 mnp
= calloc(1, sizeof (Msg_node
));
1063 DFLTMSG(result
, mp
->msgid1
, mp
->msgid2
, mp
->n
, mp
->plural
);
1066 mnp
->hashid
= mp
->hash_domain
;
1067 mnp
->path
= strdup(mp
->msgfile
);
1068 if (mnp
->path
== NULL
) {
1070 DFLTMSG(result
, mp
->msgid1
, mp
->msgid2
, mp
->n
, mp
->plural
);
1074 fd
= nls_safe_open(mp
->msgfile
, &statbuf
, &mp
->trusted
, !mp
->nlsp
);
1075 if ((fd
== -1) || (statbuf
.st_size
> LONG_MAX
)) {
1078 mnp
->type
= T_ILL_MO
;
1082 mp
->fsz
= (size_t)statbuf
.st_size
;
1083 mp
->addr
= mmap(NULL
, mp
->fsz
, PROT_READ
, MAP_SHARED
, fd
, 0);
1086 if (mp
->addr
== MAP_FAILED
) {
1089 DFLTMSG(result
, mp
->msgid1
, mp
->msgid2
, mp
->n
, mp
->plural
);
1093 if (setmsg(mnp
, (char *)mp
->addr
, mp
->fsz
) == -1) {
1096 (void) munmap(mp
->addr
, mp
->fsz
);
1097 DFLTMSG(result
, mp
->msgid1
, mp
->msgid2
, mp
->n
, mp
->plural
);
1100 mnp
->trusted
= mp
->trusted
;
1103 return (handle_type_mo(mnp
, mp
));