You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2741 lines
70 KiB
C
2741 lines
70 KiB
C
/******************************************************************************
|
|
* file lib/management/vty.c
|
|
* author YuLiang
|
|
* version 1.0.0
|
|
* date 09-Oct-2021
|
|
* brief This file provides all the vty operation functions.
|
|
*
|
|
******************************************************************************
|
|
* Attention
|
|
*
|
|
* <h2><center>© COPYRIGHT(c) 2021 LandPower</center></h2>
|
|
*
|
|
* 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.
|
|
* 3. Neither the name of LandPower nor the names of its contributors may be used to
|
|
* endorse or promote products derived from this software without specific
|
|
* prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
|
|
*
|
|
******************************************************************************/
|
|
|
|
/* Includes ------------------------------------------------------------------*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdarg.h>
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <pthread.h>
|
|
#include <arpa/inet.h>
|
|
#include <arpa/telnet.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <sys/uio.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
|
|
#include "list.h"
|
|
#include "sockunion.h"
|
|
#include "vty.h"
|
|
#include "cmd.h"
|
|
|
|
/* Private define ------------------------------------------------------------*/
|
|
#define BUFFER_SIZE_DEFAULT 4096
|
|
#define BUF_MAX_CHUNKS 16
|
|
#define BUF_MAX_FLUSH 131072
|
|
|
|
/* Private macro -------------------------------------------------------------*/
|
|
#define BUFFER_DATA_FREE(D) XFREE(MTYPE_BUF_DATA, (D))
|
|
|
|
/* Private typedef -----------------------------------------------------------*/
|
|
typedef enum
|
|
{
|
|
BUFFER_ERROR = -1, /* I/O错误产生. */
|
|
BUFFER_EMPTY = 0, /* 数据发送成功, 并且没有其他数据要发送了. */
|
|
BUFFER_PENDING = 1 /* 数据发送成功, 但是数据并没有发送完. */
|
|
} BUF_STATUS_E;
|
|
|
|
/* Private variables ---------------------------------------------------------*/
|
|
static hash_t *cpu_record = NULL;
|
|
static struct timeval last_recent_time;
|
|
static struct timeval relative_time_base;
|
|
struct timeval recent_time;
|
|
/* init flag */
|
|
static unsigned short timers_inited;
|
|
/* Relative time, since startup */
|
|
static struct timeval relative_time;
|
|
|
|
/* Configure lock. */
|
|
static int vty_config;
|
|
|
|
static const char vty_backward_char = 0x08;
|
|
static const char vty_space_char = ' ';
|
|
|
|
thread_master_t *vty_master;
|
|
|
|
/* Vector which store each vty structure. */
|
|
array_t *vtyvec;
|
|
/* VTY server thread. */
|
|
array_t *vty_serv_thread;
|
|
/* user array. */
|
|
array_t *vty_user;
|
|
|
|
/* Vty access-class command */
|
|
static char *vty_accesslist_name = NULL;
|
|
#ifdef HAVE_IPV6
|
|
/* Vty access-calss for IPv6. */
|
|
static char *vty_ipv6_accesslist_name = NULL;
|
|
#endif
|
|
|
|
/* Private function prototypes -----------------------------------------------*/
|
|
|
|
/* Internal functions --------------------------------------------------------*/
|
|
/* 在buf中添加一个data空间. */
|
|
static buf_data_t *_buf_add(buf_t *b)
|
|
{
|
|
buf_data_t *d;
|
|
|
|
d = XMALLOC(MTYPE_BUF_DATA, l_offsetof(buf_data_t, data[b->size]));
|
|
d->cp = d->sp = 0;
|
|
d->next = NULL;
|
|
|
|
if (b->tail)
|
|
b->tail->next = d;
|
|
else
|
|
b->head = d;
|
|
b->tail = d;
|
|
|
|
return d;
|
|
}
|
|
|
|
/* 创建buf. */
|
|
buf_t *buf_create(size_t size)
|
|
{
|
|
buf_t *b = NULL;
|
|
|
|
b = XMALLOC(MTYPE_BUF, sizeof(buf_t));
|
|
|
|
if (size)
|
|
b->size = size;
|
|
else
|
|
{
|
|
static size_t default_size;
|
|
if (!default_size)
|
|
{
|
|
long pgsz = sysconf(_SC_PAGESIZE);
|
|
default_size = (((BUFFER_SIZE_DEFAULT-1)/pgsz+1)*pgsz);
|
|
}
|
|
b->size = default_size;
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
/* 释放所有buf中的空间. */
|
|
void buf_reset(buf_t *b)
|
|
{
|
|
buf_data_t *data = NULL;
|
|
buf_data_t *next = NULL;
|
|
|
|
for (data = b->head; data; data = next)
|
|
{
|
|
next = data->next;
|
|
BUFFER_DATA_FREE(data);
|
|
}
|
|
b->head = b->tail = NULL;
|
|
}
|
|
|
|
/* 释放buf_t结构,包括buf空间. */
|
|
void buf_free(buf_t *b)
|
|
{
|
|
buf_reset(b);
|
|
XFREE(MTYPE_BUF, b);
|
|
}
|
|
|
|
/* 将数据存入buf. */
|
|
void buf_put(buf_t *b, const void *p, size_t size)
|
|
{
|
|
buf_data_t *data = b->tail;
|
|
const char *ptr = p;
|
|
|
|
/* We use even last one byte of data buffer. */
|
|
while(size)
|
|
{
|
|
size_t chunk = 0;
|
|
|
|
/* If there is no data buffer add it. */
|
|
if (NULL == data || data->cp == b->size)
|
|
data = _buf_add(b);
|
|
|
|
chunk = ((size <= (b->size - data->cp)) ? size : (b->size - data->cp));
|
|
memcpy((data->data + data->cp), ptr, chunk);
|
|
size -= chunk;
|
|
ptr += chunk;
|
|
data->cp += chunk;
|
|
}
|
|
}
|
|
|
|
/* 设计用于非阻塞socket. 如果数据太大, 该接口并不会全部发送.
|
|
返回0如果数据全部发送, 1表示还有数据没有发送完, -1表示发送
|
|
产生了致命错误. */
|
|
BUF_STATUS_E buf_flush_available(buf_t *b, int32_t fd)
|
|
{
|
|
buf_data_t *d = NULL;
|
|
size_t written = 0;
|
|
struct iovec iov[BUF_MAX_CHUNKS];
|
|
size_t iovcnt = 0;
|
|
size_t nbyte = 0;
|
|
|
|
/* 将数据填入iov,最多使用BUF_MAX_CHUNKS个iov,并且不能超过
|
|
BUF_MAX_FLUSH字节. */
|
|
for(d = b->head, iovcnt = 0;
|
|
d && (iovcnt < BUF_MAX_CHUNKS) && (nbyte < BUF_MAX_FLUSH);
|
|
d = d->next, iovcnt++)
|
|
{
|
|
iov[iovcnt].iov_base = d->data + d->sp;
|
|
nbyte += (iov[iovcnt].iov_len = d->cp - d->sp);
|
|
}
|
|
|
|
if (!nbyte)
|
|
return BUFFER_EMPTY;
|
|
|
|
/* 数据输出到终端. */
|
|
if ((ssize_t)(written = writev(fd, iov, iovcnt)) < 0)
|
|
{
|
|
if (ERRNO_IO_RETRY(errno))
|
|
return BUFFER_PENDING;
|
|
|
|
printf("%s: write error on fd %d: %s.\n", __FUNCTION__, fd, safe_strerror(errno));
|
|
return BUFFER_ERROR;
|
|
}
|
|
|
|
/* 释放被输出到终端的data. */
|
|
while(written > 0)
|
|
{
|
|
if (!(d = b->head))
|
|
{
|
|
printf("%s: corruption detected: buffer queue empty, but written is %lu", __func__, (u_long)written);
|
|
break;
|
|
}
|
|
|
|
if (written < (d->cp - d->sp))
|
|
{
|
|
d->sp += written;
|
|
return BUFFER_PENDING;
|
|
}
|
|
|
|
written -= (d->cp-d->sp);
|
|
if (!(b->head = d->next))
|
|
b->tail = NULL;
|
|
|
|
BUFFER_DATA_FREE(d);
|
|
}
|
|
|
|
return b->head ? BUFFER_PENDING : BUFFER_EMPTY;
|
|
}
|
|
|
|
/* 发送数据到终端,仅用于telnet接口 */
|
|
BUF_STATUS_E buf_flush_window(buf_t *b, int fd, int32_t width, int32_t height,
|
|
int32_t erase_flag, int32_t no_more_flag)
|
|
{
|
|
int32_t nbytes = 0;
|
|
int32_t iov_alloc = 0;
|
|
int32_t iov_index = 0;
|
|
struct iovec *iov = NULL;
|
|
struct iovec small_iov[3];
|
|
char more[] = " --More-- ";
|
|
char erase[] = {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
|
|
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
|
|
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08};
|
|
buf_data_t *data = NULL;
|
|
uint32_t column = 0;
|
|
|
|
if (!b->head)
|
|
return BUFFER_EMPTY;
|
|
|
|
/* 获取显示的高度与宽度. */
|
|
if (height < 1)
|
|
{
|
|
printf("%s called with non-positive window height %d, forcing to 1", __func__, height);
|
|
height = 1;
|
|
}
|
|
else if (height >= 2)
|
|
height--;
|
|
|
|
if (width < 1)
|
|
{
|
|
printf("%s called with non-positive window width %d, forcing to 1", __func__, width);
|
|
width = 1;
|
|
}
|
|
|
|
/* 计算发送一屏的数据要使用多少数据块. */
|
|
if (NULL == b->head->next)
|
|
{
|
|
iov_alloc = sizeof(small_iov)/sizeof(small_iov[0]);
|
|
iov = small_iov;
|
|
}
|
|
else
|
|
{
|
|
iov_alloc = ((height * (width + 2)) / b->size) + 10;
|
|
iov = XMALLOC(MTYPE_BUF_TMP, iov_alloc * sizeof(*iov));
|
|
}
|
|
iov_index = 0;
|
|
|
|
/* 处理清屏标志. */
|
|
if (erase_flag)
|
|
{
|
|
iov[iov_index].iov_base = erase;
|
|
iov[iov_index].iov_len = sizeof(erase);
|
|
iov_index++;
|
|
}
|
|
|
|
/* 整理发送数据. */
|
|
column = 1; /* 下一个字符显示的列地址. */
|
|
for (data = b->head; data && (height > 0); data = data->next)
|
|
{
|
|
size_t cp;
|
|
|
|
/* 计算数据位置和行数,保证只显示height行(一屏)数据. */
|
|
cp = data->sp;
|
|
while ((cp < data->cp) && (height > 0))
|
|
{
|
|
if ('\r' == data->data[cp])
|
|
column = 1;
|
|
else if (('\n' == data->data[cp]) || (column == width))
|
|
{
|
|
column = 1;
|
|
height--;
|
|
}
|
|
else
|
|
column++;
|
|
|
|
cp++;
|
|
}
|
|
iov[iov_index].iov_base = (char *)(data->data + data->sp);
|
|
iov[iov_index++].iov_len = cp - data->sp;
|
|
data->sp = cp;
|
|
|
|
/* 以下情况应该不会发生,申请的块应该是足够发送数据的. */
|
|
if (iov_index == iov_alloc)
|
|
{
|
|
iov_alloc *= 2;
|
|
if (iov != small_iov)
|
|
{
|
|
printf("%s: growing iov array to %d;width %d, height %d, size %lu",
|
|
__func__, iov_alloc, width, height, (u_long)b->size);
|
|
iov = XREALLOC(MTYPE_BUF_TMP, iov, iov_alloc * sizeof(*iov));
|
|
}
|
|
else
|
|
{
|
|
printf("%s: corruption detected: iov_small overflowed;head %p, tail %p, head->next %p",
|
|
__func__, b->head, b->tail, b->head->next);
|
|
iov = XMALLOC(MTYPE_BUF_TMP, iov_alloc * sizeof(*iov));
|
|
memcpy(iov, small_iov, sizeof(small_iov));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 显示" --More-- "提示. */
|
|
if (b->tail && (b->tail->sp < b->tail->cp) && !no_more_flag)
|
|
{
|
|
iov[iov_index].iov_base = more;
|
|
iov[iov_index].iov_len = sizeof(more);
|
|
iov_index++;
|
|
}
|
|
|
|
/* 发送数据. */
|
|
if ((nbytes = writev(fd, iov, iov_index)) < 0)
|
|
printf("%s: writev to fd %d failed: %s", __func__, fd, safe_strerror(errno));
|
|
|
|
/* 释放发送过的数据空间. */
|
|
while (b->head && (b->head->sp == b->head->cp))
|
|
{
|
|
buf_data_t *del = NULL;
|
|
|
|
if (!(b->head = (del = b->head)->next))
|
|
b->tail = NULL;
|
|
|
|
BUFFER_DATA_FREE(del);
|
|
}
|
|
|
|
if (iov != small_iov)
|
|
XFREE(MTYPE_BUF_TMP, iov);
|
|
|
|
return (nbytes < 0) ? BUFFER_ERROR : (b->head ? BUFFER_PENDING : BUFFER_EMPTY);
|
|
}
|
|
|
|
/* 持续发送数据, 直到数据发送完, 或者遇到错误,或者被拥塞. */
|
|
BUF_STATUS_E buf_flush_all(buf_t *b, int32_t fd)
|
|
{
|
|
BUF_STATUS_E ret = BUFFER_ERROR;
|
|
buf_data_t *head = NULL;
|
|
size_t head_sp = 0;
|
|
|
|
if (!b->head)
|
|
return BUFFER_EMPTY;
|
|
|
|
head_sp = (head = b->head)->sp;
|
|
|
|
while(BUFFER_PENDING == (ret = buf_flush_available(b, fd)))
|
|
{
|
|
/* 数据不空, 但是没有发送出去,可能是内核buf满了. */
|
|
if ((b->head == head) && (head_sp == head->sp) && (errno != EINTR))
|
|
return ret;
|
|
|
|
head_sp = (head = b->head)->sp;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Lookup and return hash backet in hash. If there is no
|
|
* corresponding hash backet and alloc_func is specified, create new
|
|
* hash backet. */
|
|
void *hash_get(hash_t *hash, void *data, hash_alloc_f alloc_func)
|
|
{
|
|
unsigned int key = 0;
|
|
unsigned int index = 0;
|
|
void *newdata = 0;
|
|
hash_backet_t *backet = NULL;
|
|
|
|
key = (*hash->hash_key) (data);
|
|
index = key % hash->size;
|
|
|
|
for(backet = hash->index[index]; backet != NULL; backet = backet->next)
|
|
if (backet->key == key && (*hash->hash_cmp) (backet->data, data))
|
|
return backet->data;
|
|
|
|
if (alloc_func)
|
|
{
|
|
newdata = (*alloc_func)(data);
|
|
if (NULL == newdata)
|
|
return NULL;
|
|
|
|
backet = XMALLOC(MTYPE_HASH_BACKET, sizeof(hash_backet_t));
|
|
backet->data = newdata;
|
|
backet->key = key;
|
|
backet->next = hash->index[index];
|
|
hash->index[index] = backet;
|
|
hash->count++;
|
|
return backet->data;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* gettimeofday wrapper, to keep recent_time updated */
|
|
static int _thread_gettimeofday(struct timeval *tv)
|
|
{
|
|
int ret = 0;
|
|
|
|
assert(tv);
|
|
|
|
if (!(ret = gettimeofday(&recent_time, NULL)))
|
|
{
|
|
/* init... */
|
|
if (!timers_inited)
|
|
{
|
|
relative_time_base = last_recent_time = recent_time;
|
|
timers_inited = 1;
|
|
}
|
|
/* avoid copy if user passed recent_time pointer.. */
|
|
if (tv != &recent_time)
|
|
*tv = recent_time;
|
|
return 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int _thread_get_relative(struct timeval *tv)
|
|
{
|
|
int ret = 0;
|
|
struct timespec tp;
|
|
|
|
if (!(ret = clock_gettime(CLOCK_MONOTONIC, &tp)))
|
|
{
|
|
relative_time.tv_sec = tp.tv_sec;
|
|
relative_time.tv_usec = tp.tv_nsec / 1000;
|
|
}
|
|
|
|
if (tv)
|
|
*tv = relative_time;
|
|
|
|
return ret;
|
|
}
|
|
/* Adjust so that tv_usec is in the range [0,TIMER_SECOND_MICRO).
|
|
And change negative values to 0. */
|
|
static struct timeval _thread_timeval_adjust (struct timeval a)
|
|
{
|
|
while(a.tv_usec >= TIMER_SECOND_MICRO)
|
|
{
|
|
a.tv_usec -= TIMER_SECOND_MICRO;
|
|
a.tv_sec++;
|
|
}
|
|
|
|
while(a.tv_usec < 0)
|
|
{
|
|
a.tv_usec += TIMER_SECOND_MICRO;
|
|
a.tv_sec--;
|
|
}
|
|
|
|
if (a.tv_sec < 0)
|
|
/* Change negative timeouts to 0. */
|
|
a.tv_sec = a.tv_usec = 0;
|
|
|
|
return a;
|
|
}
|
|
|
|
static unsigned long _thread_timeval_elapsed(struct timeval a, struct timeval b)
|
|
{
|
|
return ((a.tv_sec - b.tv_sec) * TIMER_SECOND_MICRO) +
|
|
(a.tv_usec - b.tv_usec);
|
|
}
|
|
|
|
/* Allocate a new hash. */
|
|
hash_t *_thread_hash_create_size(hash_key_f *hash_key,
|
|
hash_cmp_f *hash_cmp,
|
|
unsigned int size)
|
|
{
|
|
hash_t *hash;
|
|
|
|
hash = XMALLOC(MTYPE_HASH, sizeof(hash_t));
|
|
hash->index = XMALLOC(MTYPE_HASH_INDEX, sizeof(hash_backet_t *) * size);
|
|
hash->size = size;
|
|
hash->hash_key = hash_key;
|
|
hash->hash_cmp = hash_cmp;
|
|
hash->count = 0;
|
|
|
|
return hash;
|
|
}
|
|
|
|
/* Add a new thread to the list. */
|
|
static void _thread_list_enqueue(thread_list_t *list, thread_t *thread)
|
|
{
|
|
thread->next = NULL;
|
|
thread->prev = list->tail;
|
|
if (list->tail)
|
|
list->tail->next = thread;
|
|
else
|
|
list->head = thread;
|
|
|
|
list->tail = thread;
|
|
list->count++;
|
|
}
|
|
|
|
/* Delete a thread from the list. */
|
|
static thread_t *_thread_list_dequeue(thread_list_t *list, thread_t *thread)
|
|
{
|
|
if (thread->next)
|
|
thread->next->prev = thread->prev;
|
|
else
|
|
list->tail = thread->prev;
|
|
|
|
if (thread->prev)
|
|
thread->prev->next = thread->next;
|
|
else
|
|
list->head = thread->next;
|
|
|
|
thread->next = thread->prev = NULL;
|
|
list->count--;
|
|
|
|
return thread;
|
|
}
|
|
|
|
/* Thread list is empty or not. */
|
|
static inline int _thread_empty(thread_list_t *list)
|
|
{
|
|
return list->head ? 0 : 1;
|
|
}
|
|
|
|
static long _thread_timeval_cmp(struct timeval a, struct timeval b)
|
|
{
|
|
return a.tv_sec == b.tv_sec ?
|
|
a.tv_usec - b.tv_usec : a.tv_sec - b.tv_sec;
|
|
}
|
|
|
|
/* Delete top of the list and return it. */
|
|
static thread_t *_thread_list_pull(thread_list_t *list)
|
|
{
|
|
if (!_thread_empty(list))
|
|
return _thread_list_dequeue(list, list->head);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Add a new thread to the list. */
|
|
static void _thread_list_add(thread_list_t *list,
|
|
thread_t *thread)
|
|
{
|
|
thread->next = NULL;
|
|
thread->prev = list->tail;
|
|
|
|
if (list->tail)
|
|
list->tail->next = thread;
|
|
else
|
|
list->head = thread;
|
|
|
|
list->tail = thread;
|
|
list->count++;
|
|
}
|
|
|
|
/* Add a new thread just before the point. */
|
|
static void _thread_list_add_before(thread_list_t *list,
|
|
thread_t *point,
|
|
thread_t *thread)
|
|
{
|
|
thread->next = point;
|
|
thread->prev = point->prev;
|
|
if (point->prev)
|
|
point->prev->next = thread;
|
|
else
|
|
list->head = thread;
|
|
|
|
point->prev = thread;
|
|
list->count++;
|
|
}
|
|
|
|
/* Return remain time in second. */
|
|
unsigned long _thread_timer_remain_second(thread_t *thread)
|
|
{
|
|
_thread_get_relative(NULL);
|
|
|
|
if (thread->u.sands.tv_sec - relative_time.tv_sec > 0)
|
|
return thread->u.sands.tv_sec - relative_time.tv_sec;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* Trim blankspace and "()"s */
|
|
static char *_thread_strip_funcname(const char *funcname)
|
|
{
|
|
char buff[100] = {0};
|
|
char tmp = 0;
|
|
char *ret = NULL;
|
|
char *e = NULL;
|
|
char *b = buff;
|
|
|
|
strncpy(buff, funcname, sizeof(buff));
|
|
buff[sizeof(buff) -1] = '\0';
|
|
e = buff + strlen(buff) - 1;
|
|
|
|
/* Wont work for funcname == "Word (explanation)" */
|
|
while (*b == ' ' || *b == '(')
|
|
++b;
|
|
while (*e == ' ' || *e == ')')
|
|
--e;
|
|
e++;
|
|
|
|
tmp = *e;
|
|
*e = '\0';
|
|
ret = XSTRDUP(MTYPE_THREAD_FUNCNAME, b);
|
|
*e = tmp;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Get new thread. */
|
|
static thread_t *_thread_get(thread_master_t *m,
|
|
u_char type,
|
|
thread_func_f *func,
|
|
void *arg,
|
|
const char* funcname)
|
|
{
|
|
thread_t *thread = NULL;
|
|
|
|
if (!_thread_empty(&m->unuse))
|
|
{
|
|
thread = _thread_list_pull(&m->unuse);
|
|
if (thread->funcname)
|
|
XFREE(MTYPE_THREAD_FUNCNAME, thread->funcname);
|
|
}
|
|
else
|
|
{
|
|
thread = XMALLOC(MTYPE_THREAD, sizeof(thread_t));
|
|
m->alloc++;
|
|
}
|
|
|
|
thread->type = type;
|
|
thread->add_type = type;
|
|
thread->master = m;
|
|
thread->func = func;
|
|
thread->arg = arg;
|
|
thread->funcname = _thread_strip_funcname(funcname);
|
|
|
|
return thread;
|
|
}
|
|
|
|
static void *_thread_cpu_record_hash_alloc(cpu_thread_history_t *a)
|
|
{
|
|
cpu_thread_history_t *history = NULL;
|
|
history = XMALLOC(MTYPE_THREAD_STATS, sizeof(cpu_thread_history_t));
|
|
history->func = a->func;
|
|
history->funcname = XSTRDUP(MTYPE_THREAD_FUNCNAME, a->funcname);
|
|
return history;
|
|
}
|
|
|
|
/* Move thread to unuse list. */
|
|
static void _thread_add_unuse(thread_master_t *m, thread_t *thread)
|
|
{
|
|
assert(m != NULL && thread != NULL);
|
|
assert(NULL == thread->next);
|
|
assert(NULL == thread->prev);
|
|
assert(THREAD_UNUSED == thread->type);
|
|
|
|
_thread_list_add(&m->unuse, thread);
|
|
/* XXX: Should we deallocate funcname here? */
|
|
}
|
|
|
|
static unsigned int _thread_cpu_record_hash_key(cpu_thread_history_t *a)
|
|
{
|
|
return (uintptr_t)a->func;
|
|
}
|
|
|
|
static int _thread_cpu_record_hash_cmp (const cpu_thread_history_t *a,
|
|
const cpu_thread_history_t *b)
|
|
{
|
|
return a->func == b->func;
|
|
}
|
|
|
|
static thread_t *_thread_run(thread_master_t *m, thread_t *thread, thread_t *fetch)
|
|
{
|
|
*fetch = *thread;
|
|
thread->type = THREAD_UNUSED;
|
|
thread->funcname = NULL; /* thread_call will free fetch's copied pointer */
|
|
_thread_add_unuse(m, thread);
|
|
return fetch;
|
|
}
|
|
|
|
/* process a list en masse, e.g. for event thread lists */
|
|
static unsigned int _thread_process(thread_list_t *list)
|
|
{
|
|
thread_t *thread = NULL;
|
|
unsigned int ready = 0;
|
|
|
|
for(thread = list->head; thread; thread = thread->next)
|
|
{
|
|
_thread_list_dequeue(list, thread);
|
|
thread->type = THREAD_READY;
|
|
_thread_list_add(&thread->master->ready, thread);
|
|
ready++;
|
|
}
|
|
|
|
return ready;
|
|
}
|
|
|
|
static struct timeval _timeval_subtract(struct timeval a, struct timeval b)
|
|
{
|
|
struct timeval ret;
|
|
|
|
ret.tv_usec = a.tv_usec - b.tv_usec;
|
|
ret.tv_sec = a.tv_sec - b.tv_sec;
|
|
|
|
return _thread_timeval_adjust(ret);
|
|
}
|
|
|
|
static struct timeval *_thread_timer_wait(thread_list_t *tlist, struct timeval *timer_val)
|
|
{
|
|
if (!_thread_empty (tlist))
|
|
{
|
|
*timer_val = _timeval_subtract(tlist->head->u.sands, relative_time);
|
|
return timer_val;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Add all timers that have popped to the ready list. */
|
|
static uint32_t _thread_timer_process(thread_list_t *list, struct timeval *timenow)
|
|
{
|
|
thread_t *thread = NULL;
|
|
uint32_t ready = 0;
|
|
|
|
for (thread = list->head; thread; thread = thread->next)
|
|
{
|
|
if (_thread_timeval_cmp(*timenow, thread->u.sands) < 0)
|
|
return ready;
|
|
|
|
_thread_list_dequeue(list, thread);
|
|
thread->type = THREAD_READY;
|
|
_thread_list_add(&thread->master->ready, thread);
|
|
ready++;
|
|
}
|
|
|
|
return ready;
|
|
}
|
|
|
|
static int32_t _thread_process_fd(thread_list_t *list, fd_set *fdset, fd_set *mfdset)
|
|
{
|
|
thread_t *thread = NULL;
|
|
thread_t *next = NULL;
|
|
int ready = 0;
|
|
|
|
assert(list);
|
|
|
|
for (thread = list->head; thread; thread = next)
|
|
{
|
|
next = thread->next;
|
|
|
|
if (FD_ISSET(THREAD_FD(thread), fdset))
|
|
{
|
|
assert(FD_ISSET(THREAD_FD(thread), mfdset));
|
|
FD_CLR(THREAD_FD (thread), mfdset);
|
|
_thread_list_dequeue(list, thread);
|
|
_thread_list_add(&thread->master->ready, thread);
|
|
thread->type = THREAD_READY;
|
|
ready++;
|
|
}
|
|
}
|
|
|
|
return ready;
|
|
}
|
|
|
|
void thread_getrusage(RUSAGE_T *r)
|
|
{
|
|
_thread_get_relative(NULL);
|
|
getrusage(RUSAGE_SELF, &(r->cpu));
|
|
r->real = relative_time;
|
|
|
|
/* quagga_get_relative() only updates recent_time if gettimeofday
|
|
* based, not when using CLOCK_MONOTONIC. As we export recent_time
|
|
* and guarantee to update it before threads are run...*/
|
|
_thread_gettimeofday(&recent_time);
|
|
}
|
|
|
|
/* We check thread consumed time. If the system has getrusage, we'll
|
|
use that to get in-depth stats on the performance of the thread in addition
|
|
to wall clock time stats from gettimeofday. */
|
|
void thread_call(thread_t *thread)
|
|
{
|
|
unsigned long realtime = 0;
|
|
unsigned long cputime = 0;
|
|
RUSAGE_T ru;
|
|
|
|
/* Cache a pointer to the relevant cpu history thread, if the thread
|
|
* does not have it yet.
|
|
*
|
|
* Callers submitting 'dummy threads' hence must take care that
|
|
* thread->cpu is NULL */
|
|
if (!thread->hist)
|
|
{
|
|
cpu_thread_history_t tmp;
|
|
|
|
tmp.func = (int (*)(void*))thread->func;
|
|
tmp.funcname = thread->funcname;
|
|
|
|
thread->hist = hash_get(cpu_record,
|
|
&tmp,
|
|
(hash_alloc_f *)_thread_cpu_record_hash_alloc);
|
|
}
|
|
|
|
GETRUSAGE(&thread->ru);
|
|
|
|
(*thread->func)(thread);
|
|
|
|
GETRUSAGE(&ru);
|
|
|
|
realtime = thread_consumed_time(&ru, &thread->ru, &cputime);
|
|
thread->hist->real.total += realtime;
|
|
|
|
if (thread->hist->real.max < realtime)
|
|
thread->hist->real.max = realtime;
|
|
|
|
thread->hist->cpu.total += cputime;
|
|
|
|
if (thread->hist->cpu.max < cputime)
|
|
thread->hist->cpu.max = cputime;
|
|
|
|
++(thread->hist->total_calls);
|
|
thread->hist->types |= (1 << thread->add_type);
|
|
|
|
if (realtime > TIMER_SECOND_MICRO * 2)
|
|
{
|
|
/* We have a CPU Hog on our hands.
|
|
* Whinge about it now, so we're aware this is yet another task
|
|
* to fix. */
|
|
printf("SLOW THREAD: task %s (%lx) ran for %lums (cpu time %lums)",
|
|
thread->funcname,
|
|
(unsigned long) thread->func,
|
|
realtime/1000, cputime/1000);
|
|
}
|
|
|
|
XFREE(MTYPE_THREAD_FUNCNAME, thread->funcname);
|
|
}
|
|
|
|
/* Add new read thread. */
|
|
thread_t *thread_add_read(thread_master_t *m,
|
|
thread_func_f *func,
|
|
void *arg,
|
|
int fd,
|
|
const char* funcname)
|
|
{
|
|
thread_t *thread = NULL;
|
|
|
|
assert(m != NULL);
|
|
|
|
if (FD_ISSET(fd, &m->readfd))
|
|
{
|
|
printf("There is already read fd [%d].\n", fd);
|
|
return NULL;
|
|
}
|
|
|
|
thread = _thread_get(m, THREAD_READ, func, arg, funcname);
|
|
FD_SET(fd, &m->readfd);
|
|
thread->u.fd = fd;
|
|
_thread_list_enqueue(&m->read, thread);
|
|
|
|
return thread;
|
|
}
|
|
|
|
/* Add new write thread. */
|
|
thread_t *thread_add_write(thread_master_t *m,
|
|
thread_func_f *func,
|
|
void *arg,
|
|
int fd,
|
|
const char* funcname)
|
|
{
|
|
thread_t *thread = NULL;
|
|
|
|
assert(m != NULL);
|
|
|
|
if (FD_ISSET(fd, &m->writefd))
|
|
{
|
|
printf("There is already write fd [%d]", fd);
|
|
return NULL;
|
|
}
|
|
|
|
thread = _thread_get(m, THREAD_WRITE, func, arg, funcname);
|
|
FD_SET(fd, &m->writefd);
|
|
thread->u.fd = fd;
|
|
_thread_list_add(&m->write, thread);
|
|
|
|
return thread;
|
|
}
|
|
|
|
static thread_t *
|
|
thread_add_timer_timeval(thread_master_t *m,
|
|
thread_func_f *func,
|
|
int type,
|
|
void *arg,
|
|
struct timeval *time_relative,
|
|
const char* funcname)
|
|
{
|
|
thread_t *thread = NULL;
|
|
thread_list_t *list = NULL;
|
|
struct timeval alarm_time;
|
|
thread_t *tt = NULL;
|
|
|
|
assert(m != NULL);
|
|
assert(type == THREAD_TIMER || type == THREAD_BACKGROUND);
|
|
assert(time_relative);
|
|
|
|
list = (type == THREAD_TIMER) ? &m->timer : &m->background;
|
|
thread = _thread_get(m, type, func, arg, funcname);
|
|
|
|
/* Do we need jitter here? */
|
|
_thread_get_relative(NULL);
|
|
alarm_time.tv_sec = relative_time.tv_sec + time_relative->tv_sec;
|
|
alarm_time.tv_usec = relative_time.tv_usec + time_relative->tv_usec;
|
|
thread->u.sands = _thread_timeval_adjust(alarm_time);
|
|
|
|
/* Sort by timeval. */
|
|
for(tt = list->head; tt; tt = tt->next)
|
|
if (_thread_timeval_cmp(thread->u.sands, tt->u.sands) <= 0)
|
|
break;
|
|
|
|
if (tt)
|
|
_thread_list_add_before(list, tt, thread);
|
|
else
|
|
_thread_list_add(list, thread);
|
|
|
|
return thread;
|
|
}
|
|
|
|
|
|
/* Add timer event thread. */
|
|
thread_t *thread_add_timer(thread_master_t *m,
|
|
thread_func_f *func,
|
|
void *arg,
|
|
long timer,
|
|
const char* funcname)
|
|
{
|
|
struct timeval trel;
|
|
|
|
assert(m != NULL);
|
|
|
|
trel.tv_sec = timer;
|
|
trel.tv_usec = 0;
|
|
|
|
return thread_add_timer_timeval(m, func, THREAD_TIMER, arg,
|
|
&trel, funcname);
|
|
}
|
|
|
|
/* Add timer event thread with "millisecond" resolution */
|
|
thread_t *thread_add_timer_msec(thread_master_t *m,
|
|
thread_func_f *func,
|
|
void *arg,
|
|
long timer,
|
|
const char* funcname)
|
|
{
|
|
struct timeval trel;
|
|
|
|
assert(m != NULL);
|
|
|
|
trel.tv_sec = timer / 1000;
|
|
trel.tv_usec = 1000*(timer % 1000);
|
|
|
|
return thread_add_timer_timeval(m, func, THREAD_TIMER,
|
|
arg, &trel, funcname);
|
|
}
|
|
|
|
/* Add a background thread, with an optional millisec delay */
|
|
thread_t *thread_add_background(thread_master_t *m,
|
|
thread_func_f *func,
|
|
void *arg, long delay,
|
|
const char *funcname)
|
|
{
|
|
struct timeval trel;
|
|
|
|
assert(m != NULL);
|
|
|
|
if (delay)
|
|
{
|
|
trel.tv_sec = delay / 1000;
|
|
trel.tv_usec = 1000 * (delay % 1000);
|
|
}
|
|
else
|
|
{
|
|
trel.tv_sec = 0;
|
|
trel.tv_usec = 0;
|
|
}
|
|
|
|
return thread_add_timer_timeval(m, func, THREAD_BACKGROUND,
|
|
arg, &trel, funcname);
|
|
}
|
|
|
|
/* Add simple event thread. */
|
|
thread_t *thread_add_event(thread_master_t *m,
|
|
thread_func_f *func,
|
|
void *arg,
|
|
int val,
|
|
const char* funcname)
|
|
{
|
|
thread_t *thread = NULL;
|
|
|
|
assert(m != NULL);
|
|
|
|
thread = _thread_get(m, THREAD_EVENT, func, arg, funcname);
|
|
thread->u.val = val;
|
|
_thread_list_add(&m->event, thread);
|
|
|
|
return thread;
|
|
}
|
|
|
|
/* Execute thread */
|
|
thread_t *thread_execute(thread_master_t *m,
|
|
thread_func_f *func,
|
|
void *arg,
|
|
int val,
|
|
const char* funcname)
|
|
{
|
|
thread_t dummy;
|
|
|
|
memset(&dummy, 0, sizeof(thread_t));
|
|
|
|
dummy.type = THREAD_EVENT;
|
|
dummy.add_type = THREAD_PERFORM;
|
|
dummy.master = NULL;
|
|
dummy.func = func;
|
|
dummy.arg = arg;
|
|
dummy.u.val = val;
|
|
dummy.funcname = _thread_strip_funcname(funcname);
|
|
thread_call(&dummy);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate new thread master. */
|
|
thread_master_t *thread_master_create()
|
|
{
|
|
if (cpu_record == NULL)
|
|
cpu_record
|
|
= _thread_hash_create_size((hash_key_f*)_thread_cpu_record_hash_key,
|
|
(hash_cmp_f*)_thread_cpu_record_hash_cmp,
|
|
1011);
|
|
|
|
return (thread_master_t*)XMALLOC(MTYPE_THREAD_MASTER, sizeof(thread_master_t));
|
|
}
|
|
|
|
unsigned long thread_consumed_time(RUSAGE_T *now,
|
|
RUSAGE_T *start,
|
|
unsigned long *cputime)
|
|
{
|
|
/* This is 'user + sys' time. */
|
|
*cputime = _thread_timeval_elapsed(now->cpu.ru_utime, start->cpu.ru_utime) +
|
|
_thread_timeval_elapsed(now->cpu.ru_stime, start->cpu.ru_stime);
|
|
|
|
return _thread_timeval_elapsed(now->real, start->real);
|
|
}
|
|
|
|
/* Cancel thread from scheduler. */
|
|
void thread_cancel(thread_t *thread)
|
|
{
|
|
thread_list_t *list = NULL;
|
|
|
|
switch(thread->type)
|
|
{
|
|
case THREAD_READ:
|
|
assert(FD_ISSET(thread->u.fd, &thread->master->readfd));
|
|
FD_CLR(thread->u.fd, &thread->master->readfd);
|
|
list = &thread->master->read;
|
|
break;
|
|
case THREAD_WRITE:
|
|
assert(FD_ISSET (thread->u.fd, &thread->master->writefd));
|
|
FD_CLR(thread->u.fd, &thread->master->writefd);
|
|
list = &thread->master->write;
|
|
break;
|
|
case THREAD_TIMER:
|
|
list = &thread->master->timer;
|
|
break;
|
|
case THREAD_EVENT:
|
|
list = &thread->master->event;
|
|
break;
|
|
case THREAD_READY:
|
|
list = &thread->master->ready;
|
|
break;
|
|
case THREAD_BACKGROUND:
|
|
list = &thread->master->background;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
_thread_list_dequeue(list, thread);
|
|
thread->type = THREAD_UNUSED;
|
|
_thread_add_unuse(thread->master, thread);
|
|
}
|
|
|
|
/* Fetch next ready thread. */
|
|
thread_t *thread_fetch(thread_master_t *m, thread_t *fetch)
|
|
{
|
|
thread_t *thread = NULL;
|
|
fd_set readfd;
|
|
fd_set writefd;
|
|
fd_set exceptfd;
|
|
struct timeval timer_val = {.tv_sec = 0, .tv_usec = 0};
|
|
struct timeval timer_val_bg;
|
|
struct timeval *timer_wait = &timer_val;
|
|
struct timeval *timer_wait_bg = NULL;
|
|
|
|
while (1)
|
|
{
|
|
int32_t num = 0;
|
|
|
|
/* Drain the ready queue of already scheduled jobs, before scheduling
|
|
* more. */
|
|
if ((thread = _thread_list_pull(&m->ready)) != NULL)
|
|
return _thread_run(m, thread, fetch);
|
|
|
|
/* To be fair to all kinds of threads, and avoid starvation, we
|
|
* need to be careful to consider all thread types for scheduling
|
|
* in each quanta. I.e. we should not return early from here on. */
|
|
|
|
/* Normal event are the next highest priority. */
|
|
_thread_process(&m->event);
|
|
|
|
/* Structure copy. */
|
|
readfd = m->readfd;
|
|
writefd = m->writefd;
|
|
exceptfd = m->exceptfd;
|
|
|
|
/* Calculate select wait timer if nothing else to do */
|
|
if (0 == m->ready.count)
|
|
{
|
|
_thread_get_relative(NULL);
|
|
timer_wait = _thread_timer_wait(&m->timer, &timer_val);
|
|
timer_wait_bg = _thread_timer_wait(&m->background, &timer_val_bg);
|
|
|
|
if (timer_wait_bg &&
|
|
(!timer_wait || (_thread_timeval_cmp(*timer_wait, *timer_wait_bg) > 0)))
|
|
timer_wait = timer_wait_bg;
|
|
}
|
|
|
|
/* 当可用的进程全为0时,不能让timer_wait为NULL,要不让该线程将永远停在select()处. */
|
|
if (!timer_wait)
|
|
{
|
|
timer_val.tv_sec = 2;
|
|
timer_val.tv_usec = 0;
|
|
timer_wait = &timer_val;
|
|
}
|
|
|
|
num = select(FD_SETSIZE, &readfd, &writefd, &exceptfd, timer_wait);
|
|
/* Signals should get quick treatment */
|
|
if (num < 0)
|
|
{
|
|
if (EINTR == errno)
|
|
continue; /* signal received - process it */
|
|
|
|
printf("select() error: %s\n", safe_strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
/* Check foreground timers. Historically, they have had higher
|
|
* priority than I/O threads, so let's push them onto the ready
|
|
* list in front of the I/O threads. */
|
|
_thread_get_relative(NULL);
|
|
_thread_timer_process(&m->timer, &relative_time);
|
|
|
|
/* Got IO, process it */
|
|
if (num > 0)
|
|
{
|
|
/* Normal priority read thead. */
|
|
_thread_process_fd(&m->read, &readfd, &m->readfd);
|
|
/* Write thead. */
|
|
_thread_process_fd(&m->write, &writefd, &m->writefd);
|
|
}
|
|
|
|
#if 0
|
|
/* If any threads were made ready above (I/O or foreground timer),
|
|
* perhaps we should avoid adding background timers to the ready
|
|
* list at this time. If this is code is uncommented, then background
|
|
* timer threads will not run unless there is nothing else to do. */
|
|
if ((thread = thread_trim_head (&m->ready)) != NULL)
|
|
return thread_run (m, thread, fetch);
|
|
#endif
|
|
|
|
/* Background timer/events, lowest priority */
|
|
_thread_timer_process(&m->background, &relative_time);
|
|
|
|
if ((thread = _thread_list_pull(&m->ready)) != NULL)
|
|
return _thread_run(m, thread, fetch);
|
|
}
|
|
}
|
|
|
|
/* Get telnet window size. */
|
|
static int _vty_telnet_option(vty_t *vty, unsigned char *buf, int nbytes)
|
|
{
|
|
switch (buf[0])
|
|
{
|
|
case SB:
|
|
vty->sb_len = 0;
|
|
vty->iac_sb_in_progress = 1;
|
|
return 0;
|
|
break;
|
|
case SE:
|
|
{
|
|
if (!vty->iac_sb_in_progress)
|
|
return 0;
|
|
|
|
if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0'))
|
|
{
|
|
vty->iac_sb_in_progress = 0;
|
|
return 0;
|
|
}
|
|
switch (vty->sb_buf[0])
|
|
{
|
|
case TELOPT_NAWS:
|
|
if (vty->sb_len != TELNET_NAWS_SB_LEN)
|
|
printf("RFC 1073 violation detected: telnet NAWS option\
|
|
should send %d characters, but we received %lu.\n",
|
|
TELNET_NAWS_SB_LEN, (u_long)vty->sb_len);
|
|
else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN)
|
|
printf("Bug detected: sizeof(vty->sb_buf) %lu < %d, \
|
|
too small to handle the telnet NAWS option.\n",
|
|
(u_long)sizeof(vty->sb_buf), TELNET_NAWS_SB_LEN);
|
|
else
|
|
{
|
|
vty->width = ((vty->sb_buf[1] << 8)|vty->sb_buf[2]);
|
|
vty->height = ((vty->sb_buf[3] << 8)|vty->sb_buf[4]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
vty->iac_sb_in_progress = 0;
|
|
return 0;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Basic function to write buffer to vty. */
|
|
static void _vty_write(vty_t *vty, const char *buf, size_t nbytes)
|
|
{
|
|
/* Should we do buffering here ? And make vty_flush (vty) ? */
|
|
buf_put(vty->out_buf, buf, nbytes);
|
|
}
|
|
|
|
/* This function redraw all of the command line character. */
|
|
static void _vty_redraw_line(vty_t *vty)
|
|
{
|
|
_vty_write(vty, vty->buf, vty->length);
|
|
vty->cp = vty->length;
|
|
}
|
|
|
|
/* Quit print out to the buffer. */
|
|
static void _vty_buffer_reset(vty_t *vty)
|
|
{
|
|
buf_reset(vty->out_buf);
|
|
vty_prompt(vty);
|
|
_vty_redraw_line(vty);
|
|
}
|
|
|
|
/* Backward character. */
|
|
static void _vty_backward_char(vty_t *vty)
|
|
{
|
|
if (vty->cp > 0)
|
|
{
|
|
vty->cp--;
|
|
_vty_write(vty, &vty_backward_char, 1);
|
|
}
|
|
}
|
|
|
|
/* Forward character. */
|
|
static void _vty_forward_char(vty_t *vty)
|
|
{
|
|
if (vty->cp < vty->length)
|
|
{
|
|
_vty_write(vty, &vty->buf[vty->cp], 1);
|
|
vty->cp++;
|
|
}
|
|
}
|
|
|
|
/* Move to the beginning of the line. */
|
|
static void _vty_beginning_of_line(vty_t *vty)
|
|
{
|
|
while (vty->cp)
|
|
_vty_backward_char(vty);
|
|
}
|
|
|
|
/* Move to the end of the line. */
|
|
static void _vty_end_of_line(vty_t *vty)
|
|
{
|
|
while(vty->cp < vty->length)
|
|
_vty_forward_char(vty);
|
|
}
|
|
|
|
/* Kill rest of line from current point. */
|
|
static void _vty_kill_line(vty_t *vty)
|
|
{
|
|
unsigned int i = 0;
|
|
unsigned int size = 0;
|
|
|
|
size = vty->length - vty->cp;
|
|
|
|
if (0 == size)
|
|
return;
|
|
|
|
for (i = 0; i < size; i++)
|
|
_vty_write(vty, &vty_space_char, 1);
|
|
for (i = 0; i < size; i++)
|
|
_vty_write(vty, &vty_backward_char, 1);
|
|
|
|
memset(&vty->buf[vty->cp], 0, size);
|
|
vty->length = vty->cp;
|
|
}
|
|
|
|
/* Kill line from the beginning. */
|
|
static void _vty_kill_line_from_beginning(vty_t *vty)
|
|
{
|
|
_vty_beginning_of_line(vty);
|
|
_vty_kill_line(vty);
|
|
}
|
|
|
|
/* Print command line history. This function is called from
|
|
vty_next_line and vty_previous_line. */
|
|
static void _vty_history_print(vty_t *vty)
|
|
{
|
|
unsigned int length = 0;
|
|
|
|
_vty_kill_line_from_beginning (vty);
|
|
|
|
/* Get previous line from history buffer */
|
|
length = strlen(vty->hist[vty->hp]);
|
|
memcpy(vty->buf, vty->hist[vty->hp], length);
|
|
vty->cp = vty->length = length;
|
|
|
|
/* Redraw current line */
|
|
_vty_redraw_line(vty);
|
|
}
|
|
|
|
/* Show previous command line history. */
|
|
static void _vty_previous_line(vty_t *vty)
|
|
{
|
|
unsigned int try_index = 0;
|
|
|
|
try_index = vty->hp;
|
|
if (try_index == 0)
|
|
try_index = VTY_MAXHIST - 1;
|
|
else
|
|
try_index--;
|
|
|
|
if (vty->hist[try_index] == NULL)
|
|
return;
|
|
else
|
|
vty->hp = try_index;
|
|
|
|
_vty_history_print(vty);
|
|
}
|
|
|
|
/* Show next command line history. */
|
|
static void _vty_next_line(vty_t *vty)
|
|
{
|
|
unsigned int try_index = 0;
|
|
|
|
if (vty->hp == vty->hindex)
|
|
return;
|
|
|
|
/* Try is there history exist or not. */
|
|
try_index = vty->hp;
|
|
if (try_index == (VTY_MAXHIST - 1))
|
|
try_index = 0;
|
|
else
|
|
try_index++;
|
|
|
|
/* If there is not history return. */
|
|
if (vty->hist[try_index] == NULL)
|
|
return;
|
|
else
|
|
vty->hp = try_index;
|
|
|
|
_vty_history_print (vty);
|
|
}
|
|
|
|
/* Escape character command map. */
|
|
static void _vty_escape_map (unsigned char c, vty_t *vty)
|
|
{
|
|
switch (c)
|
|
{
|
|
case ('A'):
|
|
_vty_previous_line (vty);
|
|
break;
|
|
case ('B'):
|
|
_vty_next_line(vty);
|
|
break;
|
|
case ('C'):
|
|
_vty_forward_char(vty);
|
|
break;
|
|
case ('D'):
|
|
_vty_backward_char (vty);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Go back to normal mode. */
|
|
vty->escape = VTY_NO_ESCAPE;
|
|
}
|
|
|
|
/* Backward word. */
|
|
static void _vty_backward_word(vty_t *vty)
|
|
{
|
|
while(vty->cp > 0 && vty->buf[vty->cp - 1] == ' ')
|
|
_vty_backward_char(vty);
|
|
|
|
while(vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
|
|
_vty_backward_char(vty);
|
|
}
|
|
|
|
/* Forward word. */
|
|
static void _vty_forward_word(vty_t *vty)
|
|
{
|
|
while (vty->cp != vty->length && vty->buf[vty->cp] != ' ')
|
|
_vty_forward_char(vty);
|
|
|
|
while (vty->cp != vty->length && vty->buf[vty->cp] == ' ')
|
|
_vty_forward_char(vty);
|
|
}
|
|
|
|
/* Delete a charcter at the current point. */
|
|
static void _vty_delete_char(vty_t *vty)
|
|
{
|
|
unsigned int i = 0;
|
|
unsigned int size = 0;
|
|
|
|
if (vty->length == 0)
|
|
return;
|
|
|
|
if (vty->cp == vty->length)
|
|
return;
|
|
|
|
size = vty->length - vty->cp;
|
|
|
|
vty->length--;
|
|
memmove(&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1);
|
|
vty->buf[vty->length] = '\0';
|
|
|
|
_vty_write(vty, &vty->buf[vty->cp], size - 1);
|
|
_vty_write(vty, &vty_space_char, 1);
|
|
|
|
for (i = 0; i < size; i++)
|
|
_vty_write(vty, &vty_backward_char, 1);
|
|
}
|
|
|
|
/* Delete a character before the point. */
|
|
static void _vty_delete_backward_char(vty_t *vty)
|
|
{
|
|
if (vty->cp == 0)
|
|
return;
|
|
|
|
_vty_backward_char(vty);
|
|
_vty_delete_char(vty);
|
|
}
|
|
|
|
/* Delete a word before the point. */
|
|
static void _vty_forward_kill_word(vty_t *vty)
|
|
{
|
|
while(vty->cp != vty->length && vty->buf[vty->cp] == ' ')
|
|
_vty_delete_char(vty);
|
|
while(vty->cp != vty->length && vty->buf[vty->cp] != ' ')
|
|
_vty_delete_char(vty);
|
|
}
|
|
|
|
/* Delete a word before the point. */
|
|
static void _vty_backward_kill_word(vty_t *vty)
|
|
{
|
|
while(vty->cp > 0 && vty->buf[vty->cp - 1] == ' ')
|
|
_vty_delete_backward_char(vty);
|
|
while(vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
|
|
_vty_delete_backward_char(vty);
|
|
}
|
|
|
|
/* Ensure length of input buffer. Is buffer is short, double it. */
|
|
static void _vty_buf_len_ensure(vty_t *vty, int length)
|
|
{
|
|
if (vty->max <= length)
|
|
{
|
|
vty->max *= 2;
|
|
vty->buf = XREALLOC(MTYPE_VTY, vty->buf, vty->max);
|
|
}
|
|
}
|
|
|
|
/* Basic function to insert character into vty. */
|
|
static void _vty_insert_char(vty_t *vty, char c)
|
|
{
|
|
unsigned int i = 0;
|
|
unsigned int length = 0;
|
|
|
|
_vty_buf_len_ensure(vty, vty->length + 1);
|
|
length = vty->length - vty->cp;
|
|
memmove(&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length);
|
|
vty->buf[vty->cp] = c;
|
|
|
|
if (vty->node != PASSWORD_NODE)
|
|
{
|
|
_vty_write(vty, &vty->buf[vty->cp], length + 1);
|
|
for (i = 0; i < length; i++)
|
|
_vty_write(vty, &vty_backward_char, 1);
|
|
}
|
|
|
|
vty->cp++;
|
|
vty->length++;
|
|
}
|
|
|
|
/* Insert a word into vty interface with overwrite mode. */
|
|
static void _vty_insert_word(vty_t *vty, char *str)
|
|
{
|
|
int len = strlen(str);
|
|
_vty_write(vty, str, len);
|
|
strcpy(&vty->buf[vty->cp], str);
|
|
vty->cp += len;
|
|
vty->length = vty->cp;
|
|
}
|
|
|
|
static void _vty_describe_command(vty_t *vty)
|
|
{
|
|
array_t *cmd_line = NULL;
|
|
|
|
cmd_line = cmd_strs_create(vty->buf);
|
|
/* In case of '> ?'. */
|
|
if (NULL == cmd_line)
|
|
{
|
|
cmd_line = array_init(1, MTYPE_CLI);
|
|
array_append(cmd_line, '\0', MTYPE_CLI);
|
|
}
|
|
else if(vty->length && isspace((int)vty->buf[vty->length - 1]))
|
|
array_append(cmd_line, '\0', MTYPE_CLI);
|
|
|
|
vty_question(vty, cmd_line);
|
|
|
|
cmd_strs_free(cmd_line);
|
|
vty_prompt(vty);
|
|
_vty_redraw_line(vty);
|
|
return;
|
|
}
|
|
|
|
static void _vty_append_word(vty_t *vty, array_t *cmd_line, char *word, int status)
|
|
{
|
|
unsigned int index = array_active(cmd_line) - 1;
|
|
|
|
_vty_end_of_line(vty);
|
|
|
|
if (NULL == array_get(cmd_line, index))
|
|
_vty_insert_word(vty, word);
|
|
else
|
|
{
|
|
_vty_backward_word(vty);
|
|
_vty_insert_word(vty, word);
|
|
}
|
|
|
|
if (CMD_COMPLETE_FULL_MATCH == status)
|
|
_vty_insert_char(vty, ' ');
|
|
}
|
|
|
|
int _vty_complete_command(vty_t *vty)
|
|
{
|
|
array_t *cmd_line = NULL;
|
|
char **match_strs = NULL;
|
|
int32_t complete_status = CMD_ERR_NO_MATCH;
|
|
|
|
cmd_line = cmd_strs_create(vty->buf);
|
|
if (NULL == cmd_line)
|
|
{
|
|
cmd_line = array_init(1, MTYPE_CLI);
|
|
array_append(cmd_line, '\0', MTYPE_CLI);
|
|
}
|
|
else if (vty->length && isspace((int)vty->buf[vty->length - 1]))
|
|
array_append(cmd_line, '\0', MTYPE_CLI);
|
|
|
|
match_strs = cmd_complete_command(cmd_line, vty, &complete_status);
|
|
if (NULL == match_strs)
|
|
{
|
|
cmd_strs_free(cmd_line);
|
|
return 0;
|
|
}
|
|
|
|
if (CMD_COMPLETE_MATCH == complete_status ||
|
|
CMD_COMPLETE_FULL_MATCH == complete_status)
|
|
_vty_append_word(vty, cmd_line, match_strs[0], complete_status);
|
|
else
|
|
{
|
|
vty_print_word(vty, match_strs);
|
|
vty_prompt(vty);
|
|
_vty_redraw_line(vty);
|
|
}
|
|
|
|
vty_free_match_strs(match_strs);
|
|
cmd_strs_free(cmd_line);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _vty_read(thread_t *thread)
|
|
{
|
|
int i = 0;
|
|
int nbytes = 0;
|
|
unsigned char buf[VTY_READ_BUFSIZ];
|
|
int vty_sock = THREAD_FD(thread);
|
|
vty_t *vty = THREAD_ARG(thread);
|
|
|
|
vty->t_read = NULL;
|
|
|
|
/* Read raw data from socket */
|
|
if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0)
|
|
{
|
|
if (nbytes < 0)
|
|
{
|
|
if (ERRNO_IO_RETRY(errno))
|
|
{
|
|
vty_event(VTY_READ, vty_sock, vty);
|
|
return 0;
|
|
}
|
|
vty->monitor = 0; /* disable monitoring to avoid infinite recursion */
|
|
printf("%s: read error on vty client fd %d, closing: %s\n",
|
|
__func__, vty->fd, safe_strerror(errno));
|
|
}
|
|
|
|
buf_reset(vty->out_buf);
|
|
vty->status = VTY_CLOSE;
|
|
}
|
|
|
|
for (i = 0; i < nbytes; i++)
|
|
{
|
|
if (IAC == buf[i])
|
|
{
|
|
if (!vty->iac)
|
|
{
|
|
vty->iac = 1;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
vty->iac = 0;
|
|
}
|
|
}
|
|
|
|
if (vty->iac_sb_in_progress && !vty->iac)
|
|
{
|
|
if (vty->sb_len < sizeof(vty->sb_buf))
|
|
vty->sb_buf[vty->sb_len] = buf[i];
|
|
vty->sb_len++;
|
|
continue;
|
|
}
|
|
|
|
if (vty->iac)
|
|
{
|
|
/* In case of telnet command */
|
|
int ret = 0;
|
|
ret = _vty_telnet_option(vty, &buf[i], nbytes - i);
|
|
vty->iac = 0;
|
|
i += ret;
|
|
continue;
|
|
}
|
|
|
|
if (VTY_MORE == vty->status)
|
|
{
|
|
switch (buf[i])
|
|
{
|
|
case CONTROL('C'):
|
|
case 'q':
|
|
case 'Q':
|
|
_vty_buffer_reset(vty);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
/* Escape character. */
|
|
if (VTY_ESCAPE == vty->escape)
|
|
{
|
|
_vty_escape_map(buf[i], vty);
|
|
continue;
|
|
}
|
|
|
|
/* Pre-escape status. */
|
|
if (vty->escape == VTY_PRE_ESCAPE)
|
|
{
|
|
switch (buf[i])
|
|
{
|
|
case '[':
|
|
vty->escape = VTY_ESCAPE;
|
|
break;
|
|
case 'b':
|
|
_vty_backward_word(vty);
|
|
vty->escape = VTY_NO_ESCAPE;
|
|
break;
|
|
case 'f':
|
|
_vty_forward_word(vty);
|
|
vty->escape = VTY_NO_ESCAPE;
|
|
break;
|
|
case 'd':
|
|
_vty_forward_kill_word(vty);
|
|
vty->escape = VTY_NO_ESCAPE;
|
|
break;
|
|
case CONTROL('H'):
|
|
case 0x7f:
|
|
_vty_backward_kill_word(vty);
|
|
vty->escape = VTY_NO_ESCAPE;
|
|
break;
|
|
default:
|
|
vty->escape = VTY_NO_ESCAPE;
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
switch (buf[i])
|
|
{
|
|
case CONTROL('A'):
|
|
_vty_beginning_of_line(vty);
|
|
break;
|
|
case CONTROL('B'):
|
|
_vty_backward_char(vty);
|
|
break;
|
|
case CONTROL('C'):
|
|
//_vty_stop_input(vty);
|
|
break;
|
|
case CONTROL('D'):
|
|
_vty_delete_char(vty);
|
|
break;
|
|
case CONTROL('E'):
|
|
//_vty_end_of_line(vty);
|
|
break;
|
|
case CONTROL('F'):
|
|
_vty_forward_char(vty);
|
|
break;
|
|
case CONTROL('H'):
|
|
case 0x7f:
|
|
_vty_delete_backward_char(vty);
|
|
break;
|
|
case CONTROL('K'):
|
|
_vty_kill_line(vty);
|
|
break;
|
|
case CONTROL('N'):
|
|
_vty_next_line(vty);
|
|
break;
|
|
case CONTROL('P'):
|
|
_vty_previous_line(vty);
|
|
break;
|
|
case CONTROL('T'):
|
|
//_vty_transpose_chars(vty);
|
|
break;
|
|
case CONTROL('U'):
|
|
_vty_kill_line_from_beginning(vty);
|
|
break;
|
|
case CONTROL('W'):
|
|
_vty_backward_kill_word(vty);
|
|
break;
|
|
case CONTROL('Z'):
|
|
//_vty_end_config(vty);
|
|
break;
|
|
case '\n':
|
|
case '\r':
|
|
vty_out(vty, "%s", VTY_NEWLINE);
|
|
vty_execute(vty);
|
|
break;
|
|
case '\t':
|
|
_vty_complete_command(vty);
|
|
break;
|
|
case '?':
|
|
_vty_describe_command(vty);
|
|
break;
|
|
case '\033':
|
|
if (i + 1 < nbytes && buf[i + 1] == '[')
|
|
{
|
|
vty->escape = VTY_ESCAPE;
|
|
i++;
|
|
}
|
|
else
|
|
vty->escape = VTY_PRE_ESCAPE;
|
|
break;
|
|
default:
|
|
if (buf[i] > 31 && buf[i] < 127)
|
|
_vty_insert_char(vty, buf[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Check status. */
|
|
if (vty->status == VTY_CLOSE)
|
|
vty_close(vty);
|
|
else
|
|
{
|
|
vty_event(VTY_WRITE, vty_sock, vty);
|
|
vty_event(VTY_READ, vty_sock, vty);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _vty_thread(void *arg)
|
|
{
|
|
thread_t thread;
|
|
|
|
/* Fetch next active thread. */
|
|
while(thread_fetch(vty_master, &thread))
|
|
thread_call(&thread);
|
|
|
|
printf("vty thread is exit!\n");
|
|
return;
|
|
}
|
|
|
|
static void _vty_thread_init(void)
|
|
{
|
|
int32_t rv = 0;
|
|
|
|
pthread_t ppid = 0;
|
|
struct sched_param param;
|
|
pthread_attr_t attr;
|
|
|
|
pthread_attr_init(&attr);
|
|
param.sched_priority = 50;
|
|
pthread_attr_setschedpolicy(&attr, SCHED_RR);
|
|
pthread_attr_setschedparam(&attr, ¶m);
|
|
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
|
|
|
|
rv = pthread_create(&ppid, &attr, (void *)_vty_thread, NULL);
|
|
if (rv != 0)
|
|
printf("can't create pthread : %d(%s)\n", rv, safe_strerror(rv));
|
|
|
|
pthread_attr_destroy(&attr);
|
|
}
|
|
|
|
/* Create new vty structure. */
|
|
static vty_t *_vty_term_create(int vty_sock, SOCKUNION_U *su)
|
|
{
|
|
vty_t *vty = NULL;
|
|
|
|
vty = vty_create();
|
|
|
|
vty->type = VTY_TERM;
|
|
vty->node = USERNAME_NODE;
|
|
vty->fd = vty_sock;
|
|
vty->address = sockunion_su2str(su);
|
|
vty->fail_count = 0;
|
|
|
|
vty->cp = 0;
|
|
vty->length = 0;
|
|
|
|
memset(vty->hist, 0, sizeof(vty->hist));
|
|
vty->hp = 0;
|
|
vty->hindex = 0;
|
|
|
|
vty->status = VTY_NORMAL;
|
|
|
|
if (host.lines >= 0)
|
|
vty->lines = host.lines;
|
|
else
|
|
vty->lines = -1;
|
|
|
|
vty->iac = 0;
|
|
vty->iac_sb_in_progress = 0;
|
|
vty->sb_len = 0;
|
|
|
|
vty->v_timeout = VTY_TIMEOUT_VAL;
|
|
|
|
array_set(vtyvec, vty_sock, vty, MTYPE_VTY_TMP);
|
|
|
|
/* Setting up terminal. */
|
|
vty_will_echo(vty);
|
|
vty_will_suppress_go_ahead(vty);
|
|
|
|
vty_dont_linemode(vty);
|
|
vty_do_window_size(vty);
|
|
/* vty_dont_lflow_ahead (vty); */
|
|
|
|
vty_prompt(vty);
|
|
|
|
/* Add read/write thread. */
|
|
vty_event(VTY_WRITE, vty_sock, vty);
|
|
vty_event(VTY_READ, vty_sock, vty);
|
|
|
|
return vty;
|
|
}
|
|
|
|
/* Accept connection from the network. */
|
|
static int _vty_accept(thread_t *thread)
|
|
{
|
|
int vty_sock = 0;
|
|
SOCKUNION_U su;
|
|
int ret = -1;
|
|
unsigned int on = 0;
|
|
int accept_sock = 0;
|
|
prefix_t *p = NULL;
|
|
access_list_t *acl = NULL;
|
|
char *bufp = NULL;
|
|
|
|
accept_sock = THREAD_FD(thread);
|
|
|
|
/* We continue hearing vty socket. */
|
|
vty_event(VTY_SERV, accept_sock, NULL);
|
|
|
|
memset(&su, 0, sizeof(SOCKUNION_U));
|
|
|
|
/* We can handle IPv4 or IPv6 socket. */
|
|
vty_sock = sockunion_accept(accept_sock, &su);
|
|
if (vty_sock < 0)
|
|
{
|
|
printf("can't accept vty socket : %s\n", safe_strerror(errno));
|
|
return -1;
|
|
}
|
|
set_nonblocking(vty_sock);
|
|
|
|
p = sockunion2hostprefix(&su);
|
|
|
|
/* VTY's accesslist apply. */
|
|
if (AF_INET == p->family && vty_accesslist_name)
|
|
{
|
|
if ((acl = access_list_lookup(AFI_IP, vty_accesslist_name)) &&
|
|
(FILTER_DENY == access_list_apply(acl, p)))
|
|
{
|
|
char *buf = NULL;
|
|
printf("Vty connection refused from %s\n", (buf = sockunion_su2str(&su)));
|
|
XFREE(MTYPE_PREFIX, buf);
|
|
close(vty_sock);
|
|
|
|
/* continue accepting connections */
|
|
vty_event(VTY_SERV, accept_sock, NULL);
|
|
|
|
prefix_free(p);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_IPV6
|
|
/* VTY's ipv6 accesslist apply. */
|
|
if (AF_INET6 == p->family && vty_ipv6_accesslist_name)
|
|
{
|
|
if ((acl = access_list_lookup(AFI_IP6, vty_ipv6_accesslist_name))
|
|
&& (FILTER_DENY == access_list_apply(acl, p)))
|
|
{
|
|
char *buf = NULL;
|
|
printf("Vty connection refused from %s\n", (buf = sockunion_su2str(&su)));
|
|
XFREE(MTYPE_PREFIX, buf);
|
|
close(vty_sock);
|
|
|
|
/* continue accepting connections */
|
|
vty_event(VTY_SERV, accept_sock, NULL);
|
|
|
|
prefix_free(p);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
#endif /* HAVE_IPV6 */
|
|
|
|
prefix_free(p);
|
|
|
|
on = 1;
|
|
ret = setsockopt(vty_sock, IPPROTO_TCP, TCP_NODELAY, (char *)&on, sizeof(on));
|
|
if (ret < 0)
|
|
printf("can't set sockopt to vty_sock : %s\n", safe_strerror(errno));
|
|
|
|
printf("Vty connection from %s\n", (bufp = sockunion_su2str(&su)));
|
|
if (bufp)
|
|
XFREE(MTYPE_PREFIX, bufp);
|
|
|
|
_vty_term_create(vty_sock, &su);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* When time out occur output message then close connection. */
|
|
static int _vty_timeout(thread_t *thread)
|
|
{
|
|
vty_t *vty = THREAD_ARG(thread);
|
|
|
|
vty->t_timeout = NULL;
|
|
vty->v_timeout = 0;
|
|
|
|
/* Clear buffer*/
|
|
buf_reset(vty->out_buf);
|
|
vty_out(vty, "%sVty connection is timed out.%s\n", VTY_NEWLINE, VTY_NEWLINE);
|
|
|
|
/* Close connection. */
|
|
vty->status = VTY_CLOSE;
|
|
vty_close(vty);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Flush buffer to the vty. */
|
|
static int _vty_flush(thread_t *thread)
|
|
{
|
|
int32_t erase = 0;
|
|
BUF_STATUS_E flushrc = BUFFER_ERROR;
|
|
int vty_sock = THREAD_FD(thread);
|
|
vty_t *vty = THREAD_ARG(thread);
|
|
|
|
vty->t_write = NULL;
|
|
|
|
/* Tempolary disable read thread. */
|
|
if ((0 == vty->lines) && vty->t_read)
|
|
{
|
|
thread_cancel(vty->t_read);
|
|
vty->t_read = NULL;
|
|
}
|
|
|
|
/* Function execution continue. */
|
|
erase = ((VTY_MORE == vty->status || VTY_MORELINE == vty->status));
|
|
|
|
/* N.B. if width is 0, that means we don't know the window size. */
|
|
if ((vty->lines == 0) || (vty->width == 0))
|
|
flushrc = buf_flush_available(vty->out_buf, vty->fd);
|
|
else if (VTY_MORELINE == vty->status)
|
|
flushrc = buf_flush_window(vty->out_buf, vty->fd, vty->width, 1, erase, 0);
|
|
else
|
|
flushrc = buf_flush_window(vty->out_buf, vty->fd, vty->width,
|
|
vty->lines >= 0 ? vty->lines : vty->height, erase, 0);
|
|
|
|
switch (flushrc)
|
|
{
|
|
case BUFFER_ERROR:
|
|
vty->monitor = 0; /* disable monitoring to avoid infinite recursion */
|
|
printf("buffer_flush failed on vty client fd %d, closing\n", vty->fd);
|
|
buf_reset(vty->out_buf);
|
|
vty_close(vty);
|
|
return 0;
|
|
case BUFFER_EMPTY:
|
|
if (VTY_CLOSE == vty->status)
|
|
vty_close (vty);
|
|
else
|
|
{
|
|
vty->status = VTY_NORMAL;
|
|
if (0 == vty->lines)
|
|
vty_event(VTY_READ, vty_sock, vty);
|
|
}
|
|
break;
|
|
case BUFFER_PENDING:
|
|
/* There is more data waiting to be written. */
|
|
vty->status = VTY_MORE;
|
|
if (0 == vty->lines)
|
|
vty_event(VTY_WRITE, vty_sock, vty);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _vty_log_out(vty_t *vty, const char *level, const char *proto_str, const char *format,
|
|
char *time_str, va_list va)
|
|
{
|
|
int32_t ret = 0;
|
|
int32_t len = 0;
|
|
char *buf = NULL;
|
|
|
|
|
|
buf = XMALLOC(MTYPE_VTY_TMP, VTY_TMP_BUFSIZ);
|
|
if (NULL == buf)
|
|
return -1;
|
|
|
|
len = snprintf(buf, VTY_TMP_BUFSIZ, "%s", time_str);
|
|
buf[len++] = ' ';
|
|
|
|
if (level)
|
|
ret = snprintf(buf + len, VTY_TMP_BUFSIZ - len - 1, "%s: %s: ", level, proto_str);
|
|
else
|
|
ret = snprintf(buf + len, VTY_TMP_BUFSIZ - len - 1, "%s: ", proto_str);
|
|
|
|
len += ret;
|
|
if ((ret < 0) || (len > VTY_TMP_BUFSIZ))
|
|
{
|
|
XFREE(MTYPE_VTY_TMP, buf);
|
|
return -1;
|
|
}
|
|
|
|
ret = vsnprintf(buf + len, VTY_TMP_BUFSIZ - len - 1, format, va);
|
|
len += ret;
|
|
if ((ret < 0) || (len > VTY_TMP_BUFSIZ))
|
|
{
|
|
XFREE(MTYPE_VTY_TMP, buf);
|
|
return -1;
|
|
}
|
|
|
|
vty_out(vty, "%s%s", buf, VTY_NEWLINE);
|
|
buf_flush_all(vty->out_buf, vty->fd);
|
|
|
|
XFREE(MTYPE_VTY_TMP, buf);
|
|
return 0;
|
|
}
|
|
|
|
static int32_t _vty_user_cmp(vty_user_t *user)
|
|
{
|
|
int32_t i = 0;
|
|
vty_user_t *node = NULL;
|
|
|
|
for(i = 0; i < array_active(vty_user); i++)
|
|
if ((node = array_get(vty_user, i)) != NULL)
|
|
{
|
|
if (0 == strcmp(user->username, node->username)
|
|
&& 0 == strcmp(user->password, node->password))
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* 在终端输出版本信息 */
|
|
void vty_version_print(vty_t *vty)
|
|
{
|
|
char buf[128] = {0};
|
|
time_t temp = 0;
|
|
|
|
vty_out(vty, "%s%s", CL_COPYRIGHT, VTY_NEWLINE);
|
|
vty_out(vty, "%s %s (%s).%s", PROGNAME, host.version, host.name ? host.name : "", VTY_NEWLINE);
|
|
vty_out(vty, "Compile date: %s%s", host.compile, VTY_NEWLINE);
|
|
vty_out(vty, "Id: %04d.%04d%s", device_info.id_major, device_info.id_minor, VTY_NEWLINE);
|
|
vty_out(vty, "Mac: %02x:%02x:%02x:%02x:%02x:%02x%s",
|
|
device_info.mac[0], device_info.mac[1], device_info.mac[2],
|
|
device_info.mac[3], device_info.mac[4], device_info.mac[5], VTY_NEWLINE);
|
|
vty_out(vty, "ip: %s %s%s", device_info.host_ip, device_info.host_mask, VTY_NEWLINE);
|
|
vty_out(vty, "route: %s%s", device_info.host_gw, VTY_NEWLINE);
|
|
temp = device_info.factory_date;
|
|
strftime(buf, 128, "%Y-%m-%d %H:%M:%S", localtime(&temp));
|
|
vty_out(vty, "factory date: %s%s", buf, VTY_NEWLINE);
|
|
temp = device_info.deployment_date;
|
|
strftime(buf, 128, "%Y-%m-%d %H:%M:%S", localtime(&temp));
|
|
vty_out(vty, "deployment date: %s%s", buf, VTY_NEWLINE);
|
|
}
|
|
|
|
void vty_event(VTY_EVENT_E event, int sock, vty_t *vty)
|
|
{
|
|
thread_t *thread = NULL;
|
|
|
|
switch (event)
|
|
{
|
|
case VTY_SERV:
|
|
thread = THREAD_ADD_READ(vty_master, _vty_accept, vty, sock);
|
|
array_set(vty_serv_thread, sock, thread, MTYPE_VTY_TMP);
|
|
break;
|
|
#if 0
|
|
case VTYSH_SERV:
|
|
thread_add_read(vty_master, vtysh_accept, vty, sock);
|
|
break;
|
|
case VTYSH_READ:
|
|
vty->t_read = THREAD_ADD_READ(vty_master, vtysh_read, vty, sock);
|
|
break;
|
|
case VTYSH_WRITE:
|
|
vty->t_write = THREAD_ADD_WRITE(vty_master, vtysh_write, vty, sock);
|
|
break;
|
|
#endif /* VTYSH */
|
|
case VTY_READ:
|
|
vty->t_read = THREAD_ADD_READ(vty_master, _vty_read, vty, sock);
|
|
|
|
/* Time out treatment. */
|
|
if (vty->v_timeout)
|
|
{
|
|
if (vty->t_timeout)
|
|
thread_cancel(vty->t_timeout);
|
|
|
|
vty->t_timeout =
|
|
THREAD_ADD_TIMER(vty_master, _vty_timeout, vty, vty->v_timeout);
|
|
}
|
|
break;
|
|
case VTY_WRITE:
|
|
if (! vty->t_write)
|
|
vty->t_write = THREAD_ADD_WRITE(vty_master, _vty_flush, vty, sock);
|
|
break;
|
|
case VTY_TIMEOUT_RESET:
|
|
if (vty->t_timeout)
|
|
{
|
|
thread_cancel(vty->t_timeout);
|
|
vty->t_timeout = NULL;
|
|
}
|
|
if (vty->v_timeout)
|
|
{
|
|
vty->t_timeout =
|
|
THREAD_ADD_TIMER(vty_master, _vty_timeout, vty, vty->v_timeout);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void vty_clear_buf(vty_t *vty)
|
|
{
|
|
memset(vty->buf, 0, vty->max);
|
|
}
|
|
|
|
/* Put out prompt and wait input from user. */
|
|
void vty_prompt(vty_t *vty)
|
|
{
|
|
cmd_node_t *node = NULL;
|
|
|
|
if (vty->type != VTY_SHELL)
|
|
{
|
|
node = cmd_node_get(vty->node);
|
|
|
|
/* 比如用户密码输入 */
|
|
if (USERNAME_NODE == node->node
|
|
|| PASSWORD_NODE == node->node)
|
|
{
|
|
vty_out(vty, "%s", node->prompt);
|
|
}
|
|
else
|
|
{
|
|
vty_out(vty, node->prompt, host.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add current command line to the history buffer. */
|
|
static void vty_hist_add(vty_t *vty)
|
|
{
|
|
int index;
|
|
|
|
if (vty->length == 0)
|
|
return;
|
|
|
|
index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1;
|
|
|
|
/* Ignore the same string as previous one. */
|
|
if (vty->hist[index])
|
|
if(0 == strcmp(vty->buf, vty->hist[index]))
|
|
{
|
|
vty->hp = vty->hindex;
|
|
return;
|
|
}
|
|
|
|
/* Insert history entry. */
|
|
if (vty->hist[vty->hindex])
|
|
XFREE(MTYPE_VTY_HIST, vty->hist[vty->hindex]);
|
|
vty->hist[vty->hindex] = XSTRDUP(MTYPE_VTY_HIST, vty->buf);
|
|
|
|
/* History index rotation. */
|
|
vty->hindex++;
|
|
if (vty->hindex == VTY_MAXHIST)
|
|
vty->hindex = 0;
|
|
|
|
vty->hp = vty->hindex;
|
|
}
|
|
|
|
int vty_shell(vty_t *vty)
|
|
{
|
|
return vty->type == VTY_SHELL ? 1 : 0;
|
|
}
|
|
|
|
/* VTY standard output function. */
|
|
int vty_out (vty_t *vty, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
int len = 0;
|
|
int size = 1024;
|
|
char buf[1024];
|
|
char *p = NULL;
|
|
|
|
if (vty_shell(vty))
|
|
{
|
|
va_start(args, format);
|
|
vprintf(format, args);
|
|
va_end(args);
|
|
}
|
|
else
|
|
{
|
|
/* Try to write to initial buffer. */
|
|
va_start(args, format);
|
|
len = vsnprintf(buf, sizeof buf, format, args);
|
|
va_end(args);
|
|
|
|
/* Initial buffer is not enough. */
|
|
if (len < 0 || len >= size)
|
|
{
|
|
while (1)
|
|
{
|
|
if (len > -1)
|
|
size = len + 1;
|
|
else
|
|
size = size * 2;
|
|
|
|
p = XREALLOC(MTYPE_VTY_OUT_BUF, p, size);
|
|
if (!p)
|
|
return -1;
|
|
|
|
va_start(args, format);
|
|
len = vsnprintf(p, size, format, args);
|
|
va_end(args);
|
|
|
|
if (len > -1 && len < size)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* When initial buffer is enough to store all output. */
|
|
if (!p)
|
|
p = buf;
|
|
|
|
/* Pointer p must point out buffer. */
|
|
buf_put(vty->out_buf, p, len);
|
|
|
|
/* If p is not different with buf, it is allocated buffer. */
|
|
if (p != buf)
|
|
XFREE(MTYPE_VTY_OUT_BUF, p);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
/* Allocate new vty struct. */
|
|
vty_t *vty_create()
|
|
{
|
|
vty_t *vty_new = XMALLOC(MTYPE_VTY, sizeof(vty_t));
|
|
|
|
/* Use default buffer size. */
|
|
vty_new->out_buf = buf_create(0);
|
|
vty_new->buf = XMALLOC(MTYPE_VTY, VTY_BUFSIZ);
|
|
vty_new->max = VTY_BUFSIZ;
|
|
|
|
return vty_new;
|
|
}
|
|
|
|
/* Close vty interface. Warning: call this only from functions that
|
|
* will be careful not to access the vty afterwards (since it has
|
|
* now been freed). This is safest from top-level functions (called
|
|
* directly by the thread dispatcher). */
|
|
void vty_close(vty_t *vty)
|
|
{
|
|
int i = 0;
|
|
|
|
/* Cancel threads.*/
|
|
if (vty->t_read)
|
|
thread_cancel (vty->t_read);
|
|
if (vty->t_write)
|
|
thread_cancel (vty->t_write);
|
|
if (vty->t_timeout)
|
|
thread_cancel (vty->t_timeout);
|
|
|
|
/* Flush buffer. */
|
|
buf_flush_all(vty->out_buf, vty->fd);
|
|
|
|
/* Free input buffer. */
|
|
buf_free(vty->out_buf);
|
|
|
|
/* Free command history. */
|
|
for(i = 0; i < VTY_MAXHIST; i++)
|
|
if (vty->hist[i])
|
|
XFREE(MTYPE_VTY_HIST, vty->hist[i]);
|
|
|
|
/* Unset vector. */
|
|
array_unset(vtyvec, vty->fd);
|
|
|
|
/* Close socket. */
|
|
if (vty->fd > 0)
|
|
close(vty->fd);
|
|
|
|
if (vty->address)
|
|
XFREE(MTYPE_PREFIX, vty->address);
|
|
if (vty->buf)
|
|
XFREE(MTYPE_VTY, vty->buf);
|
|
|
|
/* Check configure. */
|
|
vty_config_unlock(vty);
|
|
|
|
/* OK free vty. */
|
|
XFREE(MTYPE_VTY, vty);
|
|
}
|
|
|
|
/* Execute current command line. */
|
|
int vty_execute(vty_t *vty)
|
|
{
|
|
int ret = CMD_ERR_NO_MATCH;
|
|
|
|
switch (vty->node)
|
|
{
|
|
case USERNAME_NODE:
|
|
snprintf(vty->user.username, VTY_USERNAME_LEN, "%s", vty->buf);
|
|
vty->node = PASSWORD_NODE;
|
|
break;
|
|
|
|
case PASSWORD_NODE:
|
|
snprintf(vty->user.password, VTY_USERNAME_LEN, "%s", vty->buf);
|
|
if (0 == _vty_user_cmp(&vty->user))
|
|
{
|
|
vty->node = ENABLE_NODE;
|
|
//vty_out(vty, "%s================================%s", VTY_NEWLINE, VTY_NEWLINE);
|
|
//vty_version_print(vty);
|
|
//vty_out(vty, "================================%s%s", VTY_NEWLINE, VTY_NEWLINE);
|
|
}
|
|
else
|
|
{
|
|
//vty_out(vty, "Username or password is not match!!!%s", VTY_NEWLINE);
|
|
vty->node = USERNAME_NODE;
|
|
}
|
|
break;
|
|
|
|
case ENABLE_NODE:
|
|
case CONFIG_NODE:
|
|
default:
|
|
ret = cmd_execute(vty);
|
|
if (vty->type != VTY_SHELL)
|
|
vty_hist_add(vty);
|
|
break;
|
|
}
|
|
|
|
/* Clear command line buffer. */
|
|
vty->cp = vty->length = 0;
|
|
vty_clear_buf(vty);
|
|
|
|
//if (vty->status != VTY_CLOSE )
|
|
// vty_prompt(vty);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int vty_config_lock(vty_t *vty)
|
|
{
|
|
if (0 == vty_config)
|
|
{
|
|
vty->config = 1;
|
|
vty_config = 1;
|
|
}
|
|
|
|
return vty->config;
|
|
}
|
|
|
|
int vty_config_unlock(vty_t *vty)
|
|
{
|
|
if (1 == vty_config)
|
|
vty_config = 0;
|
|
|
|
if (1 == vty->config)
|
|
vty->config = 0;
|
|
|
|
return vty->config;
|
|
}
|
|
|
|
void vty_question(vty_t *vty, array_t *cmd_line)
|
|
{
|
|
int32_t ret = CMD_ERR_NO_MATCH;
|
|
unsigned int i = 0;
|
|
array_t *matchs = NULL;
|
|
unsigned int width = 0;
|
|
desc_t *desc = NULL;
|
|
|
|
matchs = cmd_describe_command(cmd_line, vty, &ret);
|
|
vty_out(vty, "%s", VTY_NEWLINE);
|
|
|
|
/* Ambiguous and no match error. */
|
|
switch (ret)
|
|
{
|
|
case CMD_ERR_AMBIGUOUS:
|
|
fprintf (stdout,"%% Ambiguous command.\n");
|
|
return;
|
|
break;
|
|
case CMD_ERR_NO_MATCH:
|
|
fprintf (stdout,"%% There is no matched command.\n");
|
|
return;
|
|
break;
|
|
}
|
|
|
|
/* Get width of command string. */
|
|
for (i = 0; i < array_active(matchs); i++)
|
|
if ((desc = array_get(matchs, i)) != NULL)
|
|
{
|
|
unsigned int len = 0;
|
|
|
|
if (desc->cmd[0] == '\0')
|
|
continue;
|
|
|
|
len = strlen(desc->cmd);
|
|
if (desc->cmd[0] == '.')
|
|
len--;
|
|
|
|
if (width < len)
|
|
width = len;
|
|
}
|
|
|
|
for (i = 0; i < array_active(matchs); i++)
|
|
if ((desc = array_get(matchs, i)) != NULL)
|
|
{
|
|
if (desc->cmd[0] == '\0')
|
|
continue;
|
|
|
|
if (!desc->str)
|
|
vty_out(vty, " %-s%s", desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, VTY_NEWLINE);
|
|
else
|
|
vty_out(vty, " %-*s %s%s", width, desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd, desc->str, VTY_NEWLINE);
|
|
}
|
|
|
|
array_free(matchs, MTYPE_CLI);
|
|
return;
|
|
}
|
|
|
|
void vty_print_word(vty_t *vty, char *strs[])
|
|
{
|
|
char *str = NULL;
|
|
unsigned int i = 0;
|
|
|
|
vty_out(vty, "%s", VTY_NEWLINE);
|
|
for(i = 0; (str = strs[i]) != NULL; i++)
|
|
vty_out(vty, "%s", str);
|
|
}
|
|
|
|
void vty_free_match_strs(char *match_strs[])
|
|
{
|
|
char *str = NULL;
|
|
unsigned int i = 0;
|
|
|
|
for(i = 0; (str = match_strs[i]) != NULL; i++)
|
|
XFREE(MTYPE_CLI, str);
|
|
|
|
XFREE(MTYPE_CLI, match_strs);
|
|
}
|
|
|
|
/* Send WILL TELOPT_ECHO to remote server. */
|
|
void vty_will_echo(vty_t *vty)
|
|
{
|
|
unsigned char cmd[] = {IAC, WILL, TELOPT_ECHO, '\0'};
|
|
vty_out(vty, "%s", cmd);
|
|
}
|
|
|
|
/* Make suppress Go-Ahead telnet option. */
|
|
void vty_will_suppress_go_ahead(vty_t *vty)
|
|
{
|
|
unsigned char cmd[] = {IAC, WILL, TELOPT_SGA, '\0'};
|
|
vty_out(vty, "%s", cmd);
|
|
}
|
|
|
|
/* Make don't use linemode over telnet. */
|
|
void vty_dont_linemode(vty_t *vty)
|
|
{
|
|
unsigned char cmd[] = {IAC, DONT, TELOPT_LINEMODE, '\0'};
|
|
vty_out(vty, "%s", cmd);
|
|
}
|
|
|
|
/* Use window size. */
|
|
void vty_do_window_size(vty_t *vty)
|
|
{
|
|
unsigned char cmd[] = {IAC, DO, TELOPT_NAWS, '\0'};
|
|
vty_out(vty, "%s", cmd);
|
|
}
|
|
|
|
/* Make vty server socket. */
|
|
void vty_serv_sock_family(const char* addr, unsigned short port, int family)
|
|
{
|
|
int32_t ret = 0;
|
|
SOCKUNION_U su;
|
|
int accept_sock = 0;
|
|
void *naddr = NULL;
|
|
|
|
memset(&su, 0, sizeof(SOCKUNION_U));
|
|
su.sa.sa_family = family;
|
|
if(addr)
|
|
switch(family)
|
|
{
|
|
case AF_INET:
|
|
naddr = &su.sin.sin_addr;
|
|
#ifdef HAVE_IPV6
|
|
case AF_INET6:
|
|
naddr = &su.sin6.sin6_addr;
|
|
#endif
|
|
}
|
|
|
|
if(naddr)
|
|
switch(inet_pton(family, addr, naddr))
|
|
{
|
|
case -1:
|
|
printf("bad address %s\n", addr);
|
|
naddr = NULL;
|
|
break;
|
|
case 0:
|
|
printf("error translating address %s: %s\n", addr, safe_strerror(errno));
|
|
naddr = NULL;
|
|
}
|
|
|
|
/* Make new socket. */
|
|
accept_sock = sockunion_stream_socket(&su);
|
|
if (accept_sock < 0)
|
|
return;
|
|
|
|
/* This is server, so reuse address. */
|
|
sockunion_reuseaddr(accept_sock);
|
|
|
|
/* Bind socket to universal address and given port. */
|
|
ret = sockunion_bind(accept_sock, &su, port, naddr);
|
|
if (ret < 0)
|
|
{
|
|
close(accept_sock); /* Avoid sd leak. */
|
|
return;
|
|
}
|
|
|
|
/* Listen socket under queue 3. */
|
|
ret = listen(accept_sock, 3);
|
|
if (ret < 0)
|
|
{
|
|
printf("can't listen socket\n");
|
|
close(accept_sock); /* Avoid sd leak. */
|
|
return;
|
|
}
|
|
|
|
/* Add vty server event. */
|
|
vty_event(VTY_SERV, accept_sock, NULL);
|
|
}
|
|
|
|
/* Small utility function which output log to the VTY. */
|
|
void vty_log(const char *level, const char *proto_str, const char *format, char *time_str, va_list va)
|
|
{
|
|
uint32_t i = 0;
|
|
vty_t *vty = NULL;
|
|
|
|
if (!vtyvec)
|
|
return;
|
|
|
|
for(i = 0; i < array_active(vtyvec); i++)
|
|
if ((vty = array_get(vtyvec, i)) != NULL)
|
|
if (vty->monitor)
|
|
{
|
|
va_list ac;
|
|
va_copy(ac, va);
|
|
_vty_log_out(vty, level, proto_str, format, time_str, ac);
|
|
va_end(ac);
|
|
}
|
|
}
|
|
|
|
/* vty的print函数. */
|
|
void vty_print(const char *format, va_list va)
|
|
{
|
|
uint32_t i = 0;
|
|
vty_t *vty = NULL;
|
|
char *buf = NULL;
|
|
|
|
if (!vtyvec)
|
|
return;
|
|
|
|
for(i = 0; i < array_active(vtyvec); i++)
|
|
if ((vty = array_get(vtyvec, i)) != NULL)
|
|
{
|
|
buf = XMALLOC(MTYPE_VTY_TMP, VTY_TMP_BUFSIZ);
|
|
if (NULL == buf)
|
|
return;
|
|
|
|
vsnprintf(buf, VTY_TMP_BUFSIZ, format, va);
|
|
vty_out(vty, "%s", buf);
|
|
buf_flush_all(vty->out_buf, vty->fd);
|
|
|
|
XFREE(MTYPE_VTY_TMP, buf);
|
|
}
|
|
}
|
|
|
|
|
|
/* Reset all VTY status. */
|
|
void vty_reset(void)
|
|
{
|
|
unsigned int i = 0;
|
|
vty_t *vty = NULL;
|
|
thread_t *serv_thread;
|
|
|
|
for(i = 0; i < array_active(vtyvec); i++)
|
|
if ((vty = array_get(vtyvec, i)) != NULL)
|
|
{
|
|
buf_reset(vty->out_buf);
|
|
vty->status = VTY_CLOSE;
|
|
vty_close(vty);
|
|
}
|
|
|
|
for (i = 0; i < array_active(vty_serv_thread); i++)
|
|
if ((serv_thread = array_get(vty_serv_thread, i)) != NULL)
|
|
{
|
|
thread_cancel(serv_thread);
|
|
array_get(vty_serv_thread, i) = NULL;
|
|
close(i);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* Install vty's own commands like `who' command. */
|
|
void vty_init(void)
|
|
{
|
|
vty_user_t *user;
|
|
|
|
vtyvec = array_init(ARRAY_MIN_SIZE, MTYPE_VTY_TMP);
|
|
|
|
vty_serv_thread = array_init(ARRAY_MIN_SIZE, MTYPE_VTY_TMP);
|
|
|
|
vty_user = array_init(ARRAY_MIN_SIZE, MTYPE_VTY_TMP);
|
|
user = XMALLOC(MTYPE_VTY_TMP, sizeof(vty_user_t));
|
|
snprintf(user->username, VTY_USERNAME_LEN, "%s", VTY_USERNAME_DEFAULT);
|
|
snprintf(user->password, VTY_USERNAME_LEN, "%s", VTY_PASSWORD_DEFAULT);
|
|
user->level = 255;
|
|
array_append(vty_user, user, MTYPE_VTY_TMP);
|
|
|
|
vty_master = thread_master_create();
|
|
|
|
_vty_thread_init();
|
|
}
|
|
/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****************/
|