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 * Copyright 2015 Joyent, Inc.
37 #include <sys/types.h>
39 #include <sys/param.h>
47 #include "_loc_path.h"
50 #include "nlspath_checks.h"
52 static int process_nlspath(const char *, const char *,
53 const char *, char **);
54 static char *replace_nls_option(char *, const char *, char *,
55 char *, char *, char *, char *);
58 _real_gettext_u(const char *domain
, const char *msgid1
, const char *msgid2
,
59 unsigned long int ln
, int category
, int plural
, locale_t loc
)
61 char msgfile
[MAXPATHLEN
]; /* 1024 */
62 char mydomain
[TEXTDOMAINMAX
+ 1]; /* 256 + 1 */
63 char *cur_binding
; /* points to current binding in list */
64 const char *cur_locale
;
65 char *cur_domain
, *result
, *nlspath
;
66 char *msgloc
, *cb
, *cur_domain_binding
;
68 unsigned int n
= (unsigned int)ln
; /* we don't need long for n */
69 uint32_t cur_domain_len
, cblen
;
71 struct msg_pack
*mp
, omp
;
74 gprintf(0, "*************** _real_gettext_u(\"%s\", \"%s\", "
75 "\"%s\", %d, %d, %d)\n",
76 domain
? domain
: "NULL", msgid1
? msgid1
: "NULL",
77 msgid2
? msgid2
: "NULL", n
, category
, plural
);
78 gprintf(0, "***************** global_gt: 0x%p\n", global_gt
);
79 printgt(global_gt
, 1);
85 mp
= memset(&omp
, 0, sizeof (omp
)); /* msg pack */
88 * category may be LC_MESSAGES or LC_TIME
89 * locale contains the value of 'category'
92 loc
= uselocale(NULL
);
93 cur_locale
= current_locale(loc
, category
);
95 language
= getenv("LANGUAGE"); /* for GNU */
97 if (!*language
|| strchr(language
, '/') != NULL
) {
99 * LANGUAGE is an empty string or
100 * LANGUAGE contains '/'.
108 * Query the current domain if domain argument is NULL pointer
111 if (domain
== NULL
) {
113 * if NULL is specified for domainname,
114 * use the currently bound domain.
116 cur_domain
= _textdomain_u(NULL
, mydomain
);
117 } else if (!*domain
) {
119 * if an empty string is specified
121 cur_domain
= DEFAULT_DOMAIN
;
123 cur_domain
= (char *)domain
;
126 hash_domain
= get_hashid(cur_domain
, &cur_domain_len
);
127 if (cur_domain_len
> TEXTDOMAINMAX
) {
128 /* domain is invalid, return msg_id */
129 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
133 nlspath
= getenv("NLSPATH"); /* get the content of NLSPATH */
134 if (nlspath
== NULL
|| !*nlspath
) {
135 /* no NLSPATH is defined in the environ */
136 if ((*cur_locale
== 'C') && (*(cur_locale
+ 1) == '\0')) {
139 * return the original msgid immediately.
141 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
149 msgloc
= current_locale(loc
, LC_MESSAGES
);
151 ret
= process_nlspath(cur_domain
, msgloc
,
152 (const char *)nlspath
, &cur_binding
);
155 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
157 } else if (ret
== 0) {
162 cur_domain_binding
= _real_bindtextdomain_u(cur_domain
,
164 if (cur_domain_binding
== NULL
) {
165 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
171 mp
->msgfile
= msgfile
;
172 mp
->domain
= cur_domain
;
173 mp
->binding
= cur_domain_binding
;
174 mp
->locale
= cur_locale
;
175 mp
->language
= language
;
176 mp
->domain_len
= cur_domain_len
;
178 mp
->category
= category
;
180 mp
->hash_domain
= hash_domain
;
183 * Spec1170 requires that we use NLSPATH if it's defined, to
184 * override any system default variables. If NLSPATH is not
185 * defined or if a message catalog is not found in any of the
186 * components (bindings) specified by NLSPATH, dcgettext_u() will
187 * search for the message catalog in either a) the binding path set
188 * by any previous application calls to bindtextdomain() or
189 * b) the default binding path (/usr/lib/locale). Save the original
190 * binding path so that we can search it if the message catalog
191 * is not found via NLSPATH. The original binding is restored before
192 * returning from this routine because the gettext routines should
193 * not change the binding set by the application. This allows
194 * bindtextdomain() to be called once for all gettext() calls in the
199 * First, examine NLSPATH
203 * NLSPATH binding has been successfully built
206 gprintf(0, "************************** examining NLSPATH\n");
207 gprintf(0, " cur_binding: \"%s\"\n",
208 cur_binding
? cur_binding
: "(null)");
213 * cur_binding always ends with ':' before a null
216 while (*cur_binding
) {
218 while (*cur_binding
!= ':')
220 cblen
= cur_binding
- cb
;
222 if (cblen
>= MAXPATHLEN
) {
223 /* cur_binding too long */
224 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
228 (void) memcpy(mp
->msgfile
, cb
, cblen
);
229 *(mp
->msgfile
+ cblen
) = '\0';
232 gprintf(0, "*******************"
233 "********************* \n");
234 gprintf(0, " msgfile: \"%s\"\n",
235 msgfile
? msgfile
: "(null)");
236 gprintf(0, "*******************"
237 "********************* \n");
239 result
= handle_mo(mp
);
247 mp
->binding
= cur_domain_binding
;
249 * Next, examine LANGUAGE
253 ret_msg
= handle_lang(mp
);
254 if (ret_msg
!= NULL
) {
255 /* valid msg found in GNU MO */
259 * handle_lang() may have overridden locale
261 mp
->locale
= cur_locale
;
266 * Finally, handle a single binding
271 if (mk_msgfile(mp
) == NULL
) {
272 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
276 result
= handle_mo(mp
);
280 DFLTMSG(result
, msgid1
, msgid2
, n
, plural
);
282 } /* _real_gettext_u */
285 free_all(nlstmp, nnp, pathname, ppaths, lang)
288 free_all(Nlstmp
*nlstmp
, Nls_node
*nnp
, char *pathname
,
289 char *ppaths
, char *lang
)
308 * process_nlspath(): process the NLSPATH environment variable.
310 * this routine looks at NLSPATH in the environment,
311 * and will try to build up the binding list based
312 * on the settings of NLSPATH.
316 * 0: No error, but no binding list has been built
317 * 1: No error, and a binding list has been built
321 process_nlspath(const char *cur_domain
, const char *cur_msgloc
,
322 const char *nlspath
, char **binding
)
324 char *s
; /* generic string ptr */
325 char *territory
; /* our current territory element */
326 char *codeset
; /* our current codeset element */
327 char *s1
; /* for handling territory */
328 char *s2
; /* for handling codeset */
329 char *lang
= NULL
; /* our current language element */
330 char *ppaths
= NULL
; /* ptr to all of the templates */
331 char *pathname
= NULL
; /* the full pathname to the file */
332 size_t nlspath_len
, domain_len
, locale_len
, path_len
;
333 size_t ppaths_len
= 0;
334 Nlstmp
*nlstmp
= NULL
;
335 Nlstmp
*pnlstmp
, *qnlstmp
;
336 Nls_node
*cur_nls
, *nnp
;
337 Gettext_t
*gt
= global_gt
;
340 gprintf(0, "*************** process_nlspath(%s, %s, "
341 "%s, 0x%p)\n", cur_domain
,
342 cur_msgloc
, nlspath
, (void *)binding
);
345 cur_nls
= gt
->c_n_node
;
347 (strcmp(cur_nls
->domain
, cur_domain
) == 0 &&
348 strcmp(cur_nls
->locale
, cur_msgloc
) == 0 &&
349 strcmp(cur_nls
->nlspath
, nlspath
) == 0)) {
350 *binding
= cur_nls
->ppaths
;
356 if (strcmp(nnp
->domain
, cur_domain
) == 0 &&
357 strcmp(nnp
->locale
, cur_msgloc
) == 0 &&
358 strcmp(nnp
->nlspath
, nlspath
) == 0) {
361 *binding
= nnp
->ppaths
;
368 nnp
= calloc(1, sizeof (Nls_node
));
374 nlspath_len
= strlen(nlspath
);
375 locale_len
= strlen(cur_msgloc
);
376 domain_len
= strlen(cur_domain
);
378 lang
= s
= strdup(cur_msgloc
);
388 } else if (*s
== '.') {
398 * now that we have the name (domain), we first look through NLSPATH,
399 * in an attempt to get the locale. A locale may be completely
400 * specified as "language_territory.codeset". NLSPATH consists
401 * of templates separated by ":" characters. The following are
402 * the substitution values within NLSPATH:
403 * %N = DEFAULT_DOMAIN
404 * %L = The value of the LC_MESSAGES category.
405 * %I = The language element from the LC_MESSAGES category.
406 * %t = The territory element from the LC_MESSAGES category.
407 * %c = The codeset element from the LC_MESSAGES category.
408 * %% = A single character.
409 * if we find one of these characters, we will carry out the
410 * appropriate substitution.
412 pathname
= malloc(MAXPATHLEN
);
413 if (pathname
== NULL
) {
417 s
= (char *)nlspath
; /* s has a content of NLSPATH */
418 while (*s
) { /* march through NLSPATH */
419 (void) memset(pathname
, 0, MAXPATHLEN
);
422 * this loop only occurs if we have to replace
423 * ":" by "name". replace_nls_option() below
424 * will handle the subsequent ":"'s.
426 pnlstmp
= malloc(sizeof (Nlstmp
));
427 if (pnlstmp
== NULL
) {
432 (void) memcpy(pnlstmp
->pathname
, cur_domain
,
434 pnlstmp
->len
= domain_len
;
435 ppaths_len
+= domain_len
+ 1; /* 1 for ':' */
438 pnlstmp
->next
= NULL
;
440 if (nlstmp
== NULL
) {
444 qnlstmp
->next
= pnlstmp
;
451 /* replace Substitution field */
452 s
= replace_nls_option(s
, cur_domain
, pathname
,
453 (char *)cur_msgloc
, lang
, territory
, codeset
);
460 /* if we've found a valid file: */
462 /* add template to end of chain of pathnames: */
463 pnlstmp
= malloc(sizeof (Nlstmp
));
464 if (pnlstmp
== NULL
) {
469 path_len
= strlen(pathname
);
470 (void) memcpy(pnlstmp
->pathname
, pathname
,
472 pnlstmp
->len
= path_len
;
473 ppaths_len
+= path_len
+ 1; /* 1 for ':' */
475 pnlstmp
->next
= NULL
;
477 if (nlstmp
== NULL
) {
481 qnlstmp
->next
= pnlstmp
;
490 * now that we've handled the pathname templates, concatenate them
491 * all into the form "template1:template2:..." for _bindtextdomain_u()
494 if (ppaths_len
!= 0) {
495 ppaths
= malloc(ppaths_len
+ 1);
496 if (ppaths
== NULL
) {
507 * extract the path templates (fifo), and concatenate them
508 * all into a ":" separated string for _bindtextdomain_u()
513 (void) memcpy(s
, pnlstmp
->pathname
, pnlstmp
->len
);
516 qnlstmp
= pnlstmp
->next
;
523 nnp
->domain
= malloc(domain_len
+ 1);
524 if (nnp
->domain
== NULL
) {
528 (void) memcpy(nnp
->domain
, cur_domain
, domain_len
+ 1);
530 nnp
->locale
= malloc(locale_len
+ 1);
531 if (nnp
->locale
== NULL
) {
535 (void) memcpy(nnp
->locale
, cur_msgloc
, locale_len
+ 1);
537 nnp
->nlspath
= malloc(nlspath_len
+ 1);
538 if (nnp
->nlspath
== NULL
) {
542 (void) memcpy(nnp
->nlspath
, nlspath
, nlspath_len
+ 1);
544 nnp
->ppaths
= ppaths
;
546 nnp
->next
= gt
->n_node
;
553 gprintf(0, "*************** existing process_nlspath with success\n");
554 gprintf(0, " binding: \"%s\"\n", ppaths
);
562 * This routine will replace substitution parameters in NLSPATH
563 * with appropiate values.
566 replace_nls_option(char *s
, const char *name
, char *pathname
,
567 char *locale
, char *lang
, char *territory
, char *codeset
)
573 limit
= pathname
+ MAXPATHLEN
- 1;
575 while (*s
&& *s
!= ':') {
578 * %% is considered a single % character (XPG).
579 * %L : LC_MESSAGES (XPG4) LANG(XPG3)
580 * %l : The language element from the current locale.
585 else if (*++s
== 'N') {
588 while (*u
&& (t
< limit
))
591 } else if (*s
== 'L') {
594 while (*u
&& (t
< limit
))
597 } else if (*s
== 'l') {
600 while (*u
&& (*u
!= '_') &&
604 } else if (*s
== 't') {
607 while (*u
&& (*u
!= '.') &&
611 } else if (*s
== 'c') {
614 while (*u
&& (t
< limit
))
622 /* too long pathname */
633 _real_bindtextdomain_u(const char *domain
, const char *binding
,
636 struct domain_binding
*bind
, *prev
;
637 Gettext_t
*gt
= global_gt
;
641 gprintf(0, "*************** _real_bindtextdomain_u(\"%s\", "
643 (domain
? domain
: ""),
644 (binding
? binding
: ""),
645 (type
== TP_BINDING
) ? "TP_BINDING" : "TP_CODESET");
649 * If domain is a NULL pointer, no change will occur regardless
650 * of binding value. Just return NULL.
652 if (domain
== NULL
) {
657 * Global Binding is not supported any more.
658 * Just return NULL if domain is NULL string.
660 if (*domain
== '\0') {
664 /* linear search for binding, rebind if found, add if not */
665 bind
= FIRSTBIND(gt
);
666 prev
= NULL
; /* Two pointers needed for pointer operations */
669 if (strcmp(domain
, bind
->domain
) == 0) {
673 binding_addr
= (type
== TP_BINDING
) ? &(bind
->binding
) :
675 if (binding
== NULL
) {
677 * if binding is null, then query
679 return (*binding_addr
);
681 /* replace existing binding with new binding */
685 if ((*binding_addr
= strdup(binding
)) == NULL
) {
691 return (*binding_addr
);
697 /* domain has not been found in the list at this point */
700 * domain is not found, but binding is not NULL.
701 * Then add a new node to the end of linked list.
704 if ((bind
= malloc(sizeof (Dbinding
))) == NULL
) {
707 if ((bind
->domain
= strdup(domain
)) == NULL
) {
711 bind
->binding
= NULL
;
712 bind
->codeset
= NULL
;
713 binding_addr
= (type
== TP_BINDING
) ? &(bind
->binding
) :
715 if ((*binding_addr
= strdup(binding
)) == NULL
) {
723 /* reached the end of list */
727 FIRSTBIND(gt
) = bind
;
733 return (*binding_addr
);
736 * Query of domain which is not found in the list
737 * for bindtextdomain, returns defaultbind
738 * for bind_textdomain_codeset, returns NULL
740 if (type
== TP_BINDING
) {
741 return ((char *)defaultbind
);
747 /* Must not reach here */
749 } /* _real_bindtextdomain_u */
753 _textdomain_u(const char *domain
, char *result
)
757 Gettext_t
*gt
= global_gt
;
760 gprintf(0, "*************** _textdomain_u(\"%s\", 0x%p)\n",
761 (domain
? domain
: ""), (void *)result
);
764 /* Query is performed for NULL domain pointer */
765 if (domain
== NULL
) {
766 (void) strcpy(result
, CURRENT_DOMAIN(gt
));
770 /* check for error. */
772 * domain is limited to TEXTDOMAINMAX bytes
773 * excluding a null termination.
775 domain_len
= strlen(domain
);
776 if (domain_len
> TEXTDOMAINMAX
) {
782 * Calling textdomain() with a null domain string sets
783 * the domain to the default domain.
784 * If non-null string is passwd, current domain is changed
788 /* actually this if clause should be protected from signals */
789 if (*domain
== '\0') {
790 if (CURRENT_DOMAIN(gt
) != default_domain
) {
791 free(CURRENT_DOMAIN(gt
));
792 CURRENT_DOMAIN(gt
) = (char *)default_domain
;
795 p
= malloc(domain_len
+ 1);
798 (void) strcpy(p
, domain
);
799 if (CURRENT_DOMAIN(gt
) != default_domain
)
800 free(CURRENT_DOMAIN(gt
));
801 CURRENT_DOMAIN(gt
) = p
;
804 (void) strcpy(result
, CURRENT_DOMAIN(gt
));
806 } /* _textdomain_u */
809 * key_2_text() translates msd_id into target string.
812 key_2_text(Msg_s_node
*messages
, const char *key_string
)
816 unsigned char kc
= *(unsigned char *)key_string
;
817 struct msg_struct
*check_msg_list
;
820 gprintf(0, "*************** key_2_text(0x%p, \"%s\")\n",
821 (void *)messages
, key_string
? key_string
: "(null)");
822 printsunmsg(messages
, 1);
825 check_msg_list
= messages
->msg_list
+
826 messages
->msg_file_info
->msg_mid
;
828 msg_id_str
= messages
->msg_ids
+
829 check_msg_list
->msgid_offset
;
831 * To maintain the compatibility with Zeus mo file,
832 * msg_id's are stored in descending order.
833 * If the ascending order is desired, change "msgfmt.c"
834 * and switch msg_id_str and key_string in the following
835 * strcmp() statement.
837 val
= *(unsigned char *)msg_id_str
- kc
;
839 (val
= strcmp(msg_id_str
, key_string
)) == 0) {
840 return (messages
->msg_strs
841 + check_msg_list
->msgstr_offset
);
842 } else if (val
< 0) {
843 if (check_msg_list
->less
!= LEAFINDICATOR
) {
844 check_msg_list
= messages
->msg_list
+
845 check_msg_list
->less
;
848 return ((char *)key_string
);
851 if (check_msg_list
->more
!= LEAFINDICATOR
) {
852 check_msg_list
= messages
->msg_list
+
853 check_msg_list
->more
;
856 return ((char *)key_string
);
866 * addr - address to the mmapped file
867 * size - size of the file
870 * 0 - either T_SUN_MO or T_ILL_MO has been set
871 * 1 - not a valid sun mo file
875 sun_setmsg(Msg_node
*mnp
, char *addr
, size_t size
)
877 struct msg_info
*sun_header
;
879 uint32_t first_4bytes
;
881 int struct_size
, struct_size_old
;
884 if (size
< sizeof (struct msg_info
)) {
885 /* invalid mo file */
886 mnp
->type
= T_ILL_MO
;
888 gprintf(0, "********* exiting sun_setmsg\n");
894 first_4bytes
= *((uint32_t *)(uintptr_t)addr
);
895 if (first_4bytes
> INT_MAX
) {
897 * Not a valid sun mo file
902 /* candidate for sun mo */
904 sun_header
= (struct msg_info
*)(uintptr_t)addr
;
905 mid
= sun_header
->msg_mid
;
906 count
= sun_header
->msg_count
;
907 msg_struct_size
= sun_header
->msg_struct_size
;
908 struct_size_old
= (int)(OLD_MSG_STRUCT_SIZE
* count
);
909 struct_size
= (int)(MSG_STRUCT_SIZE
* count
);
911 if ((((count
- 1) / 2) != mid
) ||
912 ((msg_struct_size
!= struct_size_old
) &&
913 (msg_struct_size
!= struct_size
))) {
914 /* invalid mo file */
915 mnp
->type
= T_ILL_MO
;
917 gprintf(0, "********* exiting sun_setmsg\n");
922 /* valid sun mo file */
924 p
= malloc(sizeof (Msg_s_node
));
929 p
->msg_file_info
= sun_header
;
930 p
->msg_list
= (struct msg_struct
*)(uintptr_t)
931 (addr
+ sizeof (struct msg_info
));
932 p
->msg_ids
= (char *)(addr
+ sizeof (struct msg_info
) +
934 p
->msg_strs
= (char *)(addr
+ sizeof (struct msg_info
) +
935 struct_size
+ sun_header
->str_count_msgid
);
938 mnp
->type
= T_SUN_MO
;
940 gprintf(0, "******** exiting sun_setmsg\n");
951 * addr - address to the mmapped file
952 * size - size of the file
959 setmsg(Msg_node
*mnp
, char *addr
, size_t size
)
962 if ((ret
= sun_setmsg(mnp
, addr
, size
)) <= 0)
965 return (gnu_setmsg(mnp
, addr
, size
));
969 handle_type_mo(Msg_node
*mnp
, struct msg_pack
*mp
)
979 mp
->status
|= ST_SUN_MO_FOUND
;
983 * *ngettext is called against
986 int exp
= (mp
->n
== 1);
987 result
= (char *)mp
->msgid1
;
989 result
= (char *)mp
->msgid2
;
992 result
= key_2_text(mnp
->msg
.sunmsg
, mp
->msgid1
);
994 result
= check_format(mp
->msgid1
, result
, 0);
999 mp
->status
|= ST_GNU_MO_FOUND
;
1001 result
= gnu_key_2_text(mnp
->msg
.gnumsg
,
1002 get_codeset(mp
->domain
), mp
);
1004 if (result
== mp
->msgid1
|| result
== mp
->msgid2
) {
1005 /* no valid msg found */
1009 /* valid msg found */
1010 mp
->status
|= ST_GNU_MSG_FOUND
;
1012 if (!mnp
->trusted
) {
1013 result
= check_format(mp
->msgid1
, result
, 0);
1014 if (result
== mp
->msgid1
) {
1015 DFLTMSG(result
, mp
->msgid1
, mp
->msgid2
,
1021 /* this should never happen */
1022 DFLTMSG(result
, mp
->msgid1
, mp
->msgid2
, mp
->n
, mp
->plural
);
1029 * handle_mo() returns NULL if invalid MO found.
1032 handle_mo(struct msg_pack
*mp
)
1036 struct stat64 statbuf
;
1038 Gettext_t
*gt
= global_gt
;
1040 #define CONNECT_ENTRY \
1041 mnp->next = gt->m_node; \
1045 #ifdef GETTEXT_DEBUG
1046 gprintf(0, "*************** handle_mo(0x%p)\n", (void *)mp
);
1050 mnp
= check_cache(mp
);
1054 return (handle_type_mo(mnp
, mp
));
1058 * Valid entry not found in the cache
1060 mnp
= calloc(1, sizeof (Msg_node
));
1062 DFLTMSG(result
, mp
->msgid1
, mp
->msgid2
, mp
->n
, mp
->plural
);
1065 mnp
->hashid
= mp
->hash_domain
;
1066 mnp
->path
= strdup(mp
->msgfile
);
1067 if (mnp
->path
== NULL
) {
1069 DFLTMSG(result
, mp
->msgid1
, mp
->msgid2
, mp
->n
, mp
->plural
);
1073 fd
= nls_safe_open(mp
->msgfile
, &statbuf
, &mp
->trusted
, !mp
->nlsp
);
1074 if ((fd
== -1) || (statbuf
.st_size
> LONG_MAX
)) {
1077 mnp
->type
= T_ILL_MO
;
1081 mp
->fsz
= (size_t)statbuf
.st_size
;
1082 mp
->addr
= mmap(NULL
, mp
->fsz
, PROT_READ
, MAP_SHARED
, fd
, 0);
1085 if (mp
->addr
== MAP_FAILED
) {
1088 DFLTMSG(result
, mp
->msgid1
, mp
->msgid2
, mp
->n
, mp
->plural
);
1092 if (setmsg(mnp
, (char *)mp
->addr
, mp
->fsz
) == -1) {
1095 (void) munmap(mp
->addr
, mp
->fsz
);
1096 DFLTMSG(result
, mp
->msgid1
, mp
->msgid2
, mp
->n
, mp
->plural
);
1099 mnp
->trusted
= mp
->trusted
;
1102 return (handle_type_mo(mnp
, mp
));