root/usr/src/cmd/genmsg/util.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <libintl.h>
#include <locale.h>
#include <libgen.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "genmsg.h"

#define SET_TOKEN       "$set"
#define DELSET_TOKEN    "$delset"
#define QUOTE_TOKEN     "$quote"

#define SkipSpace(s)    while (*(s) == ' ' || *(s) == '\t') s++

extern char *program;           /* from main.c */
extern char *mctag;             /* from main.c */
extern char *sctag;             /* from main.c */
extern char *premsg;            /* from main.c */
extern char *sufmsg;            /* from main.c */
extern int suppress_error;      /* from main.c */
extern void warning(char *);    /* from genmsg.l */

typedef struct _SetID *SetID;
typedef struct _MsgID *MsgID;

typedef struct _SetID SetIDRec;
struct _SetID {
        int id;
        char *comment;
        MsgID top;
        SetID next;
};

typedef struct _MsgID MsgIDRec;
struct _MsgID {
        int no_write;
        int id;
        char *msg;
        int line;
        char *file;
        char *comment;
        MsgID next;
};


/* Top pointer of the setid list. */
static SetID setid_top;

/* comment for messages. */
static char *msg_comment;

/* comment for set numbers. */
static char *set_comment;

/* List of set number's maximum message numbers. */
static int msgid_table[NL_SETMAX+1];

/* Quote character to surround messages. */
static char quote = QUOTE;

/* Internal functions. */
static void add_msgid(SetID, int, char *, char *, int, int);
static void add_setid(int, int, char *, char *, int, int);
static SetID lookup_setid(int);
static MsgID lookup_msgid(SetID, int, char *, char *, int);
static void print_prefix(FILE *, char *, int, char *);
static int is_bs_terminated(char *);
static char *ustrdup(char *);
static void makeup_msg(char **);

void
add_msg(int setid, int msgid, char *msg, char *file, int line, int no_write)
{
        SetID si;

        if (si = lookup_setid(setid)) {
                if (lookup_msgid(si, msgid, msg, file, line)) {
                        return; /* we already have the one. */
                } else {
                        add_msgid(si, msgid, msg, file, line, no_write);
                }
        } else {
                add_setid(setid, msgid, msg, file, line, no_write);
        }
}

int
is_writable(char *file)
{
        struct stat buf;

        if (stat(file, &buf) == -1)
                return (TRUE);

        if (access(file, W_OK) == 0)
                return (TRUE);

        return (FALSE);
}

void
write_msgfile(char *file)
{
        FILE *fp;
        SetID si = setid_top;
        char *mode = "w";
        char pquote[2];

        if (is_writable(file) == FALSE) {
                prg_err(gettext("cannot create \"%s\": permission denied"),
                    file);
                return;
        }

        if (IsActiveMode(AppendMode)) {
                mode = "a";
        }

        if ((fp = fopen(file, mode)) == NULL) {
                prg_err(gettext("cannot create \"%s\""), file);
                return;
        }

        if (quote) {
                pquote[0] = quote;
        } else {
                pquote[0] = '\0';
        }
        pquote[1] = '\0';

        /* AppendMode is already turned off if the file doesn't exist. */
        if (!IsActiveMode(AppendMode)) {
                (void) fprintf(fp, "\n$quote %s\n\n", pquote);
        }

        while (si) {
                int is_set = FALSE;
                MsgID mi = si->top;
                while (mi) {
                        char msg[NL_TEXTMAX+32]; /* 32 is some other stuff. */

                        if (mi->no_write) {
                                mi = mi->next;
                                continue;
                        }
                        if (is_set == FALSE) {
                                if (si->comment &&
                                    !IsActiveMode(BackCommentMode)) {
                                        (void) fprintf(fp, "\n");
                                        print_prefix(fp, "$ ", TRUE,
                                            si->comment);
                                        (void) fprintf(fp, "$set\t%d\n",
                                            si->id);
                                } else {
                                        (void) fprintf(fp, "\n$set\t%d\n",
                                            si->id);
                                }
                                if (si->comment &&
                                    IsActiveMode(BackCommentMode)) {
                                        print_prefix(fp, "$ ", TRUE,
                                            si->comment);
                                }
                                (void) fprintf(fp, "\n");
                                is_set = TRUE;
                        }

                        makeup_msg(&(mi->msg));

                        (void) snprintf(msg, sizeof (msg), "%d\t%s%s%s\n",
                            mi->id, pquote, mi->msg, pquote);

                        if (!IsActiveMode(BackCommentMode)) {
                                if (mi->line && mi->file &&
                                    IsActiveMode(LineInfoMode)) {
                                        (void) fprintf(fp,
                                            "$ File:%s, line:%d\n",
                                            basename(mi->file), mi->line);
                                }

                                if (mi->comment) {
                                        print_prefix(fp, "$ ", TRUE,
                                            mi->comment);
                                }

                                if (IsActiveMode(DoubleLineMode)) {
                                        print_prefix(fp, "$ ", FALSE, msg);
                                }
                        }

                        (void) fprintf(fp, "%s", msg);

                        if (IsActiveMode(BackCommentMode)) {
                                if (mi->line && mi->file &&
                                    IsActiveMode(LineInfoMode)) {
                                        (void) fprintf(fp,
                                            "$ File:%s, line:%d\n",
                                            basename(mi->file), mi->line);
                                }

                                if (mi->comment) {
                                        print_prefix(fp, "$ ", TRUE,
                                            mi->comment);
                                }

                                if (IsActiveMode(DoubleLineMode)) {
                                        print_prefix(fp, "$ ", FALSE, msg);
                                }
                        }

                        (void) fprintf(fp, "\n");

                        mi = mi->next;
                }
                si = si->next;
        }

        (void) fclose(fp);
}

static SetID
lookup_setid(int id)
{
        SetID si = setid_top;
        while (si) {
                if (si->id == id) {
                        return (si);
                }
                si = si->next;
        }
        return (NULL);
}

static MsgID
lookup_msgid(SetID si, int msgid, char *msg, char *file, int line)
{
        MsgID mi = si->top;
        while (mi) {
                if (mi->id == msgid) {
                        /* same setid & msgid, but different msg. */
                        if (strcmp(mi->msg, msg)) {
                                src_err(file, line, gettext(
                        "multiple messages: set number %d, message number %d\n"
                        "       current : \"%s\"\n"
                        "       previous: \"%s\" : \"%s\", line %d"),
                                    si->id, mi->id,
                                    msg,
                                    mi->msg, mi->file, mi->line);
                        }
                        return (mi);
                }
                mi = mi->next;
        }
        return (NULL);
}

static void
add_msgid(SetID si, int msgid, char *msg, char *file, int line, int no_write)
{
        MsgID mi = si->top, newmi, prev = NULL;
        int len = strlen(msg);

        if (msgid == 0) {
                src_err(file, line, gettext("improper message number: %d"),
                    msgid);
                return;
        }

        if (msgid > NL_MSGMAX) {
                src_err(file, line, gettext("too large message number: %d"),
                    msgid);
                return;
        }

        if (len > NL_TEXTMAX) {
                src_err(file, line, gettext("too long message text"));
                return;
        }

        while (mi) {
                if (mi->id > msgid) {
                        break;
                }
                prev = mi;
                mi = mi->next;
        }

        if ((newmi = malloc(sizeof (MsgIDRec))) == NULL) {
                prg_err(gettext("fatal: out of memory"));
                exit(EXIT_FAILURE);
        }

        newmi->no_write = no_write;
        newmi->id = msgid;
        newmi->msg = ustrdup(msg);
        newmi->file = ustrdup(file);
        newmi->line = line;
        newmi->next = mi;

        if (msg_comment) {
                newmi->comment = ustrdup(msg_comment);
                free(msg_comment);
                msg_comment = NULL;
        } else {
                newmi->comment = NULL;
        }

        if (prev == NULL) {
                si->top = newmi;
        } else {
                prev->next = newmi;
        }
}

static void
add_setid(int setid, int msgid, char *msg, char *file, int line, int no_write)
{
        SetID si = setid_top, newsi, prev = NULL;

        while (si) {
                if (si->id > setid) {
                        break;
                }
                prev = si;
                si = si->next;
        }

        if ((newsi = malloc(sizeof (SetIDRec))) == NULL) {
                prg_err(gettext("fatal: out of memory"));
                exit(EXIT_FAILURE);
        }

        newsi->id = setid;
        newsi->top = NULL;
        newsi->next = si;

        if (set_comment) {
                newsi->comment = ustrdup(set_comment);
                free(set_comment);
                set_comment = NULL;
        } else {
                newsi->comment = NULL;
        }

        if (prev == NULL) {
                setid_top = newsi;
        } else {
                prev->next = newsi;
        }

        add_msgid(newsi, msgid, msg, file, line, no_write);
}

static void
print_prefix(FILE *fp, char *prefix, int rm_blank, char *str)
{
        (void) fprintf(fp, "%s", prefix);
        while (*str) {
                (void) fputc(*str, fp);
                if (*str == '\n' && *(str+1) != '\0') {
                        (void) fprintf(fp, "%s", prefix);
                        if (rm_blank == TRUE) {
                                str++;
                                SkipSpace(str);
                                continue;
                        }
                }
                str++;
        }
        if (*(str-1) != '\n') {
                (void) fputc('\n', fp);
        }
}

int
read_projfile(char *file)
{
        FILE *fp;
        char line[LINE_MAX];

        if (file == NULL) {
                return (0);
        }

        if ((fp = fopen(file, "r")) == NULL) {
                return (0);
        }

        while (fgets(line, sizeof (line), fp) != NULL) {
                char *p = line;
                int n, setid, msgid;

                SkipSpace(p);

                if (*p == '#' || *p == '\n') {
                        continue;
                }

                n = sscanf(p, "%d %d", &setid, &msgid);

                if (n == 2) {
                        if (setid > NL_SETMAX) {
                                prg_err(gettext("%s: too large set number: %d"),
                                    file, setid);
                                continue;
                        }
                        msgid_table[setid] = msgid;
                } else {
                        prg_err(gettext(
                            "warning: %s: missing or invalid entry"), file);
                }
        }

        (void) fclose(fp);

        return (1);
}

void
write_projfile(char *file)
{
        FILE *fp;
        register int i;

        if (is_writable(file) == FALSE) {
                prg_err(gettext("cannot create \"%s\": permission denied"),
                    file);
                return;
        }

        if ((fp = fopen(file, "w")) == NULL) {
                prg_err(gettext("cannot create \"%s\""), file);
                return;
        }

        for (i = 1; i <= NL_SETMAX; i++) {
                if (msgid_table[i] > 0) {
                        SetID si;
                        char *com = NULL;

                        if (IsActiveMode(SetCommentMode) &&
                            (si = lookup_setid(i)) && si->comment) {
                                com = si->comment;
                        }

                        if (com && !IsActiveMode(BackCommentMode)) {
                                print_prefix(fp, "# ", TRUE, com);
                        }

                        (void) fprintf(fp, "%d\t%d\n", i, msgid_table[i]);

                        if (com && IsActiveMode(BackCommentMode)) {
                                print_prefix(fp, "# ", TRUE, com);
                        }
                }
        }

        (void) fclose(fp);
}

int
get_msgid(char *file, int line, int setid, char *str)
{
        SetID si = setid_top;
        int id = msgid_table[setid];

        while (si) {
                if (si->id == setid) {
                        MsgID mi = si->top;
                        while (mi) {
                                if (strcmp(mi->msg, str) == 0) {
                                        return (mi->id);
                                }
                                mi = mi->next;
                        }
                }
                si = si->next;
        }

        id++;

        if (id > NL_MSGMAX) {
                src_err(file, line,
                    gettext("run out of message number in set number: %d"),
                    setid);
                return (NOMSGID);
        }

        return (msgid_table[setid] = id);
}

void
set_msgid(int setid, int msgid)
{
        if (msgid_table[setid] < msgid) {
                msgid_table[setid] = msgid;
        }
}

void
add_comment(Mode mode, char *str)
{
        char *tag = (mode == MsgCommentMode) ? mctag : sctag;
        char **comment = (mode == MsgCommentMode)
            ? &msg_comment : &set_comment;

        if (strstr(str, tag) == NULL) {
                return;
        }

        if (*comment) {
                free(*comment);
        }

        *comment = ustrdup(str);
}

void
read_msgfile(char *file)
{
        FILE *fp;
        char c = 0;
        int line = 0;
        int inmsg = FALSE;
        int setid = 0, unsetid = -1, msgid = 0;
        struct stat buf;

        if ((fp = fopen(file, "r")) == NULL) {
                prg_err(gettext("cannot open \"%s\""), file);
                ResetActiveMode(AppendMode);
                return;
        }

        if (stat(file, &buf) == -1 && buf.st_size == 0) {
                ResetActiveMode(AppendMode);
                return;
        }

        quote = c;

        /*CONSTCOND*/
        while (1) {
                char buf[LINE_MAX];
                char *ptr;
                char msg[NL_TEXTMAX];

                if (fgets(buf, sizeof (buf), fp) == NULL) {
                        break;
                }

                line++;

                ptr = &buf[0];

                SkipSpace(ptr);

                if ((*ptr == '$' && (*(ptr+1) == ' ' || *(ptr+1) == '\t')) ||
                    ((*ptr == '\n') && inmsg == FALSE)) {
                        inmsg = FALSE;
                        continue;
                }

                if (strncmp(ptr, SET_TOKEN, sizeof (SET_TOKEN) - 1) == 0) {
                        if (sscanf(ptr, "%*s %d", &setid) != 1) {
                                setid = 0;
                        }
                        inmsg = FALSE;
                        continue;
                } else if (strncmp(ptr, DELSET_TOKEN,
                    sizeof (DELSET_TOKEN) - 1) == 0) {
                        if (sscanf(ptr, "%*s %d", &unsetid) != 1) {
                                unsetid = -1;
                        }
                        inmsg = FALSE;
                        continue;
                } else if (strncmp(ptr, QUOTE_TOKEN,
                    sizeof (QUOTE_TOKEN) - 1) == 0) {
                        if (sscanf(ptr, "%*s %c", &c) != 1) {
                                c = 0;
                        }
                        quote = c;
                        inmsg = FALSE;
                        continue;
                }

                if (setid == unsetid) {
                        continue;
                }

                if (inmsg) {
                        if (is_bs_terminated(ptr)) {
                                (void) strlcat(msg, ptr, sizeof (msg));
                                inmsg = TRUE;
                        } else {
                                int len = strlen(ptr);
                                *(ptr + len - 1) = '\0';
                                if (c && (*(ptr + len - 2) == c)) {
                                        *(ptr + len - 2) = '\0';
                                }
                                (void) strlcat(msg, ptr, sizeof (msg));
                                add_msg(setid, msgid, msg, file, line, TRUE);
                                inmsg = FALSE;
                        }
                        continue;
                }

                if (isdigit((unsigned char)*ptr)) {
                        char    *pptr;

                        SkipSpace(ptr);

                        msgid = (int)strtol(ptr, &pptr, 10);
                        ptr = pptr;

                        SkipSpace(ptr);

                        if (is_bs_terminated(ptr)) {
                                (void) memset(msg, 0, sizeof (msg));
                                if (c && (*ptr == c)) {
                                        ptr++;
                                }
                                (void) strlcpy(msg, ptr, sizeof (msg));
                                inmsg = TRUE;
                        } else {
                                int len = strlen(ptr);
                                *(ptr + len - 1) = '\0';
                                if (c && ((*ptr == c) &&
                                    (*(ptr + len - 2) == c))) {
                                        *(ptr + len - 2) = '\0';
                                        ptr++;
                                }
                                add_msg(setid, msgid, ptr, file, line, TRUE);
                                inmsg = FALSE;
                        }
                }
        }

        (void) fclose(fp);
}

static int
is_bs_terminated(char *msg)
{
        int len = strlen(msg);

        while (--len >= 0) {
                if (msg[len] == ' ' || msg[len] == '\t' || msg[len] == '\n') {
                        continue;
                } else if (msg[len] == '\\') {
                        len--;
                        if (len >= 0 && msg[len] == '\\')
                                return (0);
                        return (1);
                } else {
                        return (0);
                }
        }
        return (0);
}

static char *
ustrdup(char *str)
{
        char *tmp = strdup(str);
        if (tmp == NULL) {
                prg_err(gettext("fatal: out of memory"));
                exit(EXIT_FAILURE);
        }
        return (tmp);
}

int
file_copy(char *in, char *out)
{
        int ret = TRUE;
        FILE *fin, *fout;
        int c;
        sigset_t newmask, oldmask;

        (void) sigemptyset(&newmask);
        (void) sigaddset(&newmask, SIGQUIT);
        (void) sigaddset(&newmask, SIGINT);
        (void) sigaddset(&newmask, SIGHUP);
        (void) sigaddset(&newmask, SIGTERM);
        (void) sigprocmask(SIG_BLOCK, &newmask, &oldmask);

        if ((fin = fopen(in, "r")) == NULL) {
                prg_err(gettext("cannot open \"%s\""), in);
                ret = FALSE;
                goto done;
        }

        if ((fout = fopen(out, "w")) == NULL) {
                prg_err(gettext("cannot create \"%s\""), out);
                ret = FALSE;
                goto done;
        }

        while ((c = getc(fin)) != EOF)
                (void) putc(c, fout);

        (void) fclose(fin);
        (void) fclose(fout);

done:
        (void) sigprocmask(SIG_SETMASK, &oldmask, NULL);
        return (ret);
}

static void
makeup_msg(char **pmsg)
{
        char buf[NL_TEXTMAX];
        char *msg;

        msg = *pmsg;
        buf[0] = '\0';

        if (IsActiveMode(TripleMode) && strchr(msg, '%') == NULL) {
                /* there is no '%' in message. */
                int len = strlen(msg);

                if (msg[len-2] == '\\' && msg[len-1] == 'n') {
                        msg[len-2] = '\0';
                        (void) strlcat(buf, msg, sizeof (buf));
                        (void) strlcat(buf, msg, sizeof (buf));
                        (void) strlcat(buf, msg, sizeof (buf));
                        (void) strlcat(buf, "\\n", sizeof (buf));
                } else {
                        (void) strlcat(buf, msg, sizeof (buf));
                        (void) strlcat(buf, msg, sizeof (buf));
                        (void) strlcat(buf, msg, sizeof (buf));
                }
                free(msg);
                *pmsg = ustrdup(buf);
        }

        msg = *pmsg;
        buf[0] = '\0';

        if (IsActiveMode(PrefixMode)) {
                (void) strlcat(buf, premsg, sizeof (buf));
                (void) strlcat(buf, msg, sizeof (buf));
                free(msg);
                *pmsg = ustrdup(buf);
        }

        msg = *pmsg;
        buf[0] = '\0';

        if (IsActiveMode(SuffixMode)) {
                int len = strlen(msg);

                if (msg[len-2] == '\\' && msg[len-1] == 'n') {
                        msg[len-2] = '\0';
                        (void) strlcat(buf, msg, sizeof (buf));
                        (void) strlcat(buf, sufmsg, sizeof (buf));
                        (void) strlcat(buf, "\\n", sizeof (buf));
                } else {
                        (void) strlcat(buf, msg, sizeof (buf));
                        (void) strlcat(buf, sufmsg, sizeof (buf));
                }
                free(msg);
                *pmsg = ustrdup(buf);
        }
}

void
prg_err(char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);

        (void) fprintf(stderr, "%s: ", program);
        /* LINTED: E_SEC_PRINTF_VAR_FMT */
        (void) vfprintf(stderr, fmt, ap);
        (void) fprintf(stderr, "\n");

        va_end(ap);
}

void
src_err(char *file, int line, char *fmt, ...)
{
        va_list ap;

        if (suppress_error == TRUE) {
                return;
        }

        va_start(ap, fmt);

        (void) fprintf(stderr, gettext("\"%s\", line %d: "), file, line);
        /* LINTED: E_SEC_PRINTF_VAR_FMT */
        (void) vfprintf(stderr, fmt, ap);
        (void) fprintf(stderr, "\n");

        va_end(ap);
}