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 #include "sun_msgfmt.h"
29 static void read_psffm(char *);
30 static void sortit(char *, char *);
31 static wchar_t *consume_whitespace(wchar_t *);
32 static char expand_meta(wchar_t **);
33 static struct domain_struct
*find_domain_node(char *);
34 static void insert_message(struct domain_struct
*, char *, char *);
35 static void output_all_mo_files(void);
36 static void output_one_mo_file(struct domain_struct
*);
37 static size_t _mbsntowcs(wchar_t **, char **, size_t *);
40 static void printlist(void);
43 static char gcurrent_domain
[TEXTDOMAINMAX
+1];
44 static char *gmsgid
; /* Stores msgid when read po file */
45 static char *gmsgstr
; /* Stores msgstr when read po file */
46 static int gmsgid_size
; /* The current size of msgid buffer */
47 static int gmsgstr_size
; /* The current size of msgstr buffer */
48 static char *outfile
= NULL
;
49 static int linenum
; /* The line number in the file */
50 static int msgid_linenum
; /* The last msgid token line number */
51 static int msgstr_linenum
; /* The last msgstr token line number */
57 static struct domain_struct
*first_domain
= NULL
;
58 static struct domain_struct
*last_used_domain
= NULL
;
63 static char *inputdir
;
65 extern void check_gnu(char *, size_t);
67 #define GNU_MSGFMT "/usr/lib/gmsgfmt"
69 invoke_gnu_msgfmt(void)
72 * Transferring to /usr/lib/gmsgfmt
76 gnu_msgfmt
= getenv("GNU_MSGFMT");
78 gnu_msgfmt
= GNU_MSGFMT
;
80 gnu_msgfmt
= GNU_MSGFMT
;
84 diag(gettext(DIAG_INVOKING_GNU
));
87 (void) execv(gnu_msgfmt
, oargv
);
89 error(gettext(ERR_EXEC_FAILED
), gnu_msgfmt
);
96 (void) fprintf(stderr
, gettext(ERR_USAGE
));
101 * msgfmt - Generate binary tree for runtime gettext() using psffm: "Portable
102 * Source File Format for Messages" file template. This file may have
103 * previously been generated by the xgettext filter for c source files.
107 main(int argc
, char **argv
)
110 static struct flags flag
;
112 (void) setlocale(LC_ALL
, "");
113 #if !defined(TEXT_DOMAIN)
114 #define TEXT_DOMAIN "SYS_TEST"
116 (void) textdomain(TEXT_DOMAIN
);
119 ret
= parse_option(&argc
, &argv
, &flag
);
126 /* never invoke gnu msgfmt */
128 error(gettext(ERR_GNU_ON_SUN
));
134 inputdir
= flag
.idir
;
138 outfile
= flag
.ofile
;
145 /* invoke /usr/lib/gmsgfmt */
151 * read all portable object files specified in command arguments.
152 * Allocate initial size for msgid and msgstr. If it needs more
153 * spaces, realloc later.
155 gmsgid
= (char *)Xmalloc(MAX_VALUE_LEN
);
156 gmsgstr
= (char *)Xmalloc(MAX_VALUE_LEN
);
158 gmsgid_size
= gmsgstr_size
= MAX_VALUE_LEN
;
159 (void) memset(gmsgid
, 0, gmsgid_size
);
160 (void) memset(gmsgstr
, 0, gmsgstr_size
);
162 mbcurmax
= MB_CUR_MAX
;
166 diag(gettext(DIAG_START_PROC
), *argv
);
171 output_all_mo_files();
184 * read_psffm - read in "psffm" format file, check syntax, printing error
185 * messages as needed, output binary tree to file <domain>
189 read_psffm(char *file
)
192 static char msgfile
[MAXPATHLEN
];
193 wchar_t *linebufptr
, *p
;
195 int quotefound
; /* double quote was seen */
196 int inmsgid
= 0; /* indicates "msgid" was seen */
197 int inmsgstr
= 0; /* indicates "msgstr" was seen */
198 int indomain
= 0; /* indicates "domain" was seen */
202 char token_found
; /* Boolean value */
203 unsigned int bufptr_index
= 0; /* current index of bufptr */
205 size_t fsize
, ln_size
, ll
;
206 wchar_t *linebufhead
= NULL
;
207 struct stat64 statbuf
;
211 * For each po file to be read,
212 * 1) set domain to default and
213 * 2) set linenumer to 0.
215 (void) strcpy(gcurrent_domain
, DEFAULT_DOMAIN
);
219 filename
= Xstrdup(file
);
221 size_t dirlen
, filelen
, len
;
223 dirlen
= strlen(inputdir
);
224 filelen
= strlen(file
);
225 len
= dirlen
+ 1 + filelen
+ 1;
226 filename
= (char *)Xmalloc(len
);
227 (void) memcpy(filename
, inputdir
, dirlen
);
228 *(filename
+ dirlen
) = '/';
229 (void) memcpy(filename
+ dirlen
+ 1, file
, filelen
);
230 *(filename
+ dirlen
+ 1 + filelen
) = '\0';
233 fd
= open(filename
, O_RDONLY
);
235 error(gettext(ERR_OPEN_FAILED
), filename
);
238 if (fstat64(fd
, &statbuf
) == -1) {
239 error(gettext(ERR_STAT_FAILED
), filename
);
242 fsize
= (size_t)statbuf
.st_size
;
245 * The size of the specified po file is 0.
246 * In Solaris 8 and earlier, msgfmt was silent
247 * for the null po file. So, just returns
248 * without generating an error message.
254 addr
= mmap(NULL
, fsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
255 if (addr
== MAP_FAILED
) {
256 error(gettext(ERR_MMAP_FAILED
), filename
);
262 check_gnu(addr
, fsize
);
270 ln_size
= _mbsntowcs(&linebufhead
, &mbuf
, &fsize
);
271 if (ln_size
== (size_t)-1) {
272 error(gettext(ERR_READ_FAILED
), filename
);
274 } else if (ln_size
== 0) {
275 break; /* End of File. */
279 linebufptr
= linebufhead
;
282 switch (*linebufptr
) {
283 case L
'#': /* comment */
284 case L
'\n': /* empty line */
286 case L
'\"': /* multiple lines of msgid and msgstr */
292 * Process MSGID Tokens.
294 token_found
= (wcsncmp(MSGID_TOKEN
, linebufptr
,
295 MSGID_LEN
) == 0) ? 1 : 0;
297 if (token_found
|| (quotefound
&& inmsgid
)) {
300 if (!CK_NXT_CH(linebufptr
, MSGID_LEN
+1)) {
301 diag(gettext(ERR_NOSPC
), linenum
);
302 error(gettext(ERR_EXITING
));
307 if (inmsgid
&& !quotefound
) {
308 warning(gettext(WARN_NO_MSGSTR
), msgid_linenum
);
312 sortit(gmsgid
, gmsgstr
);
313 (void) memset(gmsgid
, 0, gmsgid_size
);
314 (void) memset(gmsgstr
, 0, gmsgstr_size
);
318 /* multiple lines of msgid */
319 /* cancel the previous null termination */
323 * The first line of msgid.
324 * Save linenum of msgid to be used when
325 * printing warning or error message.
327 msgid_linenum
= linenum
;
329 linebufptr
= consume_whitespace(
330 linebufptr
+ MSGID_LEN
);
331 ln_size
-= linebufptr
- p
;
343 * Process MSGSTR Tokens.
345 token_found
= (wcsncmp(MSGSTR_TOKEN
, linebufptr
,
346 MSGSTR_LEN
) == 0) ? 1 : 0;
347 if (token_found
|| (quotefound
&& inmsgstr
)) {
350 if (!CK_NXT_CH(linebufptr
, MSGSTR_LEN
+1)) {
351 diag(gettext(ERR_NOSPC
), linenum
);
352 error(gettext(ERR_EXITING
));
358 if (inmsgstr
&& !quotefound
) {
359 warning(gettext(WARN_NO_MSGID
), msgstr_linenum
);
363 /* multiple lines of msgstr */
364 /* cancel the previous null termination */
368 * The first line of msgstr.
369 * Save linenum of msgid to be used when
370 * printing warning or error message.
372 msgstr_linenum
= linenum
;
374 linebufptr
= consume_whitespace(
375 linebufptr
+ MSGSTR_LEN
);
376 ln_size
-= linebufptr
- p
;
388 * Process DOMAIN Tokens.
389 * Add message id and message string to sorted list
390 * if msgstr was processed last time.
392 token_found
= (wcsncmp(DOMAIN_TOKEN
, linebufptr
,
393 DOMAIN_LEN
) == 0) ? 1 : 0;
394 if ((token_found
) || (quotefound
&& indomain
)) {
396 if (!CK_NXT_CH(linebufptr
, DOMAIN_LEN
+1)) {
397 diag(gettext(ERR_NOSPC
), linenum
);
398 error(gettext(ERR_EXITING
));
405 * process msgid and msgstr pair for previous domain
408 sortit(gmsgid
, gmsgstr
);
411 /* refresh msgid and msgstr buffer */
412 if (inmsgstr
|| inmsgid
) {
413 (void) memset(gmsgid
, 0, gmsgid_size
);
414 (void) memset(gmsgstr
, 0, gmsgstr_size
);
418 /* multiple lines of domain */
419 /* cancel the previous null termination */
423 linebufptr
= consume_whitespace(
424 linebufptr
+ DOMAIN_LEN
);
425 (void) memset(gcurrent_domain
, 0,
426 sizeof (gcurrent_domain
));
427 ln_size
-= linebufptr
- p
;
428 bufptr
= gcurrent_domain
;
439 * Now, fill up the buffer pointed by bufptr.
440 * At this point bufptr should point to one of
441 * msgid, msgptr, or current_domain.
442 * Otherwise, the entire line is ignored.
446 warning(gettext(WARN_SYNTAX_ERR
), linenum
);
450 if (*linebufptr
++ != L
'\"') {
451 warning(gettext(WARN_MISSING_QUOTE
), linenum
);
457 * If there is not enough space in the buffer,
458 * increase buffer by ln_size by realloc.
460 ll
= ln_size
* mbcurmax
;
461 if (bufptr
== gmsgid
) {
462 if (gmsgid_size
< (bufptr_index
+ ll
)) {
463 gmsgid
= (char *)Xrealloc(gmsgid
,
466 gmsgid_size
= bufptr_index
+ ll
;
468 } else if (bufptr
== gmsgstr
) {
469 if (gmsgstr_size
< (bufptr_index
+ ll
)) {
470 gmsgstr
= (char *)Xrealloc(gmsgstr
,
473 gmsgstr_size
= bufptr_index
+ ll
;
477 while (wc
= *linebufptr
++) {
481 warning(gettext(WARN_MISSING_QUOTE_AT_EOL
), linenum
);
490 if ((mb
= expand_meta(&linebufptr
)) != '\0')
491 bufptr
[bufptr_index
++] = mb
;
495 if ((n
= wctomb(&bufptr
[bufptr_index
], wc
)) > 0)
500 * Check if any remaining characters
501 * after closing quote.
503 linebufptr
= consume_whitespace(linebufptr
);
504 if (*linebufptr
!= L
'\n') {
505 warning(gettext(WARN_INVALID_STRING
),
512 bufptr
[bufptr_index
++] = '\0';
514 (void) strcpy(msgfile
, gcurrent_domain
);
515 (void) strcat(msgfile
, ".mo");
519 sortit(gmsgid
, gmsgstr
);
523 if (munmap(addr
, statbuf
.st_size
) == -1) {
524 error(gettext(ERR_MUNMAP_FAILED
), filename
);
535 * Skip leading white spaces and tabs.
538 consume_whitespace(wchar_t *buf
)
540 wchar_t *bufptr
= buf
;
544 * Skip leading white spaces.
546 while ((c
= *bufptr
) != L
'\0') {
547 if (c
== L
' ' || c
== L
'\t') {
554 } /* consume_white_space */
558 * handle escape sequences.
561 expand_meta(wchar_t **buf
)
609 * This case handles \ddd where ddd is octal number.
610 * There could be one, two, or three octal numbers.
613 n
= (char)(wc
- L
'0');
615 if (wc
>= L
'0' && wc
<= L
'7') {
617 n
= 8*n
+ (char)(wc
- L
'0');
619 if (wc
>= L
'0' && wc
<= L
'7') {
621 n
= 8*n
+ (char)(wc
- L
'0');
631 * Finds the head of the current domain linked list and
632 * call insert_message() to insert msgid and msgstr pair
633 * to the linked list.
636 sortit(char *msgid
, char *msgstr
)
638 struct domain_struct
*dom
;
641 (void) fprintf(stderr
,
642 "==> sortit(), domain=<%s> msgid=<%s> msgstr=<%s>\n",
643 gcurrent_domain
, msgid
, msgstr
);
647 * If "-o filename" is specified, then all "domain" directive
648 * are ignored and, all messages will be stored in domain
649 * whose name is filename.
652 dom
= find_domain_node(outfile
);
654 dom
= find_domain_node(gcurrent_domain
);
657 insert_message(dom
, msgid
, msgstr
);
661 * This routine inserts message in the current domain message list.
662 * It is inserted in ascending order.
665 insert_message(struct domain_struct
*dom
,
666 char *msgid
, char *msgstr
)
668 struct msg_chain
*p1
;
669 struct msg_chain
*node
, *prev_node
;
673 * Find the optimal starting search position.
674 * The starting search position is either the first node
675 * or the current_elem of domain.
676 * The current_elem is the pointer to the node which
677 * is most recently accessed in domain.
679 if (dom
->current_elem
!= NULL
) {
680 b
= strcmp(msgid
, dom
->current_elem
->msgid
);
683 warning(gettext(WARN_DUP_MSG
),
684 msgid
, msgid_linenum
);
686 } else if (b
> 0) { /* to implement descending order */
687 p1
= dom
->first_elem
;
689 p1
= dom
->current_elem
;
692 p1
= dom
->first_elem
;
696 * search msgid insert position in the list
697 * Search starts from the node pointed by p1.
701 b
= strcmp(msgid
, p1
->msgid
);
704 warning(gettext(WARN_DUP_MSG
),
705 msgid
, msgid_linenum
);
707 } else if (b
< 0) { /* to implement descending order */
708 /* move to the next node */
712 /* insert a new msg node */
713 node
= (struct msg_chain
*)
714 Xmalloc(sizeof (struct msg_chain
));
716 node
->msgid
= Xstrdup(msgid
);
717 node
->msgstr
= Xstrdup(msgstr
);
720 prev_node
->next
= node
;
722 dom
->first_elem
= node
;
724 dom
->current_elem
= node
;
730 * msgid is smaller than any of msgid in the list or
732 * Therefore, append it.
734 node
= (struct msg_chain
*)
735 Xmalloc(sizeof (struct msg_chain
));
737 node
->msgid
= Xstrdup(msgid
);
738 node
->msgstr
= Xstrdup(msgstr
);
741 prev_node
->next
= node
;
743 dom
->first_elem
= node
;
745 dom
->current_elem
= node
;
749 } /* insert_message */
753 * This routine will find head of the linked list for the given
754 * domain_name. This looks up cache entry first and if cache misses,
756 * If not found, then create a new node.
758 static struct domain_struct
*
759 find_domain_node(char *domain_name
)
761 struct domain_struct
*p1
;
762 struct domain_struct
*node
;
763 struct domain_struct
*prev_node
;
767 /* for perfomance, check cache 'last_used_domain' */
768 if (last_used_domain
) {
769 b
= strcmp(domain_name
, last_used_domain
->domain
);
771 return (last_used_domain
);
775 p1
= last_used_domain
;
783 b
= strcmp(domain_name
, p1
->domain
);
786 last_used_domain
= p1
;
789 /* move to the next node */
793 /* insert a new domain node */
794 node
= (struct domain_struct
*)
795 Xmalloc(sizeof (struct domain_struct
));
797 node
->domain
= Xstrdup(domain_name
);
798 node
->first_elem
= NULL
;
799 node
->current_elem
= NULL
;
801 /* insert the node in the middle */
802 prev_node
->next
= node
;
804 /* node inserted is the smallest */
807 last_used_domain
= node
;
813 * domain_name is larger than any of domain name in the list or
816 node
= (struct domain_struct
*)
817 Xmalloc(sizeof (struct domain_struct
));
819 node
->domain
= Xstrdup(domain_name
);
820 node
->first_elem
= NULL
;
821 node
->current_elem
= NULL
;
823 /* domain list is not empty */
824 prev_node
->next
= node
;
826 /* domain list is empty */
829 last_used_domain
= node
;
833 } /* find_domain_node */
837 * binary_compute() is used for pre-computing a binary search.
840 binary_compute(int i
, int j
, int *more
, int *less
)
845 return (LEAFINDICATOR
);
849 less
[k
] = binary_compute(i
, k
- 1, more
, less
);
850 more
[k
] = binary_compute(k
+ 1, j
, more
, less
);
854 } /* binary_compute */
858 * Write all domain data to file.
859 * Each domain will create one file.
862 output_all_mo_files(void)
864 struct domain_struct
*p
;
869 * generate message object file only if there is
870 * at least one element.
873 output_one_mo_file(p
);
879 } /* output_all_mo_files */
883 * Write one domain data list to file.
886 output_one_mo_file(struct domain_struct
*dom
)
891 int string_count_msgid
;
892 int string_count_msg
;
894 int msgstr_index
= 0;
897 char fname
[TEXTDOMAINMAX
+1];
899 if (!dom
|| !dom
->first_elem
)
903 * If -o flag is specified, then file name is used as domain name.
904 * If not, ".mo" is appended to the domain name.
906 (void) strcpy(fname
, dom
->domain
);
908 (void) strcat(fname
, ".mo");
910 fp
= fopen(fname
, "w");
912 error(gettext(ERR_OPEN_FAILED
), fname
);
916 /* compute offsets and counts */
920 p
->msgid_offset
= msgid_index
;
921 p
->msgstr_offset
= msgstr_index
;
922 msgid_index
+= strlen(p
->msgid
) + 1;
923 msgstr_index
+= strlen(p
->msgstr
) + 1;
929 * Fill up less and more entries to be used for binary search.
931 string_count_msgid
= msgid_index
;
932 string_count_msg
= msgstr_index
;
933 less
= (int *)Xcalloc(message_count
, sizeof (int));
934 more
= (int *)Xcalloc(message_count
, sizeof (int));
936 (void) binary_compute(0, message_count
- 1, more
, less
);
941 for (i
= 0; i
< message_count
; i
++) {
942 (void) fprintf(stderr
,
943 " less[%2d]=%2d, more[%2d]=%2d\n",
944 i
, less
[i
], i
, more
[i
]);
950 * write out the message object file.
951 * The middle one is the first message to check by gettext().
953 i
= (message_count
- 1) / 2;
954 (void) fwrite(&i
, sizeof (int), 1, fp
);
955 (void) fwrite(&message_count
, sizeof (int), 1, fp
);
956 (void) fwrite(&string_count_msgid
, sizeof (int), 1, fp
);
957 (void) fwrite(&string_count_msg
, sizeof (int), 1, fp
);
958 i
= MSG_STRUCT_SIZE
* message_count
;
959 (void) fwrite(&i
, sizeof (int), 1, fp
);
961 /* march through linked list and write out all nodes. */
964 while (p
) { /* put out message struct */
965 (void) fwrite(&less
[i
], sizeof (int), 1, fp
);
966 (void) fwrite(&more
[i
], sizeof (int), 1, fp
);
967 (void) fwrite(&p
->msgid_offset
, sizeof (int), 1, fp
);
968 (void) fwrite(&p
->msgstr_offset
, sizeof (int), 1, fp
);
973 /* put out message id strings */
976 (void) fwrite(p
->msgid
, strlen(p
->msgid
)+1, 1, fp
);
980 /* put out message strings */
983 (void) fwrite(p
->msgstr
, strlen(p
->msgstr
)+1, 1, fp
);
993 } /* output_one_mo_file */
997 * read one line from *mbuf,
998 * skip preceding whitespaces,
999 * convert the line to wide characters,
1000 * place the wide characters into *bufhead, and
1001 * return the number of wide characters placed.
1004 * **bufhead - address of a variable that is the pointer
1006 * The variable should been initialized to NULL.
1007 * **mbuf - address of a variable that is the pointer
1009 * The pointer should point to the memory mmapped to
1010 * the file to input.
1011 * **fsize - address of a size_t variable that contains
1012 * the size of unread bytes in the file to input.
1014 * return - the number of wide characters placed.
1015 * **bufhead - _mbsntowcs allocates the buffer to store
1016 * one line in wchar_t from *mbuf and sets the address
1018 * **mbuf - _mbsntowcs reads one line from *mbuf and sets *mbuf
1019 * to the beginning of the next line.
1020 * **fsize - *fsize will be set to the size of the unread
1021 * bytes in the file.
1024 _mbsntowcs(wchar_t **bufhead
, char **mbuf
, size_t *fsize
)
1028 size_t tbufsize
= LINE_SIZE
;
1029 size_t ttbufsize
, nc
;
1038 th
= (wchar_t *)Xmalloc(sizeof (wchar_t) * tbufsize
);
1041 /* skip preceding whitespaces */
1042 while ((*pc
!= '\0')) {
1043 if ((*pc
== ' ') || (*pc
== '\t')) {
1052 while (*fsize
> 0) {
1053 nb
= mbtowc(&wc
, pc
, mbcurmax
);
1055 return ((size_t)-1);
1063 * at least 2 more bytes are required for
1066 ttbufsize
= tbufsize
+ 2;
1067 th
= (wchar_t *)Xrealloc(th
,
1068 sizeof (wchar_t) * ttbufsize
);
1069 tp
= th
+ tbufsize
- nc
;
1070 tbufsize
= ttbufsize
;
1078 return ((size_t)(tp
- th
));
1081 ttbufsize
= tbufsize
+ LINE_SIZE
;
1082 th
= (wchar_t *)Xrealloc(th
,
1083 sizeof (wchar_t) * ttbufsize
);
1086 tbufsize
= ttbufsize
;
1095 * At this point, the input file has been consumed,
1096 * but there is no ending '\n'; we add it to
1102 * at least 2 more bytes are required for
1105 ttbufsize
= tbufsize
+ 2;
1106 th
= (wchar_t *)Xrealloc(th
,
1107 sizeof (wchar_t) * ttbufsize
);
1108 tp
= th
+ tbufsize
- nc
;
1109 tbufsize
= ttbufsize
;
1115 return ((size_t)(tp
- th
));
1120 * This is debug function. Not compiled in the final executable.
1126 struct domain_struct
*p
;
1127 struct msg_chain
*m
;
1129 (void) fprintf(stderr
, "\n=== Printing contents of all domains ===\n");
1132 (void) fprintf(stderr
, "domain name = <%s>\n", p
->domain
);
1135 (void) fprintf(stderr
, " msgid=<%s>, msgstr=<%s>\n",
1136 m
->msgid
, m
->msgstr
);