2020-03-30 03:09:06 +03:00
|
|
|
/* $NetBSD: tic.c,v 1.40 2020/03/30 00:09:06 roy Exp $ */
|
2010-02-03 18:16:32 +03:00
|
|
|
|
|
|
|
/*
|
terminfo: promote numeric parameters from short to int
POSIX mandates implementations must support upto a short but may exceed it.
When NetBSD terminfo was implemented, no terminfo description used over
a short, but because ncurses has supported ints for some time, some now do.
Infact, such a terminfo description was imported where colour pairs for
screen-256color went up to 65536 which exposed a bug in the existing
implementation where it set to zero. Because the number might mean
something more than a range, we need to be able to store it accurately.
This requires a version bump because whilst the API hasn't changed thanks
to C int promotion, the ABI has. Also the underlying database structure
has changed as well - we now store the numeric paramter inside a uint32_t
field rather than a uint16_t one.
Whilst this change can still read the old style database, the old one
cannot read the new one and thus we now maintain the database as
terminfo2.cdb, leaving the old library and database alone so old programs
still work fine.
libcurses, libfrom, libmenu and libpanel have also been bumped to
accomoate this change.
2020-03-13 18:19:24 +03:00
|
|
|
* Copyright (c) 2009, 2010, 2020 The NetBSD Foundation, Inc.
|
2010-02-03 18:16:32 +03:00
|
|
|
*
|
|
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
|
|
* by Roy Marples.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#if HAVE_NBTOOL_CONFIG_H
|
|
|
|
#include "nbtool_config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <sys/cdefs.h>
|
2020-03-30 03:09:06 +03:00
|
|
|
__RCSID("$NetBSD: tic.c,v 1.40 2020/03/30 00:09:06 roy Exp $");
|
2010-02-03 18:16:32 +03:00
|
|
|
|
|
|
|
#include <sys/types.h>
|
2012-05-31 23:56:32 +04:00
|
|
|
#include <sys/queue.h>
|
2017-01-11 00:15:23 +03:00
|
|
|
#include <sys/stat.h>
|
2010-02-20 09:15:06 +03:00
|
|
|
|
2010-02-20 09:20:46 +03:00
|
|
|
#if !HAVE_NBTOOL_CONFIG_H || HAVE_SYS_ENDIAN_H
|
2010-02-20 09:08:01 +03:00
|
|
|
#include <sys/endian.h>
|
2010-02-20 09:15:06 +03:00
|
|
|
#endif
|
2010-02-03 18:16:32 +03:00
|
|
|
|
2012-06-04 03:19:09 +04:00
|
|
|
#include <cdbw.h>
|
2010-02-03 18:16:32 +03:00
|
|
|
#include <ctype.h>
|
|
|
|
#include <err.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <getopt.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <fcntl.h>
|
2012-06-01 00:10:06 +04:00
|
|
|
#include <search.h>
|
2010-02-03 18:16:32 +03:00
|
|
|
#include <stdarg.h>
|
2020-03-30 00:54:03 +03:00
|
|
|
#include <stdbool.h>
|
2010-02-03 18:16:32 +03:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <term_private.h>
|
|
|
|
#include <term.h>
|
2017-10-03 00:53:55 +03:00
|
|
|
#include <unistd.h>
|
2012-12-09 03:29:28 +04:00
|
|
|
#include <util.h>
|
2012-06-01 00:10:06 +04:00
|
|
|
|
|
|
|
#define HASH_SIZE 16384 /* 2012-06-01: 3600 entries */
|
2010-02-03 18:16:32 +03:00
|
|
|
|
|
|
|
typedef struct term {
|
2012-06-04 03:19:09 +04:00
|
|
|
STAILQ_ENTRY(term) next;
|
2010-02-03 18:16:32 +03:00
|
|
|
char *name;
|
2010-02-23 02:05:39 +03:00
|
|
|
TIC *tic;
|
2012-06-04 03:19:09 +04:00
|
|
|
uint32_t id;
|
|
|
|
struct term *base_term;
|
2010-02-03 18:16:32 +03:00
|
|
|
} TERM;
|
2012-06-04 03:19:09 +04:00
|
|
|
static STAILQ_HEAD(, term) terms = STAILQ_HEAD_INITIALIZER(terms);
|
2010-02-03 18:16:32 +03:00
|
|
|
|
|
|
|
static int error_exit;
|
2010-02-23 02:05:39 +03:00
|
|
|
static int Sflag;
|
2012-06-01 01:01:06 +04:00
|
|
|
static size_t nterm, nalias;
|
2010-02-03 18:16:32 +03:00
|
|
|
|
2012-05-31 23:00:41 +04:00
|
|
|
static void __printflike(1, 2)
|
2010-02-03 18:16:32 +03:00
|
|
|
dowarn(const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list va;
|
|
|
|
|
|
|
|
error_exit = 1;
|
|
|
|
va_start(va, fmt);
|
|
|
|
vwarnx(fmt, va);
|
|
|
|
va_end(va);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *
|
|
|
|
grow_tbuf(TBUF *tbuf, size_t len)
|
|
|
|
{
|
|
|
|
char *buf;
|
2010-02-11 03:24:46 +03:00
|
|
|
|
2010-02-23 02:05:39 +03:00
|
|
|
buf = _ti_grow_tbuf(tbuf, len);
|
|
|
|
if (buf == NULL)
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE, "_ti_grow_tbuf");
|
2010-02-23 02:05:39 +03:00
|
|
|
return buf;
|
2010-02-11 03:24:46 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2012-06-04 03:19:09 +04:00
|
|
|
save_term(struct cdbw *db, TERM *term)
|
2010-02-11 03:24:46 +03:00
|
|
|
{
|
2010-02-23 02:05:39 +03:00
|
|
|
uint8_t *buf;
|
|
|
|
ssize_t len;
|
2012-06-04 03:19:09 +04:00
|
|
|
size_t slen = strlen(term->name) + 1;
|
|
|
|
|
|
|
|
if (term->base_term != NULL) {
|
2020-03-27 20:42:36 +03:00
|
|
|
char *cap;
|
|
|
|
len = (ssize_t)(1 + sizeof(uint32_t) + sizeof(uint16_t) + slen);
|
2012-06-04 03:19:09 +04:00
|
|
|
buf = emalloc(len);
|
2020-03-27 20:42:36 +03:00
|
|
|
cap = (char *)buf;
|
|
|
|
*cap++ = TERMINFO_ALIAS;
|
|
|
|
_ti_encode_32(&cap, term->base_term->id);
|
|
|
|
_ti_encode_count_str(&cap, term->name, slen);
|
2012-06-04 03:19:09 +04:00
|
|
|
if (cdbw_put(db, term->name, slen, buf, len))
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE, "cdbw_put");
|
2016-11-24 20:12:23 +03:00
|
|
|
free(buf);
|
2012-06-04 03:19:09 +04:00
|
|
|
return 0;
|
|
|
|
}
|
2010-02-11 03:24:46 +03:00
|
|
|
|
2010-02-23 02:05:39 +03:00
|
|
|
len = _ti_flatten(&buf, term->tic);
|
|
|
|
if (len == -1)
|
2010-02-11 03:24:46 +03:00
|
|
|
return -1;
|
2010-02-03 18:16:32 +03:00
|
|
|
|
2012-06-04 03:19:09 +04:00
|
|
|
if (cdbw_put_data(db, buf, len, &term->id))
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE, "cdbw_put_data");
|
2012-06-04 03:19:09 +04:00
|
|
|
if (cdbw_put_key(db, term->name, slen, term->id))
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE, "cdbw_put_key");
|
2010-02-23 02:05:39 +03:00
|
|
|
free(buf);
|
2010-02-03 18:16:32 +03:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static TERM *
|
|
|
|
find_term(const char *name)
|
|
|
|
{
|
2012-06-01 00:10:06 +04:00
|
|
|
ENTRY elem, *elemp;
|
2012-05-31 23:56:32 +04:00
|
|
|
|
2012-06-01 00:10:06 +04:00
|
|
|
elem.key = __UNCONST(name);
|
|
|
|
elem.data = NULL;
|
|
|
|
elemp = hsearch(elem, FIND);
|
|
|
|
return elemp ? (TERM *)elemp->data : NULL;
|
2010-02-03 18:16:32 +03:00
|
|
|
}
|
|
|
|
|
2020-03-30 00:54:03 +03:00
|
|
|
static TERM *
|
|
|
|
find_newest_term(const char *name)
|
|
|
|
{
|
|
|
|
char *lname;
|
|
|
|
TERM *term;
|
|
|
|
|
|
|
|
lname = _ti_getname(TERMINFO_RTYPE, name);
|
|
|
|
if (lname == NULL)
|
|
|
|
return NULL;
|
|
|
|
term = find_term(lname);
|
|
|
|
free(lname);
|
|
|
|
if (term == NULL)
|
|
|
|
term = find_term(name);
|
|
|
|
return term;
|
|
|
|
}
|
|
|
|
|
2010-02-03 18:16:32 +03:00
|
|
|
static TERM *
|
2012-06-04 03:19:09 +04:00
|
|
|
store_term(const char *name, TERM *base_term)
|
2010-02-03 18:16:32 +03:00
|
|
|
{
|
|
|
|
TERM *term;
|
2012-06-01 00:10:06 +04:00
|
|
|
ENTRY elem;
|
2010-02-03 18:16:32 +03:00
|
|
|
|
2012-06-01 00:38:19 +04:00
|
|
|
term = ecalloc(1, sizeof(*term));
|
|
|
|
term->name = estrdup(name);
|
2012-06-04 03:19:09 +04:00
|
|
|
STAILQ_INSERT_TAIL(&terms, term, next);
|
2012-06-01 00:10:06 +04:00
|
|
|
elem.key = estrdup(name);
|
|
|
|
elem.data = term;
|
|
|
|
hsearch(elem, ENTER);
|
2012-06-01 01:01:06 +04:00
|
|
|
|
2012-06-04 03:19:09 +04:00
|
|
|
term->base_term = base_term;
|
|
|
|
if (base_term != NULL)
|
2012-06-01 01:01:06 +04:00
|
|
|
nalias++;
|
|
|
|
else
|
|
|
|
nterm++;
|
|
|
|
|
2010-02-03 18:16:32 +03:00
|
|
|
return term;
|
|
|
|
}
|
|
|
|
|
2020-03-30 03:09:06 +03:00
|
|
|
static void
|
|
|
|
alias_terms(TERM *term)
|
|
|
|
{
|
|
|
|
char *p, *e, *alias;
|
|
|
|
|
|
|
|
/* Create aliased terms */
|
|
|
|
if (term->tic->alias == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
alias = p = estrdup(term->tic->alias);
|
|
|
|
while (p != NULL && *p != '\0') {
|
|
|
|
e = strchr(p, '|');
|
|
|
|
if (e != NULL)
|
|
|
|
*e++ = '\0';
|
|
|
|
/* No need to lengthcheck the alias because the main
|
|
|
|
* terminfo description already stores all the aliases
|
|
|
|
* in the same length field as the alias. */
|
|
|
|
if (find_term(p) != NULL) {
|
|
|
|
dowarn("%s: has alias for already assigned"
|
|
|
|
" term %s", term->tic->name, p);
|
|
|
|
} else {
|
|
|
|
store_term(p, term);
|
|
|
|
}
|
|
|
|
p = e;
|
|
|
|
}
|
|
|
|
free(alias);
|
|
|
|
}
|
|
|
|
|
2010-02-03 18:16:32 +03:00
|
|
|
static int
|
2010-02-23 02:05:39 +03:00
|
|
|
process_entry(TBUF *buf, int flags)
|
2010-02-03 18:16:32 +03:00
|
|
|
{
|
|
|
|
TERM *term;
|
|
|
|
TIC *tic;
|
2020-03-27 18:11:57 +03:00
|
|
|
TBUF sbuf = *buf;
|
2016-02-24 15:01:44 +03:00
|
|
|
|
2010-02-03 18:16:32 +03:00
|
|
|
if (buf->bufpos == 0)
|
|
|
|
return 0;
|
|
|
|
/* Terminate the string */
|
|
|
|
buf->buf[buf->bufpos - 1] = '\0';
|
|
|
|
/* First rewind the buffer for new entries */
|
|
|
|
buf->bufpos = 0;
|
|
|
|
|
|
|
|
if (isspace((unsigned char)*buf->buf))
|
|
|
|
return 0;
|
|
|
|
|
2010-02-23 02:05:39 +03:00
|
|
|
tic = _ti_compile(buf->buf, flags);
|
|
|
|
if (tic == NULL)
|
2010-02-03 18:16:32 +03:00
|
|
|
return 0;
|
|
|
|
|
2010-02-23 02:05:39 +03:00
|
|
|
if (find_term(tic->name) != NULL) {
|
|
|
|
dowarn("%s: duplicate entry", tic->name);
|
|
|
|
_ti_freetic(tic);
|
2010-02-03 18:16:32 +03:00
|
|
|
return 0;
|
|
|
|
}
|
2012-06-04 03:19:09 +04:00
|
|
|
term = store_term(tic->name, NULL);
|
2010-02-23 02:05:39 +03:00
|
|
|
term->tic = tic;
|
2020-03-30 03:09:06 +03:00
|
|
|
alias_terms(term);
|
2016-02-24 15:01:44 +03:00
|
|
|
|
2020-03-27 18:11:57 +03:00
|
|
|
if (tic->rtype == TERMINFO_RTYPE)
|
|
|
|
return process_entry(&sbuf, flags | TIC_COMPAT_V1);
|
|
|
|
|
2010-02-03 18:16:32 +03:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2010-02-23 02:05:39 +03:00
|
|
|
merge(TIC *rtic, TIC *utic, int flags)
|
2010-02-03 18:16:32 +03:00
|
|
|
{
|
2020-03-27 18:11:57 +03:00
|
|
|
char flag, type;
|
|
|
|
const char *cap, *code, *str;
|
terminfo: promote numeric parameters from short to int
POSIX mandates implementations must support upto a short but may exceed it.
When NetBSD terminfo was implemented, no terminfo description used over
a short, but because ncurses has supported ints for some time, some now do.
Infact, such a terminfo description was imported where colour pairs for
screen-256color went up to 65536 which exposed a bug in the existing
implementation where it set to zero. Because the number might mean
something more than a range, we need to be able to store it accurately.
This requires a version bump because whilst the API hasn't changed thanks
to C int promotion, the ABI has. Also the underlying database structure
has changed as well - we now store the numeric paramter inside a uint32_t
field rather than a uint16_t one.
Whilst this change can still read the old style database, the old one
cannot read the new one and thus we now maintain the database as
terminfo2.cdb, leaving the old library and database alone so old programs
still work fine.
libcurses, libfrom, libmenu and libpanel have also been bumped to
accomoate this change.
2020-03-13 18:19:24 +03:00
|
|
|
short ind, len;
|
|
|
|
int num;
|
2010-02-03 18:16:32 +03:00
|
|
|
size_t n;
|
|
|
|
|
2020-03-30 00:54:03 +03:00
|
|
|
if (rtic->rtype < utic->rtype)
|
|
|
|
errx(EXIT_FAILURE, "merge rtype diff (%s:%d into %s:%d)",
|
|
|
|
utic->name, utic->rtype, rtic->name, rtic->rtype);
|
2020-03-28 18:19:56 +03:00
|
|
|
|
2010-02-03 18:16:32 +03:00
|
|
|
cap = utic->flags.buf;
|
|
|
|
for (n = utic->flags.entries; n > 0; n--) {
|
2020-03-27 20:42:36 +03:00
|
|
|
ind = _ti_decode_16(&cap);
|
2010-02-03 18:16:32 +03:00
|
|
|
flag = *cap++;
|
|
|
|
if (VALID_BOOLEAN(flag) &&
|
2020-03-27 18:11:57 +03:00
|
|
|
_ti_find_cap(rtic, &rtic->flags, 'f', ind) == NULL)
|
2010-02-03 18:16:32 +03:00
|
|
|
{
|
2020-03-27 20:42:36 +03:00
|
|
|
if (!_ti_encode_buf_id_flags(&rtic->flags, ind, flag))
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE, "encode flag");
|
2010-02-03 18:16:32 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cap = utic->nums.buf;
|
|
|
|
for (n = utic->nums.entries; n > 0; n--) {
|
2020-03-27 20:42:36 +03:00
|
|
|
ind = _ti_decode_16(&cap);
|
|
|
|
num = _ti_decode_num(&cap, utic->rtype);
|
2010-02-03 18:16:32 +03:00
|
|
|
if (VALID_NUMERIC(num) &&
|
2020-03-27 18:11:57 +03:00
|
|
|
_ti_find_cap(rtic, &rtic->nums, 'n', ind) == NULL)
|
2010-02-03 18:16:32 +03:00
|
|
|
{
|
2020-03-27 20:42:36 +03:00
|
|
|
if (!_ti_encode_buf_id_num(&rtic->nums, ind, num,
|
|
|
|
_ti_numsize(rtic)))
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE, "encode num");
|
2010-02-03 18:16:32 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cap = utic->strs.buf;
|
|
|
|
for (n = utic->strs.entries; n > 0; n--) {
|
2020-03-27 20:42:36 +03:00
|
|
|
ind = _ti_decode_16(&cap);
|
|
|
|
len = _ti_decode_16(&cap);
|
terminfo: promote numeric parameters from short to int
POSIX mandates implementations must support upto a short but may exceed it.
When NetBSD terminfo was implemented, no terminfo description used over
a short, but because ncurses has supported ints for some time, some now do.
Infact, such a terminfo description was imported where colour pairs for
screen-256color went up to 65536 which exposed a bug in the existing
implementation where it set to zero. Because the number might mean
something more than a range, we need to be able to store it accurately.
This requires a version bump because whilst the API hasn't changed thanks
to C int promotion, the ABI has. Also the underlying database structure
has changed as well - we now store the numeric paramter inside a uint32_t
field rather than a uint16_t one.
Whilst this change can still read the old style database, the old one
cannot read the new one and thus we now maintain the database as
terminfo2.cdb, leaving the old library and database alone so old programs
still work fine.
libcurses, libfrom, libmenu and libpanel have also been bumped to
accomoate this change.
2020-03-13 18:19:24 +03:00
|
|
|
if (len > 0 &&
|
2020-03-27 18:11:57 +03:00
|
|
|
_ti_find_cap(rtic, &rtic->strs, 's', ind) == NULL)
|
2010-02-03 18:16:32 +03:00
|
|
|
{
|
2020-03-27 20:42:36 +03:00
|
|
|
if (!_ti_encode_buf_id_count_str(&rtic->strs, ind, cap,
|
|
|
|
len))
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE, "encode str");
|
2010-02-03 18:16:32 +03:00
|
|
|
}
|
terminfo: promote numeric parameters from short to int
POSIX mandates implementations must support upto a short but may exceed it.
When NetBSD terminfo was implemented, no terminfo description used over
a short, but because ncurses has supported ints for some time, some now do.
Infact, such a terminfo description was imported where colour pairs for
screen-256color went up to 65536 which exposed a bug in the existing
implementation where it set to zero. Because the number might mean
something more than a range, we need to be able to store it accurately.
This requires a version bump because whilst the API hasn't changed thanks
to C int promotion, the ABI has. Also the underlying database structure
has changed as well - we now store the numeric paramter inside a uint32_t
field rather than a uint16_t one.
Whilst this change can still read the old style database, the old one
cannot read the new one and thus we now maintain the database as
terminfo2.cdb, leaving the old library and database alone so old programs
still work fine.
libcurses, libfrom, libmenu and libpanel have also been bumped to
accomoate this change.
2020-03-13 18:19:24 +03:00
|
|
|
cap += len;
|
2010-02-03 18:16:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
cap = utic->extras.buf;
|
|
|
|
for (n = utic->extras.entries; n > 0; n--) {
|
2020-03-27 20:42:36 +03:00
|
|
|
num = _ti_decode_16(&cap);
|
2010-02-03 18:16:32 +03:00
|
|
|
code = cap;
|
|
|
|
cap += num;
|
|
|
|
type = *cap++;
|
|
|
|
flag = 0;
|
|
|
|
str = NULL;
|
|
|
|
switch (type) {
|
|
|
|
case 'f':
|
|
|
|
flag = *cap++;
|
|
|
|
if (!VALID_BOOLEAN(flag))
|
|
|
|
continue;
|
|
|
|
break;
|
|
|
|
case 'n':
|
2020-03-27 20:42:36 +03:00
|
|
|
num = _ti_decode_num(&cap, utic->rtype);
|
2010-02-03 18:16:32 +03:00
|
|
|
if (!VALID_NUMERIC(num))
|
|
|
|
continue;
|
|
|
|
break;
|
|
|
|
case 's':
|
2020-03-27 20:42:36 +03:00
|
|
|
num = _ti_decode_16(&cap);
|
2010-02-03 18:16:32 +03:00
|
|
|
str = cap;
|
|
|
|
cap += num;
|
|
|
|
if (num == 0)
|
|
|
|
continue;
|
|
|
|
break;
|
|
|
|
}
|
2010-02-23 02:05:39 +03:00
|
|
|
_ti_store_extra(rtic, 0, code, type, flag, num, str, num,
|
|
|
|
flags);
|
2010-02-03 18:16:32 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-30 00:54:03 +03:00
|
|
|
static int
|
|
|
|
dup_tbuf(TBUF *dst, const TBUF *src)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (src->buflen == 0)
|
|
|
|
return 0;
|
|
|
|
dst->buf = malloc(src->buflen);
|
|
|
|
if (dst->buf == NULL)
|
|
|
|
return -1;
|
|
|
|
dst->buflen = src->buflen;
|
|
|
|
memcpy(dst->buf, src->buf, dst->buflen);
|
|
|
|
dst->bufpos = src->bufpos;
|
|
|
|
dst->entries = src->entries;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
promote(TIC *rtic, TIC *utic)
|
|
|
|
{
|
|
|
|
TERM *nrterm = find_newest_term(rtic->name);
|
|
|
|
TERM *nuterm = find_newest_term(utic->name);
|
|
|
|
TERM *term;
|
|
|
|
TIC *tic;
|
|
|
|
|
|
|
|
if (nrterm == NULL || nuterm == NULL)
|
|
|
|
return -1;
|
|
|
|
if (nrterm->tic->rtype >= nuterm->tic->rtype)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
tic = calloc(1, sizeof(*tic));
|
|
|
|
if (tic == NULL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
tic->name = _ti_getname(TERMINFO_RTYPE, rtic->name);
|
|
|
|
if (tic->name == NULL)
|
|
|
|
goto err;
|
2020-03-30 03:09:06 +03:00
|
|
|
if (rtic->alias != NULL) {
|
|
|
|
tic->alias = strdup(rtic->alias);
|
|
|
|
if (tic->alias == NULL)
|
|
|
|
goto err;
|
|
|
|
}
|
2020-03-30 00:54:03 +03:00
|
|
|
if (rtic->desc != NULL) {
|
|
|
|
tic->desc = strdup(rtic->desc);
|
|
|
|
if (tic->desc == NULL)
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
tic->rtype = rtic->rtype;
|
|
|
|
if (dup_tbuf(&tic->flags, &rtic->flags) == -1)
|
|
|
|
goto err;
|
|
|
|
if (dup_tbuf(&tic->nums, &rtic->nums) == -1)
|
|
|
|
goto err;
|
|
|
|
if (dup_tbuf(&tic->strs, &rtic->strs) == -1)
|
|
|
|
goto err;
|
|
|
|
if (dup_tbuf(&tic->extras, &rtic->extras) == -1)
|
|
|
|
goto err;
|
|
|
|
if (_ti_promote(tic) == -1)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
term = store_term(tic->name, NULL);
|
2020-03-30 03:09:06 +03:00
|
|
|
if (term == NULL)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
term->tic = tic;
|
|
|
|
alias_terms(term);
|
|
|
|
return 0;
|
2020-03-30 00:54:03 +03:00
|
|
|
|
|
|
|
err:
|
|
|
|
free(tic->flags.buf);
|
|
|
|
free(tic->nums.buf);
|
|
|
|
free(tic->strs.buf);
|
|
|
|
free(tic->extras.buf);
|
|
|
|
free(tic->desc);
|
2020-03-30 03:09:06 +03:00
|
|
|
free(tic->alias);
|
2020-03-30 00:54:03 +03:00
|
|
|
free(tic->name);
|
|
|
|
free(tic);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-02-03 18:16:32 +03:00
|
|
|
static size_t
|
2010-02-23 02:05:39 +03:00
|
|
|
merge_use(int flags)
|
2010-02-03 18:16:32 +03:00
|
|
|
{
|
|
|
|
size_t skipped, merged, memn;
|
2020-03-27 20:42:36 +03:00
|
|
|
const char *cap;
|
2020-03-30 00:54:03 +03:00
|
|
|
char *name, *basename;
|
2010-02-03 18:16:32 +03:00
|
|
|
uint16_t num;
|
|
|
|
TIC *rtic, *utic;
|
2020-03-30 00:54:03 +03:00
|
|
|
TERM *term, *uterm;
|
|
|
|
bool promoted;
|
2010-02-03 18:16:32 +03:00
|
|
|
|
|
|
|
skipped = merged = 0;
|
2012-06-04 03:19:09 +04:00
|
|
|
STAILQ_FOREACH(term, &terms, next) {
|
|
|
|
if (term->base_term != NULL)
|
2010-02-03 18:16:32 +03:00
|
|
|
continue;
|
2010-02-23 02:05:39 +03:00
|
|
|
rtic = term->tic;
|
2020-03-30 00:54:03 +03:00
|
|
|
basename = _ti_getname(TERMINFO_RTYPE_O1, rtic->name);
|
|
|
|
promoted = false;
|
2020-03-27 18:11:57 +03:00
|
|
|
while ((cap = _ti_find_extra(rtic, &rtic->extras, "use"))
|
|
|
|
!= NULL) {
|
2010-02-03 18:16:32 +03:00
|
|
|
if (*cap++ != 's') {
|
|
|
|
dowarn("%s: use is not string", rtic->name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
cap += sizeof(uint16_t);
|
2020-03-30 00:54:03 +03:00
|
|
|
if (strcmp(basename, cap) == 0) {
|
2010-02-03 18:16:32 +03:00
|
|
|
dowarn("%s: uses itself", rtic->name);
|
|
|
|
goto remove;
|
|
|
|
}
|
2020-03-30 00:54:03 +03:00
|
|
|
name = _ti_getname(rtic->rtype, cap);
|
|
|
|
if (name == NULL) {
|
|
|
|
dowarn("%s: ???: %s", rtic->name, cap);
|
|
|
|
goto remove;
|
|
|
|
}
|
|
|
|
uterm = find_term(name);
|
|
|
|
free(name);
|
|
|
|
if (uterm == NULL)
|
|
|
|
uterm = find_term(cap);
|
2012-06-04 03:19:09 +04:00
|
|
|
if (uterm != NULL && uterm->base_term != NULL)
|
|
|
|
uterm = uterm->base_term;
|
2010-02-03 18:16:32 +03:00
|
|
|
if (uterm == NULL) {
|
|
|
|
dowarn("%s: no use record for %s",
|
|
|
|
rtic->name, cap);
|
|
|
|
goto remove;
|
|
|
|
}
|
2010-02-23 02:05:39 +03:00
|
|
|
utic = uterm->tic;
|
2010-02-03 18:16:32 +03:00
|
|
|
if (strcmp(utic->name, rtic->name) == 0) {
|
|
|
|
dowarn("%s: uses itself", rtic->name);
|
|
|
|
goto remove;
|
|
|
|
}
|
2020-03-27 18:11:57 +03:00
|
|
|
if (_ti_find_extra(utic, &utic->extras, "use")
|
|
|
|
!= NULL) {
|
2010-02-03 18:16:32 +03:00
|
|
|
skipped++;
|
|
|
|
break;
|
|
|
|
}
|
2020-03-30 00:54:03 +03:00
|
|
|
|
|
|
|
/* If we need to merge in a term that requires
|
|
|
|
* this term to be promoted, we need to duplicate
|
|
|
|
* this term, promote it and append it to our list. */
|
|
|
|
if (!promoted && rtic->rtype != TERMINFO_RTYPE) {
|
|
|
|
if (promote(rtic, utic) == -1)
|
|
|
|
err(EXIT_FAILURE, "promote");
|
|
|
|
promoted = true;
|
|
|
|
}
|
|
|
|
|
2010-02-23 02:05:39 +03:00
|
|
|
merge(rtic, utic, flags);
|
2010-02-03 18:16:32 +03:00
|
|
|
remove:
|
|
|
|
/* The pointers may have changed, find the use again */
|
2020-03-27 18:11:57 +03:00
|
|
|
cap = _ti_find_extra(rtic, &rtic->extras, "use");
|
2010-02-03 18:16:32 +03:00
|
|
|
if (cap == NULL)
|
|
|
|
dowarn("%s: use no longer exists - impossible",
|
|
|
|
rtic->name);
|
|
|
|
else {
|
2020-03-27 20:42:36 +03:00
|
|
|
char *scap = __UNCONST(
|
|
|
|
cap - (4 + sizeof(uint16_t)));
|
2010-02-03 18:16:32 +03:00
|
|
|
cap++;
|
2020-03-27 20:42:36 +03:00
|
|
|
num = _ti_decode_16(&cap);
|
|
|
|
cap += num;
|
2010-02-03 18:16:32 +03:00
|
|
|
memn = rtic->extras.bufpos -
|
|
|
|
(cap - rtic->extras.buf);
|
2011-11-03 14:12:57 +04:00
|
|
|
memmove(scap, cap, memn);
|
2010-02-03 18:16:32 +03:00
|
|
|
rtic->extras.bufpos -= cap - scap;
|
|
|
|
cap = scap;
|
|
|
|
rtic->extras.entries--;
|
|
|
|
merged++;
|
|
|
|
}
|
|
|
|
}
|
2020-03-30 00:54:03 +03:00
|
|
|
free(basename);
|
2010-02-03 18:16:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (merged == 0 && skipped != 0)
|
|
|
|
dowarn("circular use detected");
|
|
|
|
return merged;
|
|
|
|
}
|
|
|
|
|
2010-02-11 03:24:46 +03:00
|
|
|
static int
|
|
|
|
print_dump(int argc, char **argv)
|
|
|
|
{
|
|
|
|
TERM *term;
|
2010-02-23 02:05:39 +03:00
|
|
|
uint8_t *buf;
|
2010-02-11 03:24:46 +03:00
|
|
|
int i, n;
|
|
|
|
size_t j, col;
|
2010-02-23 02:05:39 +03:00
|
|
|
ssize_t len;
|
2010-02-11 03:24:46 +03:00
|
|
|
|
2010-02-11 16:09:57 +03:00
|
|
|
printf("struct compiled_term {\n");
|
|
|
|
printf("\tconst char *name;\n");
|
|
|
|
printf("\tconst char *cap;\n");
|
|
|
|
printf("\tsize_t caplen;\n");
|
|
|
|
printf("};\n\n");
|
|
|
|
|
|
|
|
printf("const struct compiled_term compiled_terms[] = {\n");
|
2010-02-11 03:24:46 +03:00
|
|
|
|
2010-02-11 16:09:57 +03:00
|
|
|
n = 0;
|
2010-02-11 03:24:46 +03:00
|
|
|
for (i = 0; i < argc; i++) {
|
2020-03-30 00:54:03 +03:00
|
|
|
term = find_newest_term(argv[i]);
|
2010-02-11 03:24:46 +03:00
|
|
|
if (term == NULL) {
|
|
|
|
warnx("%s: no description for terminal", argv[i]);
|
|
|
|
continue;
|
|
|
|
}
|
2012-06-04 03:19:09 +04:00
|
|
|
if (term->base_term != NULL) {
|
2010-02-11 03:24:46 +03:00
|
|
|
warnx("%s: cannot dump alias", argv[i]);
|
|
|
|
continue;
|
|
|
|
}
|
2010-02-23 02:05:39 +03:00
|
|
|
/* Don't compile the aliases in, save space */
|
|
|
|
free(term->tic->alias);
|
|
|
|
term->tic->alias = NULL;
|
|
|
|
len = _ti_flatten(&buf, term->tic);
|
|
|
|
if (len == 0 || len == -1)
|
2010-02-11 03:24:46 +03:00
|
|
|
continue;
|
|
|
|
|
2010-02-11 16:09:57 +03:00
|
|
|
printf("\t{\n");
|
|
|
|
printf("\t\t\"%s\",\n", argv[i]);
|
2010-02-11 03:24:46 +03:00
|
|
|
n++;
|
2010-02-23 02:05:39 +03:00
|
|
|
for (j = 0, col = 0; j < (size_t)len; j++) {
|
2010-02-11 03:24:46 +03:00
|
|
|
if (col == 0) {
|
2010-02-11 16:09:57 +03:00
|
|
|
printf("\t\t\"");
|
|
|
|
col = 16;
|
2010-02-11 03:24:46 +03:00
|
|
|
}
|
2016-02-24 15:01:44 +03:00
|
|
|
|
2010-02-23 02:05:39 +03:00
|
|
|
col += printf("\\%03o", (uint8_t)buf[j]);
|
2010-02-11 03:24:46 +03:00
|
|
|
if (col > 75) {
|
|
|
|
printf("\"%s\n",
|
2010-02-23 02:05:39 +03:00
|
|
|
j + 1 == (size_t)len ? "," : "");
|
2010-02-11 03:24:46 +03:00
|
|
|
col = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (col != 0)
|
2010-02-11 16:09:57 +03:00
|
|
|
printf("\",\n");
|
2010-02-23 02:05:39 +03:00
|
|
|
printf("\t\t%zu\n", len);
|
2010-02-11 16:09:57 +03:00
|
|
|
printf("\t}");
|
|
|
|
if (i + 1 < argc)
|
|
|
|
printf(",");
|
|
|
|
printf("\n");
|
2010-02-23 02:05:39 +03:00
|
|
|
free(buf);
|
2010-02-11 03:24:46 +03:00
|
|
|
}
|
2010-02-11 16:09:57 +03:00
|
|
|
printf("};\n");
|
2010-02-11 03:24:46 +03:00
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2012-06-04 03:19:09 +04:00
|
|
|
static void
|
|
|
|
write_database(const char *dbname)
|
|
|
|
{
|
|
|
|
struct cdbw *db;
|
|
|
|
char *tmp_dbname;
|
|
|
|
TERM *term;
|
|
|
|
int fd;
|
|
|
|
|
|
|
|
db = cdbw_open();
|
|
|
|
if (db == NULL)
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE, "cdbw_open failed");
|
2012-06-04 03:19:09 +04:00
|
|
|
/* Save the terms */
|
|
|
|
STAILQ_FOREACH(term, &terms, next)
|
|
|
|
save_term(db, term);
|
|
|
|
|
|
|
|
easprintf(&tmp_dbname, "%s.XXXXXX", dbname);
|
|
|
|
fd = mkstemp(tmp_dbname);
|
|
|
|
if (fd == -1)
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE,
|
|
|
|
"creating temporary database %s failed", tmp_dbname);
|
2012-06-04 03:19:09 +04:00
|
|
|
if (cdbw_output(db, fd, "NetBSD terminfo", cdbw_stable_seeder))
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE,
|
|
|
|
"writing temporary database %s failed", tmp_dbname);
|
2012-06-04 03:19:09 +04:00
|
|
|
if (fchmod(fd, DEFFILEMODE))
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE, "fchmod failed");
|
2012-06-04 03:19:09 +04:00
|
|
|
if (close(fd))
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE,
|
|
|
|
"writing temporary database %s failed", tmp_dbname);
|
2012-06-04 03:19:09 +04:00
|
|
|
if (rename(tmp_dbname, dbname))
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE, "renaming %s to %s failed", tmp_dbname, dbname);
|
2012-06-04 03:19:09 +04:00
|
|
|
free(tmp_dbname);
|
|
|
|
cdbw_close(db);
|
|
|
|
}
|
|
|
|
|
2010-02-03 18:16:32 +03:00
|
|
|
int
|
|
|
|
main(int argc, char **argv)
|
|
|
|
{
|
2010-02-23 02:05:39 +03:00
|
|
|
int ch, cflag, sflag, flags;
|
2012-06-04 03:19:09 +04:00
|
|
|
char *source, *dbname, *buf, *ofile;
|
2010-02-03 18:16:32 +03:00
|
|
|
FILE *f;
|
2012-06-01 01:01:06 +04:00
|
|
|
size_t buflen;
|
2011-11-09 11:40:27 +04:00
|
|
|
ssize_t len;
|
2010-02-03 18:16:32 +03:00
|
|
|
TBUF tbuf;
|
2017-05-04 17:07:33 +03:00
|
|
|
struct term *term;
|
2010-02-03 18:16:32 +03:00
|
|
|
|
|
|
|
cflag = sflag = 0;
|
|
|
|
ofile = NULL;
|
2010-02-23 02:05:39 +03:00
|
|
|
flags = TIC_ALIAS | TIC_DESCRIPTION | TIC_WARNING;
|
2010-02-11 03:24:46 +03:00
|
|
|
while ((ch = getopt(argc, argv, "Saco:sx")) != -1)
|
2010-02-03 18:16:32 +03:00
|
|
|
switch (ch) {
|
2010-02-11 03:24:46 +03:00
|
|
|
case 'S':
|
|
|
|
Sflag = 1;
|
2010-02-23 02:05:39 +03:00
|
|
|
/* We still compile aliases so that use= works.
|
|
|
|
* However, it's removed before we flatten to save space. */
|
|
|
|
flags &= ~TIC_DESCRIPTION;
|
2010-02-11 03:24:46 +03:00
|
|
|
break;
|
2010-02-03 18:16:32 +03:00
|
|
|
case 'a':
|
2010-02-23 02:05:39 +03:00
|
|
|
flags |= TIC_COMMENT;
|
2010-02-03 18:16:32 +03:00
|
|
|
break;
|
|
|
|
case 'c':
|
2010-02-05 19:36:09 +03:00
|
|
|
cflag = 1;
|
2010-02-03 18:16:32 +03:00
|
|
|
break;
|
|
|
|
case 'o':
|
|
|
|
ofile = optarg;
|
|
|
|
break;
|
|
|
|
case 's':
|
2010-02-05 19:36:09 +03:00
|
|
|
sflag = 1;
|
2010-02-03 18:16:32 +03:00
|
|
|
break;
|
|
|
|
case 'x':
|
2010-02-23 02:05:39 +03:00
|
|
|
flags |= TIC_EXTRA;
|
2010-02-03 18:16:32 +03:00
|
|
|
break;
|
|
|
|
case '?': /* FALLTHROUGH */
|
|
|
|
default:
|
2010-02-11 16:09:57 +03:00
|
|
|
fprintf(stderr, "usage: %s [-acSsx] [-o file] source\n",
|
2010-02-03 18:16:32 +03:00
|
|
|
getprogname());
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (optind == argc)
|
|
|
|
errx(1, "No source file given");
|
|
|
|
source = argv[optind++];
|
|
|
|
f = fopen(source, "r");
|
|
|
|
if (f == NULL)
|
2020-03-28 18:22:27 +03:00
|
|
|
err(EXIT_FAILURE, "fopen: %s", source);
|
2010-02-03 18:16:32 +03:00
|
|
|
|
2012-06-01 00:10:06 +04:00
|
|
|
hcreate(HASH_SIZE);
|
|
|
|
|
2012-06-01 16:08:40 +04:00
|
|
|
buf = tbuf.buf = NULL;
|
2017-05-04 16:53:36 +03:00
|
|
|
buflen = tbuf.buflen = tbuf.bufpos = 0;
|
2011-11-09 11:40:27 +04:00
|
|
|
while ((len = getline(&buf, &buflen, f)) != -1) {
|
2010-02-03 18:16:32 +03:00
|
|
|
/* Skip comments */
|
|
|
|
if (*buf == '#')
|
|
|
|
continue;
|
2011-11-09 11:40:27 +04:00
|
|
|
if (buf[len - 1] != '\n') {
|
2010-02-23 02:05:39 +03:00
|
|
|
process_entry(&tbuf, flags);
|
2010-02-03 18:16:32 +03:00
|
|
|
dowarn("last line is not a comment"
|
|
|
|
" and does not end with a newline");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/*
|
2017-05-04 16:53:36 +03:00
|
|
|
* If the first char is space not a space then we have a
|
|
|
|
* new entry, so process it.
|
|
|
|
*/
|
2010-02-03 18:16:32 +03:00
|
|
|
if (!isspace((unsigned char)*buf) && tbuf.bufpos != 0)
|
2010-02-23 02:05:39 +03:00
|
|
|
process_entry(&tbuf, flags);
|
2016-02-24 15:01:44 +03:00
|
|
|
|
2010-02-03 18:16:32 +03:00
|
|
|
/* Grow the buffer if needed */
|
2011-11-09 11:40:27 +04:00
|
|
|
grow_tbuf(&tbuf, len);
|
2010-02-03 18:16:32 +03:00
|
|
|
/* Append the string */
|
2011-11-09 11:40:27 +04:00
|
|
|
memcpy(tbuf.buf + tbuf.bufpos, buf, len);
|
|
|
|
tbuf.bufpos += len;
|
2010-02-03 18:16:32 +03:00
|
|
|
}
|
2012-06-01 16:08:40 +04:00
|
|
|
free(buf);
|
2010-02-03 18:16:32 +03:00
|
|
|
/* Process the last entry if not done already */
|
2010-02-23 02:05:39 +03:00
|
|
|
process_entry(&tbuf, flags);
|
2012-06-01 16:08:40 +04:00
|
|
|
free(tbuf.buf);
|
2010-02-03 18:16:32 +03:00
|
|
|
|
|
|
|
/* Merge use entries until we have merged all we can */
|
2010-02-23 02:05:39 +03:00
|
|
|
while (merge_use(flags) != 0)
|
2010-02-03 18:16:32 +03:00
|
|
|
;
|
|
|
|
|
2010-02-11 16:09:57 +03:00
|
|
|
if (Sflag) {
|
2010-02-11 03:24:46 +03:00
|
|
|
print_dump(argc - optind, argv + optind);
|
|
|
|
return error_exit;
|
|
|
|
}
|
|
|
|
|
2010-02-11 16:09:57 +03:00
|
|
|
if (cflag)
|
2010-02-03 18:16:32 +03:00
|
|
|
return error_exit;
|
2012-06-01 01:01:06 +04:00
|
|
|
|
2012-06-04 03:19:09 +04:00
|
|
|
if (ofile == NULL)
|
|
|
|
easprintf(&dbname, "%s.cdb", source);
|
|
|
|
else
|
|
|
|
dbname = ofile;
|
|
|
|
write_database(dbname);
|
2010-02-03 18:16:32 +03:00
|
|
|
|
|
|
|
if (sflag != 0)
|
|
|
|
fprintf(stderr, "%zu entries and %zu aliases written to %s\n",
|
2012-06-04 03:19:09 +04:00
|
|
|
nterm, nalias, dbname);
|
2010-02-03 18:16:32 +03:00
|
|
|
|
2012-06-04 03:19:09 +04:00
|
|
|
if (ofile == NULL)
|
|
|
|
free(dbname);
|
|
|
|
while ((term = STAILQ_FIRST(&terms)) != NULL) {
|
|
|
|
STAILQ_REMOVE_HEAD(&terms, next);
|
2012-06-01 16:08:40 +04:00
|
|
|
_ti_freetic(term->tic);
|
|
|
|
free(term->name);
|
|
|
|
free(term);
|
|
|
|
}
|
2017-05-05 15:21:28 +03:00
|
|
|
#ifndef HAVE_NBTOOL_CONFIG_H
|
|
|
|
/*
|
|
|
|
* hdestroy1 is not standard but we don't really care if we
|
|
|
|
* leak in the tools version
|
|
|
|
*/
|
2014-07-21 00:20:16 +04:00
|
|
|
hdestroy1(free, NULL);
|
2017-05-05 15:21:28 +03:00
|
|
|
#endif
|
2012-06-01 16:08:40 +04:00
|
|
|
|
2010-02-03 18:16:32 +03:00
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|