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  * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
  23  *
  24  * sgsmsg generates several message files from an input template file.  Messages
  25  * are constructed for use with gettext(3i) - the default - or catgets(3c).  The
  26  * files generate are:
  27  *
  28  * msg.h        a header file containing definitions for each message.  The -h
  29  *              option triggers the creation of these definitions and specifies
  30  *              the name to use.
  31  *
  32  * msg.c        a data array of message strings.  The msg.h definitions are
  33  *              offsets into this array.  The -d option triggers the creation of
  34  *              these definitions and specifies the name to use.
  35  *
  36  * messages     a message file suitable for catgets(3c) or gettext(3i) use.  The
  37  *              -m option triggers this output and specifies the filename to be
  38  *              used.
  39  *
  40  * The template file is processed based on the first character of each line:
  41  *
  42  * # or $       entries are copied (as is) to the message file (messages).
  43  *
  44  * @ token(s)   entries are translated.  Two translations are possible dependent
  45  *              on whether one or more tokens are supplied:
  46  *
  47  *              A single token is interpreted as one of two reserved message
  48  *              output indicators, or a message identifier.  The reserved output
  49  *              indicator _START_ enables output to the message file - Note that
  50  *              the occurance of any other @ token will also enable message
  51  *              output.  The reserved output indicator _END_ disables output to
  52  *              the message file.  The use of these two indicators provides for
  53  *              only those message strings that require translation to be output
  54  *              to the message file.
  55  *
  56  *              Besides the reserved output indicators, a single token is taken
  57  *              to be a message identifier which will be subsituted for a
  58  *              `setid' for catgets(3c) output, or a `domain' name for
  59  *              gettext(3i) output.  This value is determine by substituting the
  60  *              token for the associated definition found in the message
  61  *              identifier file (specified with the -i option).
  62  *
  63  *              Multiple tokens are taken to be a message definition followed by
  64  *              the associated message string.  The message string is copied to
  65  *              the data array being built in msg.c.  The index into this array
  66  *              becomes the `message' identifier created in the msg.h file.
  67  */
  68 
  69 #include        <fcntl.h>
  70 #include        <stdlib.h>
  71 #include        <stdio.h>
  72 #include        <unistd.h>
  73 #include        <limits.h>
  74 #include        <string.h>
  75 #include        <ctype.h>
  76 #include        <errno.h>
  77 #include        <sys/param.h>
  78 
  79 #include        <sgs.h>
  80 #include        <_string_table.h>
  81 
  82 /*
  83  * Define any error message strings.
  84  */
  85 static const char
  86         * Errmsg_malt = "sgsmsg: file %s: line %d: malformed input "
  87                         "at line\n",
  88         * Errmsg_nmem = "sgsmsg: memory allocation failed: %s\n",
  89         * Errmsg_opne = "sgsmsg: file %s: open failed: %s\n",
  90         * Errmsg_wrte = "sgsmsg: file %s: write failed: %s\n",
  91         * Errmsg_read = "sgsmsg: file %s: read failed %s\n",
  92         * Errmsg_stnw = "sgsmsg: st_new(): failed: %s\n",
  93         * Errmsg_stin = "sgsmsg: Str_tbl insert failed: %s\n",
  94         * Errmsg_mnfn = "sgsmsg: message not found in Str_tbl: %s\n",
  95         * Errmsg_use  = "usage: sgsmsg [-clv] [-d mesgdata] [-h mesgdefs] "
  96                         "[-m messages] [-n name] [-i mesgident] file ...\n";
  97 
  98 /*
  99  * Define all output filenames and associated descriptors.
 100  */
 101 static FILE     *fddefs, *fddata, *fdmsgs, *fdmids, *fddesc;
 102 static char     *fldefs, *fldata, *flmsgs, *flmids, *fldesc;
 103 static FILE     *fdlint;
 104 static char     fllint[MAXPATHLEN];
 105 
 106 static uint_t           vflag;  /* verbose flag */
 107 static Str_tbl          *stp;   /* string table */
 108 
 109 /*
 110  * Define any default strings.
 111  */
 112 static const char
 113         *nmlint =       "/tmp/sgsmsg.lint",
 114         *interface =    "sgs_msg",
 115         *start =        "_START_",
 116         *end =          "_END_";
 117 
 118 /*
 119  * Define any default flags and data items.
 120  */
 121 static int      cflag = 0, lflag = 0, prtmsgs = 0, line, ptr = 1, msgid = 0;
 122 static char     *mesgid = 0, *setid = 0, *domain = 0;
 123 
 124 typedef struct msg_string {
 125         char                    *ms_defn;
 126         char                    *ms_message;
 127         struct msg_string       *ms_next;
 128 } msg_string;
 129 
 130 static msg_string       *msg_head;
 131 static msg_string       *msg_tail;
 132 
 133 /*
 134  * message_append() is responsible for both inserting strings into
 135  * the master Str_tbl as well as maintaining a list of the
 136  * DEFINITIONS associated with each string.
 137  *
 138  * The list of strings is traversed at the end once the full
 139  * Str_tbl has been constructed - and string offsets can be
 140  * assigned.
 141  */
 142 static void
 143 message_append(const char *defn, const char *message)
 144 {
 145         msg_string      *msg;
 146         if ((msg = calloc(sizeof (msg_string), 1)) == 0) {
 147                 (void) fprintf(stderr, Errmsg_nmem, strerror(errno));
 148                 exit(1);
 149         }
 150 
 151         /*
 152          * Initialize the string table.
 153          */
 154         if ((stp == 0) && ((stp = st_new(FLG_STNEW_COMPRESS)) == NULL)) {
 155                 (void) fprintf(stderr, Errmsg_stnw, strerror(errno));
 156                 exit(1);
 157         }
 158 
 159 
 160         if ((msg->ms_defn = strdup(defn)) == 0) {
 161                 (void) fprintf(stderr, Errmsg_nmem, strerror(errno));
 162                 exit(1);
 163         }
 164         if ((msg->ms_message = strdup(message)) == 0) {
 165                 (void) fprintf(stderr, Errmsg_nmem, strerror(errno));
 166                 exit(1);
 167         }
 168 
 169         if (st_insert(stp, msg->ms_message) == -1) {
 170                 (void) fprintf(stderr, Errmsg_stin,
 171                     message);
 172                 exit(1);
 173         }
 174 
 175         if (msg_head == 0) {
 176                 msg_head = msg_tail = msg;
 177                 return;
 178         }
 179         msg_tail->ms_next = msg;
 180         msg_tail = msg;
 181 }
 182 
 183 /*
 184  * Initialize a setid value.  Given a setid definition determine its numeric
 185  * value from the specified message identifier file (specified with the -i
 186  * option).  Return a pointer to the numeric string.
 187  */
 188 static int
 189 getmesgid(char *id)
 190 {
 191         char    *buffer, *token, *_mesgid = 0, *_setid = 0, *_domain = 0;
 192 
 193         /*
 194          * If we're being asked to interpret a message id but the user didn't
 195          * provide the required message identifier file (-i option) we're in
 196          * trouble.
 197          */
 198         if (flmids == 0) {
 199                 (void) fprintf(stderr, "sgsmsg: file %s: line %d: mesgid %s: "
 200                     "unable to process mesgid\n\t"
 201                     "no message identifier file specified "
 202                     "(see -i option)\n", fldesc, line, id);
 203                 return (1);
 204         }
 205 
 206         if ((buffer = malloc(LINE_MAX)) == 0) {
 207                 (void) fprintf(stderr, Errmsg_nmem, strerror(errno));
 208                 return (1);
 209         }
 210 
 211         /*
 212          * Read the message identifier file and locate the required mesgid.
 213          */
 214         rewind(fdmids);
 215         while (fgets(buffer, LINE_MAX, fdmids) != NULL) {
 216                 if ((token = strstr(buffer, id)) == NULL)
 217                         continue;
 218 
 219                 /*
 220                  * Establish individual strings for the mesgid, setid and domain
 221                  * values.
 222                  */
 223                 _mesgid = token;
 224                 while (!(isspace(*token)))
 225                         token++;
 226                 *token++ = 0;
 227 
 228                 while (isspace(*token))
 229                         token++;
 230                 _setid = token;
 231                 while (!(isspace(*token)))
 232                         token++;
 233                 *token++ = 0;
 234 
 235                 while (isspace(*token))
 236                         token++;
 237                 _domain = token;
 238                 while (!(isspace(*token)))
 239                         token++;
 240                 *token = 0;
 241                 break;
 242         }
 243 
 244         /*
 245          * Did we find a match?
 246          */
 247         if ((_mesgid == 0) || (_setid == 0) || (_domain == 0)) {
 248                 (void) fprintf(stderr, "sgsmsg: file %s: line %d: mesgid %s: "
 249                     "unable to process mesgid\n\t"
 250                     "identifier does not exist in file %s\n",
 251                     fldesc, line, id, flmids);
 252                 return (1);
 253         }
 254 
 255         /*
 256          * Have we been here before?
 257          */
 258         if (mesgid) {
 259                 if (cflag == 1) {
 260                         /*
 261                          * If we're being asked to process more than one mesgid
 262                          * warn the user that only one mesgid can be used for
 263                          * the catgets(3c) call.
 264                          */
 265                         (void) fprintf(stderr, "sgsmsg: file %s: line %d: "
 266                             "setid %s: warning: multiple mesgids "
 267                             "encountered\n\t"
 268                             "last setting used in messaging code\n",
 269                             fldesc, line, id);
 270                 }
 271         }
 272 
 273         mesgid = _mesgid;
 274         setid = _setid;
 275         domain = _domain;
 276 
 277         /*
 278          * Generate the message file output (insure output flag is enabled).
 279          */
 280         if (prtmsgs != -1)
 281                 prtmsgs = 1;
 282         if (fdmsgs && (prtmsgs == 1)) {
 283                 if (cflag == 1) {
 284                         if (fprintf(fdmsgs, "$quote \"\n$set %s\n",
 285                             setid) < 0) {
 286                                 (void) fprintf(stderr, Errmsg_wrte, flmsgs,
 287                                     strerror(errno));
 288                                 return (1);
 289                         }
 290                 } else {
 291                         if (fprintf(fdmsgs, "domain\t\"%s\"\n", domain) < 0) {
 292                                 (void) fprintf(stderr, Errmsg_wrte, flmsgs,
 293                                     strerror(errno));
 294                                 return (1);
 295                         }
 296                 }
 297         }
 298 
 299         /*
 300          * For catgets(3c) output generate a setid definition in the message
 301          * definition file.
 302          */
 303         if (fddefs && (cflag == 1) &&
 304             (fprintf(fddefs, "#define\t%s\t%s\n\n", mesgid, setid) < 0)) {
 305                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 306                 return (1);
 307         }
 308 
 309         return (0);
 310 }
 311 
 312 /*
 313  * Dump contents of String Table to standard out
 314  */
 315 static void
 316 dump_stringtab(Str_tbl *stp)
 317 {
 318         uint_t  cnt;
 319 
 320         if ((stp->st_flags & FLG_STTAB_COMPRESS) == 0) {
 321                 (void) printf("string table full size: %ld: uncompressed\n",
 322                     stp->st_fullstrsize);
 323                 return;
 324         }
 325 
 326         (void) printf("string table full size: %ld compressed down to: %ld\n\n",
 327             stp->st_fullstrsize, stp->st_strsize);
 328         (void) printf("string table compression information [%d buckets]:\n",
 329             stp->st_hbckcnt);
 330 
 331         for (cnt = 0; cnt < stp->st_hbckcnt; cnt++) {
 332                 Str_hash        *sthash = stp->st_hashbcks[cnt];
 333 
 334                 if (sthash == 0)
 335                         continue;
 336 
 337                 (void) printf(" bucket: [%d]\n", cnt);
 338 
 339                 while (sthash) {
 340                         size_t  stroff = sthash->hi_mstr->sm_strlen -
 341                             sthash->hi_strlen;
 342 
 343                         if (stroff == 0) {
 344                                 (void) printf("  [%ld]: '%s'  <master>\n",
 345                                     sthash->hi_refcnt, sthash->hi_mstr->sm_str);
 346                         } else {
 347                                 (void) printf("  [%ld]: '%s'  <suffix of: "
 348                                     "'%s'>\n", sthash->hi_refcnt,
 349                                     &sthash->hi_mstr->sm_str[stroff],
 350                                     sthash->hi_mstr->sm_str);
 351                         }
 352                         sthash = sthash->hi_next;
 353                 }
 354         }
 355 }
 356 
 357 /*
 358  * Initialize the message definition header file stream.
 359  */
 360 static int
 361 init_defs(void)
 362 {
 363         static char     guard[FILENAME_MAX + 6];
 364         char            *optr;
 365         const char      *iptr, *_ptr;
 366 
 367         /*
 368          * Establish a header guard name using the files basename.
 369          */
 370         for (iptr = 0, _ptr = fldefs; _ptr && (*_ptr != '\0'); _ptr++) {
 371                 if (*_ptr == '/')
 372                         iptr = _ptr + 1;
 373         }
 374         if (iptr == 0)
 375                 iptr = fldefs;
 376 
 377         optr = guard;
 378         for (*optr++ = '_'; iptr && (*iptr != '\0'); iptr++, optr++) {
 379                 if (*iptr == '.') {
 380                         *optr++ = '_';
 381                         *optr++ = 'D';
 382                         *optr++ = 'O';
 383                         *optr++ = 'T';
 384                         *optr = '_';
 385                 } else
 386                         *optr = toupper(*iptr);
 387         }
 388 
 389         if (fprintf(fddefs, "#ifndef\t%s\n#define\t%s\n\n", guard, guard) < 0) {
 390                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 391                 return (1);
 392         }
 393 
 394         if (fprintf(fddefs, "#include <sgsmsg.h>\t/* Msg typedef */\n\n") < 0) {
 395                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 396                 return (1);
 397         }
 398 
 399         if (fprintf(fddefs, "#ifndef\t__lint\n\n") < 0) {
 400                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 401                 return (1);
 402         }
 403 
 404         /*
 405          * The MSG_SGS_ARRAY_NAME macro supplies a generic way to
 406          * reference the string table regardless of its name.
 407          */
 408         if (fprintf(fddefs, "#define\tMSG_SGS_LOCAL_ARRAY\t__%s\n\n",
 409             interface) < 0) {
 410                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 411                 return (1);
 412         }
 413 
 414         /*
 415          * If the associated data array is global define a prototype.
 416          * Define a macro to access the array elements.
 417          */
 418         if (lflag == 0) {
 419                 if (fprintf(fddefs, "extern\tconst char\t__%s[];\n\n",
 420                     interface) < 0) {
 421                         (void) fprintf(stderr, Errmsg_wrte, fldefs,
 422                             strerror(errno));
 423                         return (1);
 424                 }
 425         }
 426         if (fprintf(fddefs,
 427             "#define\tMSG_ORIG_STRTAB(_x, _s)\t&_s[_x]\n\n") < 0) {
 428                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 429                 return (1);
 430         }
 431         if (fprintf(fddefs,
 432             "#define\tMSG_ORIG(x)\tMSG_ORIG_STRTAB(x, __%s)\n\n",
 433             interface) < 0) {
 434                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 435                 return (1);
 436         }
 437 
 438         /*
 439          * Generate a prototype to access the associated data array.
 440          */
 441         if (fprintf(fddefs, "extern\tconst char *\t_%s(Msg);\n\n",
 442             interface) < 0) {
 443                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 444                 return (1);
 445         }
 446         if (fprintf(fddefs, "#define\tMSG_INTL(x)\t_%s(x)\n\n",
 447             interface) < 0) {
 448                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 449                 return (1);
 450         }
 451 
 452         return (0);
 453 }
 454 
 455 
 456 /*
 457  * Finish the message definition header file.
 458  */
 459 static int
 460 fini_defs(void)
 461 {
 462         if (fprintf(fddefs, "\n#else\t/* __lint */\n\n") < 0) {
 463                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 464                 return (1);
 465         }
 466 
 467         if (fprintf(fddefs, "extern\tconst char *\t_%s(Msg);\n\n",
 468             interface) < 0) {
 469                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 470                 return (1);
 471         }
 472 
 473         if (fprintf(fddefs, "#ifndef MSG_SGS_LOCAL_ARRAY\n"
 474             "#define\tMSG_SGS_LOCAL_ARRAY\t\"\"\n#endif\n\n") < 0) {
 475                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 476                 return (1);
 477         }
 478 
 479         if (lflag == 0) {
 480                 if (fprintf(fddefs, "extern\tconst char\t__%s[];\n\n",
 481                     interface) < 0) {
 482                         (void) fprintf(stderr, Errmsg_wrte, fldefs,
 483                             strerror(errno));
 484                         return (1);
 485                 }
 486         }
 487 
 488         if (fprintf(fddefs,
 489             "#define MSG_ORIG_STRTAB(_x, _s)\t_x\n"
 490             "#define MSG_ORIG(x)\tx\n#define MSG_INTL(x)\tx\n") < 0) {
 491                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 492                 return (1);
 493         }
 494 
 495         /*
 496          * Provide a way to get the array and function declarations above
 497          * without also getting the actual messages. This is useful in
 498          * our lintsup.c files that include more than one message header.
 499          * lintsup doesn't need the actual messages, and this prevents
 500          * macro name collisions.
 501          */
 502         if (fprintf(fddefs, "\n#ifndef LINTSUP_SUPPRESS_STRINGS\n") < 0) {
 503                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 504                 return (1);
 505         }
 506 
 507         /*
 508          * Copy the temporary lint defs file into the new header.
 509          */
 510         if (fdlint) {
 511                 long    size;
 512                 char    *buf;
 513 
 514                 size = ftell(fdlint);
 515                 (void) rewind(fdlint);
 516 
 517                 if ((buf = malloc(size)) == 0) {
 518                         (void) fprintf(stderr, Errmsg_nmem, strerror(errno));
 519                         return (1);
 520                 }
 521                 if (fread(buf, size, 1, fdlint) == 0) {
 522                         (void) fprintf(stderr, Errmsg_read, fllint,
 523                             strerror(errno));
 524                         return (1);
 525                 }
 526                 if (fwrite(buf, size, 1, fddefs) == 0) {
 527                         (void) fprintf(stderr, Errmsg_wrte, fldefs,
 528                             strerror(errno));
 529                         return (1);
 530                 }
 531                 (void) free(buf);
 532         }
 533 
 534         if (fprintf(fddefs, "\n#endif\t/* LINTSUP_SUPPRESS_STRINGS */\n") < 0) {
 535                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 536                 return (1);
 537         }
 538 
 539         if (fprintf(fddefs, "\n#endif\t/* __lint */\n") < 0) {
 540                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 541                 return (1);
 542         }
 543 
 544         if (fprintf(fddefs, "\n#endif\n") < 0) {
 545                 (void) fprintf(stderr, Errmsg_wrte, fldefs, strerror(errno));
 546                 return (1);
 547         }
 548 
 549         return (0);
 550 }
 551 
 552 /*
 553  * The entire messaging file has been scanned - and all strings have been
 554  * inserted into the string_table.  We can now walk the message queue
 555  * and create the '#define <DEFN>' for each string - with the strings
 556  * assigned offset into the string_table.
 557  */
 558 static int
 559 output_defs(void)
 560 {
 561         msg_string      *msg;
 562         size_t          stbufsize;
 563         char            *stbuf;
 564 
 565         stbufsize = st_getstrtab_sz(stp);
 566         if ((stbuf = malloc(stbufsize)) == 0) {
 567                 (void) fprintf(stderr, Errmsg_nmem, strerror(errno));
 568                 exit(1);
 569         }
 570         (void) st_setstrbuf(stp, stbuf, stbufsize);
 571         for (msg = msg_head; msg; msg = msg->ms_next) {
 572                 size_t  stoff;
 573                 if ((st_setstring(stp, msg->ms_message, &stoff)) == -1) {
 574                         (void) fprintf(stderr, Errmsg_mnfn, msg->ms_message);
 575                         return (1);
 576                 }
 577                 if (fprintf(fddefs, "\n#define\t%s\t%ld\n",
 578                     msg->ms_defn, stoff) < 0) {
 579                         (void) fprintf(stderr, Errmsg_wrte,
 580                             fldefs, strerror(errno));
 581                         return (1);
 582                 }
 583                 if (fddefs && fprintf(fddefs, "#define\t%s_SIZE\t%d\n",
 584                     msg->ms_defn, strlen(msg->ms_message)) < 0) {
 585                         (void) fprintf(stderr, Errmsg_wrte,
 586                             fldefs, strerror(errno));
 587                         return (1);
 588                 }
 589         }
 590         return (0);
 591 }
 592 
 593 
 594 /*
 595  * Finish off the data structure definition.
 596  */
 597 static int
 598 output_data(void)
 599 {
 600         size_t          stbufsize;
 601         size_t          ndx;
 602         size_t          column = 1;
 603         const char      *stbuf;
 604         const char      *fmtstr;
 605 
 606         stbufsize = st_getstrtab_sz(stp);
 607         stbuf = st_getstrbuf(stp);
 608 
 609         assert(stbuf);
 610 
 611         /*
 612          * Determine from the local flag whether the data declaration should
 613          * be static.
 614          */
 615         if (lflag)
 616                 fmtstr = (const char *)"static const";
 617         else
 618                 fmtstr = (const char *)"const";
 619 
 620         if (fprintf(fddata, "\n%s char __%s[%ld] = { ",
 621             fmtstr, interface, stbufsize) < 0) {
 622                 (void) fprintf(stderr, Errmsg_wrte, fldata, strerror(errno));
 623                 return (1);
 624         }
 625 
 626         for (ndx = 0; ndx < (stbufsize - 1); ndx++) {
 627                 if (column == 1) {
 628                         if (fddata && fprintf(fddata,
 629                             "\n/* %4ld */ 0x%.2x,", ndx,
 630                             (unsigned char)stbuf[ndx]) < 0) {
 631                                 (void) fprintf(stderr, Errmsg_wrte,
 632                                     fldata, strerror(errno));
 633                                 return (1);
 634                         }
 635                 } else {
 636                         if (fddata && fprintf(fddata, "  0x%.2x,",
 637                             (unsigned char)stbuf[ndx]) < 0) {
 638                                 (void) fprintf(stderr, Errmsg_wrte,
 639                                     fldata, strerror(errno));
 640                                 return (1);
 641                         }
 642                 }
 643 
 644                 if (column++ == 10)
 645                         column = 1;
 646         }
 647 
 648         if (column == 1)
 649                 fmtstr = "\n\t0x%.2x };\n";
 650         else
 651                 fmtstr = "  0x%.2x };\n";
 652 
 653         if (fprintf(fddata, fmtstr, (unsigned char)stbuf[stbufsize - 1]) < 0) {
 654                 (void) fprintf(stderr, Errmsg_wrte, fldata, strerror(errno));
 655                 return (1);
 656         }
 657 
 658         return (0);
 659 }
 660 
 661 static int
 662 file()
 663 {
 664         char    buffer[LINE_MAX], * token;
 665         uint_t  bufsize;
 666         char    *token_buffer;
 667         int     escape = 0;
 668         int     len = 0;
 669 
 670         if ((token_buffer = malloc(LINE_MAX)) == 0) {
 671                 (void) fprintf(stderr, Errmsg_nmem, strerror(errno));
 672                 return (1);
 673         }
 674         bufsize = LINE_MAX;
 675 
 676         line = 1;
 677 
 678         while ((token = fgets(buffer, LINE_MAX, fddesc)) != NULL) {
 679                 char    defn[PATH_MAX], * _defn, * str;
 680 
 681                 switch (*token) {
 682                 case '#':
 683                 case '$':
 684                         if (escape) {
 685                                 (void) fprintf(stderr, Errmsg_malt, fldesc,
 686                                     line);
 687                                 return (1);
 688                         }
 689 
 690                         /*
 691                          * If a msgid has been output a msgstr must follow
 692                          * before we digest the new token.  A msgid is only set
 693                          * if fdmsgs is in use.
 694                          */
 695                         if (msgid) {
 696                                 msgid = 0;
 697                                 if (fprintf(fdmsgs, "msgstr\t\"\"\n") < 0) {
 698                                         (void) fprintf(stderr, Errmsg_wrte,
 699                                             flmsgs, strerror(errno));
 700                                         return (1);
 701                                 }
 702                         }
 703 
 704                         /*
 705                          * Pass lines directly through to the output message
 706                          * file.
 707                          */
 708                         if (fdmsgs && (prtmsgs == 1)) {
 709                                 char    comment;
 710 
 711                                 if (cflag == 0)
 712                                         comment = '#';
 713                                 else
 714                                         comment = '$';
 715 
 716                                 if (fprintf(fdmsgs, "%c%s", comment,
 717                                     ++token) < 0) {
 718                                         (void) fprintf(stderr, Errmsg_wrte,
 719                                             flmsgs, strerror(errno));
 720                                         return (1);
 721                                 }
 722                         }
 723                         break;
 724 
 725                 case '@':
 726                         if (escape) {
 727                                 (void) fprintf(stderr, Errmsg_malt, fldesc,
 728                                     line);
 729                                 return (1);
 730                         }
 731 
 732                         /*
 733                          * If a msgid has been output a msgstr must follow
 734                          * before we digest the new token.
 735                          */
 736                         if (msgid) {
 737                                 msgid = 0;
 738                                 if (fprintf(fdmsgs, "msgstr\t\"\"\n") < 0) {
 739                                         (void) fprintf(stderr, Errmsg_wrte,
 740                                             flmsgs, strerror(errno));
 741                                         return (1);
 742                                 }
 743                         }
 744 
 745                         /*
 746                          * Determine whether we have one or more tokens.
 747                          */
 748                         token++;
 749                         while (isspace(*token))         /* rid any whitespace */
 750                                 token++;
 751                         _defn = token;                  /* definition start */
 752                         while (!(isspace(*token)))
 753                                 token++;
 754                         *token++ = 0;
 755 
 756                         while (isspace(*token))         /* rid any whitespace */
 757                                 token++;
 758 
 759                         /*
 760                          * Determine whether the single token is one of the
 761                          * reserved message output delimiters otherwise
 762                          * translate it as a message identifier.
 763                          */
 764                         if (*token == 0) {
 765                                 if (strcmp(_defn, start) == 0)
 766                                         prtmsgs = 1;
 767                                 else if (strcmp(_defn, end) == 0)
 768                                         prtmsgs = -1;
 769                                 else if (getmesgid(_defn) == 1)
 770                                         return (1);
 771                                 break;
 772                         }
 773 
 774                         /*
 775                          * Multiple tokens are translated by taking the first
 776                          * token as the message definition, and the rest of the
 777                          * line as the message itself.  A message line ending
 778                          * with an escape ('\') is expected to be continued on
 779                          * the next line.
 780                          */
 781                         if (prtmsgs != -1)
 782                                 prtmsgs = 1;
 783                         if (fdmsgs && (prtmsgs == 1)) {
 784                                 /*
 785                                  * For catgets(3c) make sure a message
 786                                  * identifier has been established (this is
 787                                  * normally a domain for gettext(3i), but for
 788                                  * sgsmsg use this could be argued as being
 789                                  * redundent).  Also make sure that the message
 790                                  * definitions haven't exceeeded the maximum
 791                                  * value allowed by gencat(1) before generating
 792                                  * any message file entries.
 793                                  */
 794                                 if (cflag == 1) {
 795                                         if (setid == 0) {
 796                                                 (void) fprintf(stderr, "file "
 797                                                     "%s: no message identifier "
 798                                                     "has been established\n",
 799                                                     fldesc);
 800                                                 return (1);
 801                                         }
 802                                         if (ptr > NL_MSGMAX) {
 803                                                 (void) fprintf(stderr, "file "
 804                                                     "%s: message definition "
 805                                                     "(%d) exceeds allowable "
 806                                                     "limit (NL_MSGMAX)\n",
 807                                                     fldesc, ptr);
 808                                                 return (1);
 809                                         }
 810                                 }
 811 
 812                                 /*
 813                                  * For catgets(3c) write the definition and the
 814                                  * message string to the message file.  For
 815                                  * gettext(3i) write the message string as a
 816                                  * mesgid - indicate a mesgid has been output
 817                                  * so that a msgstr can follow.
 818                                  */
 819                                 if (cflag == 1) {
 820                                         if (fprintf(fdmsgs, "%d\t%s", ptr,
 821                                             token) < 0) {
 822                                                 (void) fprintf(stderr,
 823                                                     Errmsg_wrte, flmsgs,
 824                                                     strerror(errno));
 825                                                 return (1);
 826                                         }
 827                                 } else {
 828                                         if (fprintf(fdmsgs, "msgid\t\"") < 0) {
 829                                                 (void) fprintf(stderr,
 830                                                     Errmsg_wrte, flmsgs,
 831                                                     strerror(errno));
 832                                                 return (1);
 833                                         }
 834                                         msgid = 1;
 835                                 }
 836                         }
 837 
 838                         /*
 839                          * The message itself is a quoted string as this makes
 840                          * embedding spaces at the start (or the end) of the
 841                          * string very easy.
 842                          */
 843                         if (*token != '"') {
 844                                 (void) fprintf(stderr, Errmsg_malt, fldesc,
 845                                     line);
 846                                 return (1);
 847                         }
 848 
 849                         (void) strcpy(defn, _defn);
 850 
 851                         /*
 852                          * Write the tag to the lint definitions.
 853                          */
 854                         if (fdlint) {
 855                                 if (fprintf(fdlint, "\n#define\t%s\t",
 856                                     _defn) < 0) {
 857                                         (void) fprintf(stderr, Errmsg_wrte,
 858                                             fllint, strerror(errno));
 859                                         return (1);
 860                                 }
 861                         }
 862 
 863                         len = 0;
 864 
 865                         /*
 866                          * Write each character of the message string to the
 867                          * data array.  Translate any escaped characters - use
 868                          * the same specially recognized characters as defined
 869                          * by gencat(1).
 870                          */
 871 message:
 872                         if (*token == '"') {
 873                                 if (fdlint &&
 874                                     (fprintf(fdlint, "%c", *token) < 0)) {
 875                                         (void) fprintf(stderr, Errmsg_wrte,
 876                                             fllint, strerror(errno));
 877                                         return (1);
 878                                 }
 879                                 token++;
 880                         }
 881                         while (*token) {
 882                                 char    _token;
 883 
 884                                 if ((*token == '\\') && (escape == 0)) {
 885                                         escape = 1;
 886                                         if (fdlint && (*(token + 1) != '\n') &&
 887                                             fprintf(fdlint, "%c", *token) < 0) {
 888                                                 (void) fprintf(stderr,
 889                                                     Errmsg_wrte, fllint,
 890                                                     strerror(errno));
 891                                                 return (1);
 892                                         }
 893                                         token++;
 894                                         continue;
 895                                 }
 896                                 if (escape) {
 897                                         if (*token == 'n')
 898                                                 _token = '\n';
 899                                         else if (*token == 't')
 900                                                 _token = '\t';
 901                                         else if (*token == 'v')
 902                                                 _token = '\v';
 903                                         else if (*token == 'b')
 904                                                 _token = '\b';
 905                                         else if (*token == 'f')
 906                                                 _token = '\f';
 907                                         else if (*token == '\\')
 908                                                 _token = '\\';
 909                                         else if (*token == '"')
 910                                                 _token = '"';
 911                                         else if (*token == '\n')
 912                                                 break;
 913                                         else
 914                                                 _token = *token;
 915 
 916                                         if (fdmsgs && (prtmsgs == 1) &&
 917                                             (fprintf(fdmsgs, "\\") < 0)) {
 918                                                 (void) fprintf(stderr,
 919                                                     Errmsg_wrte, flmsgs,
 920                                                     strerror(errno));
 921                                                 return (1);
 922                                         }
 923                                 } else {
 924                                         /*
 925                                          * If this is the trailing quote then
 926                                          * thats the last of the message string.
 927                                          * Eat up any remaining white space and
 928                                          * unless an escape character is found
 929                                          * terminate the data string with a 0.
 930                                          */
 931                                         /* BEGIN CSTYLED */
 932                                         if (*token == '"') {
 933                                             if (fdlint && (fprintf(fdlint,
 934                                                 "%c", *token) < 0)) {
 935                                                 (void) fprintf(stderr,
 936                                                     Errmsg_wrte, fllint,
 937                                                     strerror(errno));
 938                                                 return (1);
 939                                             }
 940 
 941                                             if (fdmsgs && (prtmsgs == 1) &&
 942                                                 (fprintf(fdmsgs, "%c",
 943                                                 *token) < 0)) {
 944                                                 (void) fprintf(stderr,
 945                                                     Errmsg_wrte, flmsgs,
 946                                                     strerror(errno));
 947                                                 return (1);
 948                                             }
 949 
 950                                             while (*++token) {
 951                                                 if (*token == '\n')
 952                                                         break;
 953                                             }
 954                                             _token = '\0';
 955                                         } else
 956                                             _token = *token;
 957                                         /* END CSTYLED */
 958                                 }
 959 
 960                                 if (fdmsgs && (prtmsgs == 1) &&
 961                                     (fprintf(fdmsgs, "%c", *token) < 0)) {
 962                                         (void) fprintf(stderr, Errmsg_wrte,
 963                                             flmsgs, strerror(errno));
 964                                         return (1);
 965                                 }
 966 
 967                                 if (fdlint && fprintf(fdlint,
 968                                     "%c", *token) < 0) {
 969                                         (void) fprintf(stderr, Errmsg_wrte,
 970                                             fllint, strerror(errno));
 971                                         return (1);
 972                                 }
 973 
 974                                 if (len >= bufsize) {
 975                                         bufsize += LINE_MAX;
 976                                         if ((token_buffer = realloc(
 977                                             token_buffer, bufsize)) == 0) {
 978                                                 (void) fprintf(stderr,
 979                                                     Errmsg_nmem,
 980                                                     strerror(errno));
 981                                                 return (1);
 982                                         }
 983                                 }
 984                                 token_buffer[len] = _token;
 985                                 ptr++, token++, len++;
 986                                 escape = 0;
 987 
 988                                 if (_token == '\0')
 989                                         break;
 990                         }
 991 
 992                         /*
 993                          * After the complete message string has been processed
 994                          * (including its continuation beyond one line), create
 995                          * a string size definition.
 996                          */
 997                         if (escape == 0) {
 998                                 const char *form = "#define\t%s_SIZE\t%d\n";
 999 
1000                                 token_buffer[len] = '\0';
1001 
1002                                 message_append(defn, token_buffer);
1003 
1004                                 if (fdlint && fprintf(fdlint, form, defn,
1005                                     (len - 1)) < 0) {
1006                                         (void) fprintf(stderr, Errmsg_wrte,
1007                                             fllint, strerror(errno));
1008                                         return (1);
1009                                 }
1010                         }
1011                         break;
1012 
1013                 default:
1014                         /*
1015                          * Empty lines are passed through to the message file.
1016                          */
1017                         while (isspace(*token))
1018                                 token++;
1019 
1020                         if (*token == 0) {
1021                                 if (msgid || (fdmsgs && (prtmsgs == 1))) {
1022                                         /*
1023                                          * If a msgid has been output a msgstr
1024                                          * must follow before we digest the new
1025                                          * token.
1026                                          */
1027                                         if (msgid) {
1028                                                 msgid = 0;
1029                                                 str = "msgstr\t\"\"\n\n";
1030                                         } else
1031                                                 str = "\n";
1032 
1033                                         if (fprintf(fdmsgs, str) < 0) {
1034                                                 (void) fprintf(stderr,
1035                                                     Errmsg_wrte, flmsgs,
1036                                                     strerror(errno));
1037                                                 return (1);
1038                                         }
1039                                 }
1040                                 break;
1041                         }
1042 
1043                         /*
1044                          * If an escape is in effect then any tokens are taken
1045                          * to be message continuations.
1046                          */
1047                         if (escape) {
1048                                 escape = 0;
1049                                 goto message;
1050                         }
1051 
1052                         (void) fprintf(stderr, "file %s: line %d: invalid "
1053                             "input does not start with #, $ or @\n", fldesc,
1054                             line);
1055                         return (1);
1056                 }
1057                 line++;
1058         }
1059 
1060         free(token_buffer);
1061 
1062         return (0);
1063 }
1064 
1065 int
1066 main(int argc, char ** argv)
1067 {
1068         opterr = 0;
1069         while ((line = getopt(argc, argv, "cd:h:lm:n:i:v")) != EOF) {
1070                 switch (line) {
1071                 case 'c':                       /* catgets instead of gettext */
1072                         cflag = 1;
1073                         break;
1074                 case 'd':                       /* new message data filename */
1075                         fldata = optarg;        /*      (msg.c is default) */
1076                         break;
1077                 case 'h':                       /* new message defs filename */
1078                         fldefs = optarg;        /*      (msg.h is default) */
1079                         break;
1080                 case 'i':                       /* input message ids from */
1081                         flmids = optarg;        /*      from this file */
1082                         break;
1083                 case 'l':                       /* define message data arrays */
1084                         lflag = 1;              /*      to be local (static) */
1085                         break;
1086                 case 'm':                       /* generate message database */
1087                         flmsgs = optarg;        /*      to this file */
1088                         break;
1089                 case 'n':                       /* new data array and func */
1090                         interface = optarg;     /*      name (msg is default) */
1091                         break;
1092                 case 'v':
1093                         vflag = 1;              /* set verbose flag */
1094                         break;
1095                 case '?':
1096                         (void) fprintf(stderr, Errmsg_use, argv[0]);
1097                         exit(1);
1098                 default:
1099                         break;
1100                 }
1101         }
1102 
1103         /*
1104          * Validate the we have been given at least one input file.
1105          */
1106         if ((argc - optind) < 1) {
1107                 (void) fprintf(stderr, Errmsg_use);
1108                 exit(1);
1109         }
1110 
1111         /*
1112          * Open all the required output files.
1113          */
1114         if (fldefs) {
1115                 if ((fddefs = fopen(fldefs, "w+")) == NULL) {
1116                         (void) fprintf(stderr, Errmsg_opne, fldefs,
1117                             strerror(errno));
1118                         return (1);
1119                 }
1120         }
1121         if (fldata) {
1122                 if (fldefs && (strcmp(fldefs, fldata) == 0))
1123                         fddata = fddefs;
1124                 else if ((fddata = fopen(fldata, "w+")) == NULL) {
1125                         (void) fprintf(stderr, Errmsg_opne, fldata,
1126                             strerror(errno));
1127                         return (1);
1128                 }
1129         }
1130         if (fddefs && fddata) {
1131                 (void) sprintf(fllint, "%s.%d.XXXXXX", nmlint, (int)getpid());
1132                 if ((mkstemp(fllint) == -1) ||
1133                     ((fdlint = fopen(fllint, "w+")) == NULL)) {
1134                         (void) fprintf(stderr, Errmsg_opne, fllint,
1135                             strerror(errno));
1136                         return (1);
1137                 }
1138         }
1139         if (flmsgs) {
1140                 if ((fdmsgs = fopen(flmsgs, "w+")) == NULL) {
1141                         (void) fprintf(stderr, Errmsg_opne, flmsgs,
1142                             strerror(errno));
1143                         return (1);
1144                 }
1145         }
1146         if (flmids) {
1147                 if ((fdmids = fopen(flmids, "r")) == NULL) {
1148                         (void) fprintf(stderr, Errmsg_opne, flmids,
1149                             strerror(errno));
1150                         return (1);
1151                 }
1152         }
1153 
1154 
1155         /*
1156          * Initialize the message definition and message data streams.
1157          */
1158         if (fddefs) {
1159                 if (init_defs())
1160                         return (1);
1161         }
1162 
1163         /*
1164          * Read the input message file, and for each line process accordingly.
1165          */
1166         for (; optind < argc; optind++) {
1167                 int     err;
1168 
1169                 fldesc = argv[optind];
1170 
1171                 if ((fddesc = fopen(fldesc, "r")) == NULL) {
1172                         (void) fprintf(stderr, Errmsg_opne, fldesc,
1173                             strerror(errno));
1174                         return (1);
1175                 }
1176                 err = file();
1177                 (void) fclose(fddesc);
1178 
1179                 if (err != 0)
1180                         return (1);
1181         }
1182 
1183         /*
1184          * If a msgid has been output a msgstr must follow before we end the
1185          * file.
1186          */
1187         if (msgid) {
1188                 msgid = 0;
1189                 if (fprintf(fdmsgs, "msgstr\t\"\"\n") < 0) {
1190                         (void) fprintf(stderr, Errmsg_wrte, flmsgs,
1191                             strerror(errno));
1192                         return (1);
1193                 }
1194         }
1195 
1196         if (fdmids)
1197                 (void) fclose(fdmids);
1198         if (fdmsgs)
1199                 (void) fclose(fdmsgs);
1200 
1201         if (fddefs) {
1202                 if (output_defs())
1203                         return (1);
1204         }
1205 
1206         /*
1207          * Finish off any generated data and header file.
1208          */
1209         if (fldata) {
1210                 if (output_data())
1211                         return (1);
1212         }
1213         if (fddefs) {
1214                 if (fini_defs())
1215                         return (1);
1216         }
1217 
1218         if (vflag)
1219                 dump_stringtab(stp);
1220 
1221         /*
1222          * Close up everything and go home.
1223          */
1224         if (fddata)
1225                 (void) fclose(fddata);
1226         if (fddefs && (fddefs != fddata))
1227                 (void) fclose(fddefs);
1228         if (fddefs && fddata) {
1229                 (void) fclose(fdlint);
1230                 (void) unlink(fllint);
1231         }
1232 
1233         if (stp)
1234                 st_destroy(stp);
1235 
1236         return (0);
1237 }