/*
* 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 (c) 1993, 2011, Oracle and/or its affiliates. All rights reserved.
*/
/* Copyright (c) 1988 AT&T */
/* All Rights Reserved */
/* __gtxt(): Common part to gettxt() and pfmt() */
#pragma weak _setcat = setcat
#include "lint.h"
#include "libc.h"
#include <mtlib.h>
#include <sys/types.h>
#include <string.h>
#include <locale.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <synch.h>
#include <pfmt.h>
#include <thread.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include "../i18n/_locale.h"
#include "../i18n/_loc_path.h"
#define MESSAGES "/LC_MESSAGES/"
static const char *def_locale = "C";
static const char *not_found = "Message not found!!\n";
static struct db_info *db_info;
static int db_count, maxdb;
struct db_info {
char db_name[DB_NAME_LEN]; /* Name of the message file */
uintptr_t addr; /* Virtual memory address */
size_t length;
char *saved_locale;
char flag;
};
#define DB_EXIST 1 /* The catalogue exists */
#define DB_OPEN 2 /* Already tried to open */
/* Minimum number of open catalogues */
#define MINDB 3
char cur_cat[DB_NAME_LEN];
rwlock_t _rw_cur_cat = DEFAULTRWLOCK;
/*
* setcat(cat): Specify the default catalogue.
* Return a pointer to the local copy of the default catalogue
*/
const char *
setcat(const char *cat)
{
lrw_wrlock(&_rw_cur_cat);
if (cat) {
if (((strchr(cat, '/') != NULL)) ||
((strchr(cat, ':') != NULL))) {
cur_cat[0] = '\0';
goto out;
}
(void) strncpy(cur_cat, cat, sizeof (cur_cat) - 1);
cur_cat[sizeof (cur_cat) - 1] = '\0';
}
out:
lrw_unlock(&_rw_cur_cat);
return (cur_cat[0] ? cur_cat : NULL);
}
/*
* load a message catalog which specified with current locale,
* and catalog name.
*/
static struct db_info *
load_db(const char *curloc, const char *catname, int *err)
{
char pathname[PATH_MAX];
struct stat64 sb;
caddr_t addr;
struct db_info *db;
int fd;
int i;
*err = 0;
/* First time called, allocate space */
if (!db_info) {
if ((db_info =
libc_malloc(MINDB * sizeof (struct db_info))) == NULL) {
*err = 1;
return (NULL);
}
maxdb = MINDB;
}
for (i = 0; i < db_count; i++) {
if (db_info[i].flag == 0)
break;
}
/* New catalogue */
if (i == db_count) {
if (db_count == maxdb) {
if ((db = libc_realloc(db_info,
++maxdb * sizeof (struct db_info))) == NULL) {
*err = 1;
return (NULL);
}
db_info = db;
}
db_count++;
}
db = &db_info[i];
db->flag = 0;
(void) strcpy(db->db_name, catname);
db->saved_locale = libc_strdup(curloc);
if (db->saved_locale == NULL) {
*err = 1;
return (NULL);
}
db->flag = DB_OPEN;
if (snprintf(pathname, sizeof (pathname),
_DFLT_LOC_PATH "%s" MESSAGES "%s",
db->saved_locale, db->db_name) >= sizeof (pathname)) {
/*
* We won't set err here, because an invalid locale is not
* the fatal condition, but we can fall back to "C"
* locale.
*/
return (NULL);
}
if ((fd = open(pathname, O_RDONLY)) != -1 &&
fstat64(fd, &sb) != -1 &&
(addr = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0L)) !=
MAP_FAILED) {
db->flag |= DB_EXIST;
db->addr = (uintptr_t)addr;
db->length = (size_t)sb.st_size;
}
if (fd != -1)
(void) close(fd);
return (db);
}
/*
* unmap the message catalog, and release the db_info slot.
*/
static void
unload_db(struct db_info *db)
{
if ((db->flag & (DB_OPEN|DB_EXIST)) ==
(DB_OPEN|DB_EXIST)) {
(void) munmap((caddr_t)db->addr, db->length);
}
db->flag = 0;
if (db->saved_locale)
libc_free(db->saved_locale);
db->saved_locale = NULL;
}
/*
* go through the db_info, and find out a db_info slot regarding
* the given current locale and catalog name.
* If db is not NULL, then search will start from top of the array,
* otherwise it will start from the next of given db.
* If curloc is set to NULL, then return a cache without regards of
* locale.
*/
static struct db_info *
lookup_cache(struct db_info *db, const char *curloc, const char *catname)
{
if (db_info == NULL)
return (NULL);
if (db == NULL)
db = db_info;
else
db++;
for (; db < &db_info[db_count]; db++) {
if (db->flag == 0)
continue;
if (strcmp(db->db_name, catname) == 0) {
if (curloc == NULL ||
(db->saved_locale != NULL &&
strcmp(db->saved_locale, curloc) == 0)) {
return (db);
}
}
}
return (NULL);
}
static int
valid_msg(struct db_info *db, int id)
{
if (db == NULL || (db->flag & DB_EXIST) == 0)
return (0);
/* catalog has been loaded */
if (id != 0 && id <= *(int *)(db->addr))
return (1);
/* not a valid id */
return (0);
}
static char *
msg(struct db_info *db, int id)
{
return ((char *)(db->addr + *(int *)(db->addr +
id * sizeof (int))));
}
/*
* __gtxt(catname, id, dflt): Return a pointer to a message.
* catname is the name of the catalog. If null, the default catalog is
* used.
* id is the numeric id of the message in the catalogue
* dflt is the default message.
*
* Information about non-existent catalogues is kept in db_info, in
* such a way that subsequent calls with the same catalogue do not
* try to open the catalogue again.
*/
const char *
__gtxt(const char *catname, int id, const char *dflt)
{
char *curloc;
struct db_info *db;
int err;
/* Check for invalid message id */
if (id < 0)
return (not_found);
if (id == 0)
return ((dflt && *dflt) ? dflt : not_found);
/*
* If catalogue is unspecified, use default catalogue.
* No catalogue at all is an error
*/
if (!catname || !*catname) {
lrw_rdlock(&_rw_cur_cat);
if (cur_cat == NULL || !*cur_cat) {
lrw_unlock(&_rw_cur_cat);
return (not_found);
}
catname = cur_cat;
lrw_unlock(&_rw_cur_cat);
}
curloc = setlocale(LC_MESSAGES, NULL);
/* First look up the cache */
db = lookup_cache(NULL, curloc, catname);
if (db != NULL) {
/*
* The catalog has been loaded, and if id seems valid,
* then just return.
*/
if (valid_msg(db, id))
return (msg(db, id));
/*
* seems given id is out of bound or does not exist. In this
* case, we need to look up a message for the "C" locale as
* documented in the man page.
*/
db = lookup_cache(NULL, def_locale, catname);
if (db == NULL) {
/*
* Even the message catalog for the "C" has not been
* loaded.
*/
db = load_db(def_locale, catname, &err);
if (err)
return (not_found);
}
if (valid_msg(db, id))
return (msg(db, id));
/* no message found */
return ((dflt && *dflt) ? dflt : not_found);
}
/*
* The catalog has not been loaded or even has not
* attempted to be loaded, invalidate all caches related to
* the catname for possibly different locale.
*/
db = NULL;
while ((db = lookup_cache(db, NULL, catname)) != NULL)
unload_db(db);
/*
* load a message catalog for the requested locale.
*/
db = load_db(curloc, catname, &err);
if (err)
return (not_found);
if (valid_msg(db, id))
return (msg(db, id));
/*
* If the requested catalog is either not exist or message
* id is invalid, then try to load from "C" locale.
*/
db = load_db(def_locale, catname, &err);
if (err)
return (not_found);
if (valid_msg(db, id))
return (msg(db, id));
/* no message found */
return ((dflt && *dflt) ? dflt : not_found);
}