4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
27 #pragma ident "%Z%%M% %I% %E% SMI"
29 #include "sun_msgfmt.h"
31 static void read_psffm(char *);
32 static void sortit(char *, char *);
33 static wchar_t *consume_whitespace(wchar_t *);
34 static char expand_meta(wchar_t **);
35 static struct domain_struct
*find_domain_node(char *);
36 static void insert_message(struct domain_struct
*, char *, char *);
37 static void output_all_mo_files(void);
38 static void output_one_mo_file(struct domain_struct
*);
39 static size_t _mbsntowcs(wchar_t **, char **, size_t *);
42 static void printlist(void);
45 static char gcurrent_domain
[TEXTDOMAINMAX
+1];
46 static char *gmsgid
; /* Stores msgid when read po file */
47 static char *gmsgstr
; /* Stores msgstr when read po file */
48 static int gmsgid_size
; /* The current size of msgid buffer */
49 static int gmsgstr_size
; /* The current size of msgstr buffer */
50 static char *outfile
= NULL
;
51 static int linenum
; /* The line number in the file */
52 static int msgid_linenum
; /* The last msgid token line number */
53 static int msgstr_linenum
; /* The last msgstr token line number */
59 static struct domain_struct
*first_domain
= NULL
;
60 static struct domain_struct
*last_used_domain
= NULL
;
65 static char *inputdir
;
67 extern void check_gnu(char *, size_t);
69 #define GNU_MSGFMT "/usr/lib/gmsgfmt"
71 invoke_gnu_msgfmt(void)
74 * Transferring to /usr/lib/gmsgfmt
78 gnu_msgfmt
= getenv("GNU_MSGFMT");
80 gnu_msgfmt
= GNU_MSGFMT
;
82 gnu_msgfmt
= GNU_MSGFMT
;
86 diag(gettext(DIAG_INVOKING_GNU
));
89 (void) execv(gnu_msgfmt
, oargv
);
91 error(gettext(ERR_EXEC_FAILED
), gnu_msgfmt
);
98 (void) fprintf(stderr
, gettext(ERR_USAGE
));
103 * msgfmt - Generate binary tree for runtime gettext() using psffm: "Portable
104 * Source File Format for Messages" file template. This file may have
105 * previously been generated by the xgettext filter for c source files.
109 main(int argc
, char **argv
)
112 static struct flags flag
;
114 (void) setlocale(LC_ALL
, "");
115 #if !defined(TEXT_DOMAIN)
116 #define TEXT_DOMAIN "SYS_TEST"
118 (void) textdomain(TEXT_DOMAIN
);
121 ret
= parse_option(&argc
, &argv
, &flag
);
128 /* never invoke gnu msgfmt */
130 error(gettext(ERR_GNU_ON_SUN
));
136 inputdir
= flag
.idir
;
140 outfile
= flag
.ofile
;
147 /* invoke /usr/lib/gmsgfmt */
153 * read all portable object files specified in command arguments.
154 * Allocate initial size for msgid and msgstr. If it needs more
155 * spaces, realloc later.
157 gmsgid
= (char *)Xmalloc(MAX_VALUE_LEN
);
158 gmsgstr
= (char *)Xmalloc(MAX_VALUE_LEN
);
160 gmsgid_size
= gmsgstr_size
= MAX_VALUE_LEN
;
161 (void) memset(gmsgid
, 0, gmsgid_size
);
162 (void) memset(gmsgstr
, 0, gmsgstr_size
);
164 mbcurmax
= MB_CUR_MAX
;
168 diag(gettext(DIAG_START_PROC
), *argv
);
173 output_all_mo_files();
186 * read_psffm - read in "psffm" format file, check syntax, printing error
187 * messages as needed, output binary tree to file <domain>
191 read_psffm(char *file
)
194 static char msgfile
[MAXPATHLEN
];
195 wchar_t *linebufptr
, *p
;
197 int quotefound
; /* double quote was seen */
198 int inmsgid
= 0; /* indicates "msgid" was seen */
199 int inmsgstr
= 0; /* indicates "msgstr" was seen */
200 int indomain
= 0; /* indicates "domain" was seen */
204 char token_found
; /* Boolean value */
205 unsigned int bufptr_index
= 0; /* current index of bufptr */
207 size_t fsize
, ln_size
, ll
;
208 wchar_t *linebufhead
= NULL
;
209 struct stat64 statbuf
;
213 * For each po file to be read,
214 * 1) set domain to default and
215 * 2) set linenumer to 0.
217 (void) strcpy(gcurrent_domain
, DEFAULT_DOMAIN
);
221 filename
= Xstrdup(file
);
223 size_t dirlen
, filelen
, len
;
225 dirlen
= strlen(inputdir
);
226 filelen
= strlen(file
);
227 len
= dirlen
+ 1 + filelen
+ 1;
228 filename
= (char *)Xmalloc(len
);
229 (void) memcpy(filename
, inputdir
, dirlen
);
230 *(filename
+ dirlen
) = '/';
231 (void) memcpy(filename
+ dirlen
+ 1, file
, filelen
);
232 *(filename
+ dirlen
+ 1 + filelen
) = '\0';
235 fd
= open(filename
, O_RDONLY
);
237 error(gettext(ERR_OPEN_FAILED
), filename
);
240 if (fstat64(fd
, &statbuf
) == -1) {
241 error(gettext(ERR_STAT_FAILED
), filename
);
244 fsize
= (size_t)statbuf
.st_size
;
247 * The size of the specified po file is 0.
248 * In Solaris 8 and earlier, msgfmt was silent
249 * for the null po file. So, just returns
250 * without generating an error message.
256 addr
= mmap(NULL
, fsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
257 if (addr
== MAP_FAILED
) {
258 error(gettext(ERR_MMAP_FAILED
), filename
);
264 check_gnu(addr
, fsize
);
272 ln_size
= _mbsntowcs(&linebufhead
, &mbuf
, &fsize
);
273 if (ln_size
== (size_t)-1) {
274 error(gettext(ERR_READ_FAILED
), filename
);
276 } else if (ln_size
== 0) {
277 break; /* End of File. */
281 linebufptr
= linebufhead
;
284 switch (*linebufptr
) {
285 case L
'#': /* comment */
286 case L
'\n': /* empty line */
288 case L
'\"': /* multiple lines of msgid and msgstr */
294 * Process MSGID Tokens.
296 token_found
= (wcsncmp(MSGID_TOKEN
, linebufptr
,
297 MSGID_LEN
) == 0) ? 1 : 0;
299 if (token_found
|| (quotefound
&& inmsgid
)) {
302 if (!CK_NXT_CH(linebufptr
, MSGID_LEN
+1)) {
303 diag(gettext(ERR_NOSPC
), linenum
);
304 error(gettext(ERR_EXITING
));
309 if (inmsgid
&& !quotefound
) {
310 warning(gettext(WARN_NO_MSGSTR
), msgid_linenum
);
314 sortit(gmsgid
, gmsgstr
);
315 (void) memset(gmsgid
, 0, gmsgid_size
);
316 (void) memset(gmsgstr
, 0, gmsgstr_size
);
320 /* multiple lines of msgid */
321 /* cancel the previous null termination */
325 * The first line of msgid.
326 * Save linenum of msgid to be used when
327 * printing warning or error message.
329 msgid_linenum
= linenum
;
331 linebufptr
= consume_whitespace(
332 linebufptr
+ MSGID_LEN
);
333 ln_size
-= linebufptr
- p
;
345 * Process MSGSTR Tokens.
347 token_found
= (wcsncmp(MSGSTR_TOKEN
, linebufptr
,
348 MSGSTR_LEN
) == 0) ? 1 : 0;
349 if (token_found
|| (quotefound
&& inmsgstr
)) {
352 if (!CK_NXT_CH(linebufptr
, MSGSTR_LEN
+1)) {
353 diag(gettext(ERR_NOSPC
), linenum
);
354 error(gettext(ERR_EXITING
));
360 if (inmsgstr
&& !quotefound
) {
361 warning(gettext(WARN_NO_MSGID
), msgstr_linenum
);
365 /* multiple lines of msgstr */
366 /* cancel the previous null termination */
370 * The first line of msgstr.
371 * Save linenum of msgid to be used when
372 * printing warning or error message.
374 msgstr_linenum
= linenum
;
376 linebufptr
= consume_whitespace(
377 linebufptr
+ MSGSTR_LEN
);
378 ln_size
-= linebufptr
- p
;
390 * Process DOMAIN Tokens.
391 * Add message id and message string to sorted list
392 * if msgstr was processed last time.
394 token_found
= (wcsncmp(DOMAIN_TOKEN
, linebufptr
,
395 DOMAIN_LEN
) == 0) ? 1 : 0;
396 if ((token_found
) || (quotefound
&& indomain
)) {
398 if (!CK_NXT_CH(linebufptr
, DOMAIN_LEN
+1)) {
399 diag(gettext(ERR_NOSPC
), linenum
);
400 error(gettext(ERR_EXITING
));
407 * process msgid and msgstr pair for previous domain
410 sortit(gmsgid
, gmsgstr
);
413 /* refresh msgid and msgstr buffer */
414 if (inmsgstr
|| inmsgid
) {
415 (void) memset(gmsgid
, 0, gmsgid_size
);
416 (void) memset(gmsgstr
, 0, gmsgstr_size
);
420 /* multiple lines of domain */
421 /* cancel the previous null termination */
425 linebufptr
= consume_whitespace(
426 linebufptr
+ DOMAIN_LEN
);
427 (void) memset(gcurrent_domain
, 0,
428 sizeof (gcurrent_domain
));
429 ln_size
-= linebufptr
- p
;
430 bufptr
= gcurrent_domain
;
441 * Now, fill up the buffer pointed by bufptr.
442 * At this point bufptr should point to one of
443 * msgid, msgptr, or current_domain.
444 * Otherwise, the entire line is ignored.
448 warning(gettext(WARN_SYNTAX_ERR
), linenum
);
452 if (*linebufptr
++ != L
'\"') {
453 warning(gettext(WARN_MISSING_QUOTE
), linenum
);
459 * If there is not enough space in the buffer,
460 * increase buffer by ln_size by realloc.
462 ll
= ln_size
* mbcurmax
;
463 if (bufptr
== gmsgid
) {
464 if (gmsgid_size
< (bufptr_index
+ ll
)) {
465 gmsgid
= (char *)Xrealloc(gmsgid
,
468 gmsgid_size
= bufptr_index
+ ll
;
470 } else if (bufptr
== gmsgstr
) {
471 if (gmsgstr_size
< (bufptr_index
+ ll
)) {
472 gmsgstr
= (char *)Xrealloc(gmsgstr
,
475 gmsgstr_size
= bufptr_index
+ ll
;
479 while (wc
= *linebufptr
++) {
483 warning(gettext(WARN_MISSING_QUOTE_AT_EOL
), linenum
);
492 if ((mb
= expand_meta(&linebufptr
)) != NULL
)
493 bufptr
[bufptr_index
++] = mb
;
497 if ((n
= wctomb(&bufptr
[bufptr_index
], wc
)) > 0)
502 * Check if any remaining characters
503 * after closing quote.
505 linebufptr
= consume_whitespace(linebufptr
);
506 if (*linebufptr
!= L
'\n') {
507 warning(gettext(WARN_INVALID_STRING
),
514 bufptr
[bufptr_index
++] = '\0';
516 (void) strcpy(msgfile
, gcurrent_domain
);
517 (void) strcat(msgfile
, ".mo");
521 sortit(gmsgid
, gmsgstr
);
526 if (munmap(addr
, statbuf
.st_size
) == -1) {
527 error(gettext(ERR_MUNMAP_FAILED
), filename
);
538 * Skip leading white spaces and tabs.
541 consume_whitespace(wchar_t *buf
)
543 wchar_t *bufptr
= buf
;
547 * Skip leading white spaces.
549 while ((c
= *bufptr
) != L
'\0') {
550 if (c
== L
' ' || c
== L
'\t') {
557 } /* consume_white_space */
561 * handle escape sequences.
564 expand_meta(wchar_t **buf
)
612 * This case handles \ddd where ddd is octal number.
613 * There could be one, two, or three octal numbers.
616 n
= (char)(wc
- L
'0');
618 if (wc
>= L
'0' && wc
<= L
'7') {
620 n
= 8*n
+ (char)(wc
- L
'0');
622 if (wc
>= L
'0' && wc
<= L
'7') {
624 n
= 8*n
+ (char)(wc
- L
'0');
634 * Finds the head of the current domain linked list and
635 * call insert_message() to insert msgid and msgstr pair
636 * to the linked list.
639 sortit(char *msgid
, char *msgstr
)
641 struct domain_struct
*dom
;
644 (void) fprintf(stderr
,
645 "==> sortit(), domain=<%s> msgid=<%s> msgstr=<%s>\n",
646 gcurrent_domain
, msgid
, msgstr
);
650 * If "-o filename" is specified, then all "domain" directive
651 * are ignored and, all messages will be stored in domain
652 * whose name is filename.
655 dom
= find_domain_node(outfile
);
657 dom
= find_domain_node(gcurrent_domain
);
660 insert_message(dom
, msgid
, msgstr
);
664 * This routine inserts message in the current domain message list.
665 * It is inserted in ascending order.
668 insert_message(struct domain_struct
*dom
,
669 char *msgid
, char *msgstr
)
671 struct msg_chain
*p1
;
672 struct msg_chain
*node
, *prev_node
;
676 * Find the optimal starting search position.
677 * The starting search position is either the first node
678 * or the current_elem of domain.
679 * The current_elem is the pointer to the node which
680 * is most recently accessed in domain.
682 if (dom
->current_elem
!= NULL
) {
683 b
= strcmp(msgid
, dom
->current_elem
->msgid
);
686 warning(gettext(WARN_DUP_MSG
),
687 msgid
, msgid_linenum
);
689 } else if (b
> 0) { /* to implement descending order */
690 p1
= dom
->first_elem
;
692 p1
= dom
->current_elem
;
695 p1
= dom
->first_elem
;
699 * search msgid insert position in the list
700 * Search starts from the node pointed by p1.
704 b
= strcmp(msgid
, p1
->msgid
);
707 warning(gettext(WARN_DUP_MSG
),
708 msgid
, msgid_linenum
);
710 } else if (b
< 0) { /* to implement descending order */
711 /* move to the next node */
715 /* insert a new msg node */
716 node
= (struct msg_chain
*)
717 Xmalloc(sizeof (struct msg_chain
));
719 node
->msgid
= Xstrdup(msgid
);
720 node
->msgstr
= Xstrdup(msgstr
);
723 prev_node
->next
= node
;
725 dom
->first_elem
= node
;
727 dom
->current_elem
= node
;
733 * msgid is smaller than any of msgid in the list or
735 * Therefore, append it.
737 node
= (struct msg_chain
*)
738 Xmalloc(sizeof (struct msg_chain
));
740 node
->msgid
= Xstrdup(msgid
);
741 node
->msgstr
= Xstrdup(msgstr
);
744 prev_node
->next
= node
;
746 dom
->first_elem
= node
;
748 dom
->current_elem
= node
;
752 } /* insert_message */
756 * This routine will find head of the linked list for the given
757 * domain_name. This looks up cache entry first and if cache misses,
759 * If not found, then create a new node.
761 static struct domain_struct
*
762 find_domain_node(char *domain_name
)
764 struct domain_struct
*p1
;
765 struct domain_struct
*node
;
766 struct domain_struct
*prev_node
;
770 /* for perfomance, check cache 'last_used_domain' */
771 if (last_used_domain
) {
772 b
= strcmp(domain_name
, last_used_domain
->domain
);
774 return (last_used_domain
);
778 p1
= last_used_domain
;
786 b
= strcmp(domain_name
, p1
->domain
);
789 last_used_domain
= p1
;
792 /* move to the next node */
796 /* insert a new domain node */
797 node
= (struct domain_struct
*)
798 Xmalloc(sizeof (struct domain_struct
));
800 node
->domain
= Xstrdup(domain_name
);
801 node
->first_elem
= NULL
;
802 node
->current_elem
= NULL
;
804 /* insert the node in the middle */
805 prev_node
->next
= node
;
807 /* node inserted is the smallest */
810 last_used_domain
= node
;
816 * domain_name is larger than any of domain name in the list or
819 node
= (struct domain_struct
*)
820 Xmalloc(sizeof (struct domain_struct
));
822 node
->domain
= Xstrdup(domain_name
);
823 node
->first_elem
= NULL
;
824 node
->current_elem
= NULL
;
826 /* domain list is not empty */
827 prev_node
->next
= node
;
829 /* domain list is empty */
832 last_used_domain
= node
;
836 } /* find_domain_node */
840 * binary_compute() is used for pre-computing a binary search.
843 binary_compute(int i
, int j
, int *more
, int *less
)
848 return (LEAFINDICATOR
);
852 less
[k
] = binary_compute(i
, k
- 1, more
, less
);
853 more
[k
] = binary_compute(k
+ 1, j
, more
, less
);
857 } /* binary_compute */
861 * Write all domain data to file.
862 * Each domain will create one file.
865 output_all_mo_files(void)
867 struct domain_struct
*p
;
872 * generate message object file only if there is
873 * at least one element.
876 output_one_mo_file(p
);
882 } /* output_all_mo_files */
886 * Write one domain data list to file.
889 output_one_mo_file(struct domain_struct
*dom
)
894 int string_count_msgid
;
895 int string_count_msg
;
897 int msgstr_index
= 0;
900 char fname
[TEXTDOMAINMAX
+1];
902 if (!dom
|| !dom
->first_elem
)
906 * If -o flag is specified, then file name is used as domain name.
907 * If not, ".mo" is appended to the domain name.
909 (void) strcpy(fname
, dom
->domain
);
911 (void) strcat(fname
, ".mo");
913 fp
= fopen(fname
, "w");
915 error(gettext(ERR_OPEN_FAILED
), fname
);
919 /* compute offsets and counts */
923 p
->msgid_offset
= msgid_index
;
924 p
->msgstr_offset
= msgstr_index
;
925 msgid_index
+= strlen(p
->msgid
) + 1;
926 msgstr_index
+= strlen(p
->msgstr
) + 1;
932 * Fill up less and more entries to be used for binary search.
934 string_count_msgid
= msgid_index
;
935 string_count_msg
= msgstr_index
;
936 less
= (int *)Xcalloc(message_count
, sizeof (int));
937 more
= (int *)Xcalloc(message_count
, sizeof (int));
939 (void) binary_compute(0, message_count
- 1, more
, less
);
944 for (i
= 0; i
< message_count
; i
++) {
945 (void) fprintf(stderr
,
946 " less[%2d]=%2d, more[%2d]=%2d\n",
947 i
, less
[i
], i
, more
[i
]);
953 * write out the message object file.
954 * The middle one is the first message to check by gettext().
956 i
= (message_count
- 1) / 2;
957 (void) fwrite(&i
, sizeof (int), 1, fp
);
958 (void) fwrite(&message_count
, sizeof (int), 1, fp
);
959 (void) fwrite(&string_count_msgid
, sizeof (int), 1, fp
);
960 (void) fwrite(&string_count_msg
, sizeof (int), 1, fp
);
961 i
= MSG_STRUCT_SIZE
* message_count
;
962 (void) fwrite(&i
, sizeof (int), 1, fp
);
964 /* march through linked list and write out all nodes. */
967 while (p
) { /* put out message struct */
968 (void) fwrite(&less
[i
], sizeof (int), 1, fp
);
969 (void) fwrite(&more
[i
], sizeof (int), 1, fp
);
970 (void) fwrite(&p
->msgid_offset
, sizeof (int), 1, fp
);
971 (void) fwrite(&p
->msgstr_offset
, sizeof (int), 1, fp
);
976 /* put out message id strings */
979 (void) fwrite(p
->msgid
, strlen(p
->msgid
)+1, 1, fp
);
983 /* put out message strings */
986 (void) fwrite(p
->msgstr
, strlen(p
->msgstr
)+1, 1, fp
);
996 } /* output_one_mo_file */
1000 * read one line from *mbuf,
1001 * skip preceding whitespaces,
1002 * convert the line to wide characters,
1003 * place the wide characters into *bufhead, and
1004 * return the number of wide characters placed.
1007 * **bufhead - address of a variable that is the pointer
1009 * The variable should been initialized to NULL.
1010 * **mbuf - address of a variable that is the pointer
1012 * The pointer should point to the memory mmapped to
1013 * the file to input.
1014 * **fsize - address of a size_t variable that contains
1015 * the size of unread bytes in the file to input.
1017 * return - the number of wide characters placed.
1018 * **bufhead - _mbsntowcs allocates the buffer to store
1019 * one line in wchar_t from *mbuf and sets the address
1021 * **mbuf - _mbsntowcs reads one line from *mbuf and sets *mbuf
1022 * to the beginning of the next line.
1023 * **fsize - *fsize will be set to the size of the unread
1024 * bytes in the file.
1027 _mbsntowcs(wchar_t **bufhead
, char **mbuf
, size_t *fsize
)
1031 size_t tbufsize
= LINE_SIZE
;
1032 size_t ttbufsize
, nc
;
1041 th
= (wchar_t *)Xmalloc(sizeof (wchar_t) * tbufsize
);
1044 /* skip preceding whitespaces */
1045 while ((*pc
!= '\0')) {
1046 if ((*pc
== ' ') || (*pc
== '\t')) {
1055 while (*fsize
> 0) {
1056 nb
= mbtowc(&wc
, pc
, mbcurmax
);
1058 return ((size_t)-1);
1066 * at least 2 more bytes are required for
1069 ttbufsize
= tbufsize
+ 2;
1070 th
= (wchar_t *)Xrealloc(th
,
1071 sizeof (wchar_t) * ttbufsize
);
1072 tp
= th
+ tbufsize
- nc
;
1073 tbufsize
= ttbufsize
;
1081 return ((size_t)(tp
- th
));
1084 ttbufsize
= tbufsize
+ LINE_SIZE
;
1085 th
= (wchar_t *)Xrealloc(th
,
1086 sizeof (wchar_t) * ttbufsize
);
1089 tbufsize
= ttbufsize
;
1098 * At this point, the input file has been consumed,
1099 * but there is no ending '\n'; we add it to
1105 * at least 2 more bytes are required for
1108 ttbufsize
= tbufsize
+ 2;
1109 th
= (wchar_t *)Xrealloc(th
,
1110 sizeof (wchar_t) * ttbufsize
);
1111 tp
= th
+ tbufsize
- nc
;
1112 tbufsize
= ttbufsize
;
1118 return ((size_t)(tp
- th
));
1123 * This is debug function. Not compiled in the final executable.
1129 struct domain_struct
*p
;
1130 struct msg_chain
*m
;
1132 (void) fprintf(stderr
, "\n=== Printing contents of all domains ===\n");
1135 (void) fprintf(stderr
, "domain name = <%s>\n", p
->domain
);
1138 (void) fprintf(stderr
, " msgid=<%s>, msgstr=<%s>\n",
1139 m
->msgid
, m
->msgstr
);