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.
		
		
		
		
		
			
		
			
	
	
		
			2764 lines
		
	
	
		
			71 KiB
		
	
	
	
		
			C
		
	
		
		
			
		
	
	
			2764 lines
		
	
	
		
			71 KiB
		
	
	
	
		
			C
		
	
|   
											2 months ago
										 | /******************************************************************************
 | ||
|  |  * 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; | ||
|  | } | ||
|  | 
 | ||
|  | 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) | ||
|  | { | ||
|  |     struct sockaddr_in server; | ||
|  |     char buf[128] = {0}; | ||
|  |     time_t temp = 0; | ||
|  |      | ||
|  |     vty_out(vty, "%s%s", CL_COPYRIGHT, VTY_NEWLINE); | ||
|  |     vty_out(vty, "%s %s (%x).%s", PROGNAME, host.version, device_info.dev_id, VTY_NEWLINE); | ||
|  |     vty_out(vty, "Compile date: %s%s", host.compile, VTY_NEWLINE); | ||
|  |     vty_out(vty, "Id: %03d.%03d%s", device_info.type_m, device_info.type_s, 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); | ||
|  |     server.sin_addr.s_addr = device_info.ip; | ||
|  |     vty_out(vty, "ip: %s ", inet_ntoa(server.sin_addr)); | ||
|  |     server.sin_addr.s_addr = device_info.mask; | ||
|  |     vty_out(vty, "%s%s", inet_ntoa(server.sin_addr), VTY_NEWLINE); | ||
|  |     server.sin_addr.s_addr = device_info.gw; | ||
|  |     vty_out(vty, "route: %s%s", inet_ntoa(server.sin_addr), 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, device_info.hostname); | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | /* 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 if(VTY_CMD == vty->type) | ||
|  |     { | ||
|  |         va_start(args, format); | ||
|  |         vtycmd_print(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%s", VTY_NEWLINE, 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) | ||
|  | { | ||
|  |     vty_t *vty = NULL; | ||
|  |     char *buf = NULL; | ||
|  |     uint32_t i = 0; | ||
|  |     bool is_vty = FALSE; | ||
|  | 
 | ||
|  |     if (vtycmd_connect()) | ||
|  |     { | ||
|  |         is_vty = TRUE; | ||
|  |         vtycmd_print(format, va); | ||
|  |     } | ||
|  |     else if(vtyvec) | ||
|  |     { | ||
|  |         for(i = 0; i < array_active(vtyvec); i++) | ||
|  |         if ((vty = array_get(vtyvec, i)) != NULL) | ||
|  |         { | ||
|  |             is_vty = TRUE; | ||
|  |             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); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!is_vty) | ||
|  |     { | ||
|  |         vprintf(format, va); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* 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 ****************/ |