Changeset 4299
- Timestamp:
- Nov 23, 2012 10:30:55 AM (12 years ago)
- Location:
- pjproject/branches/projects/cli
- Files:
-
- 11 edited
Legend:
- Unmodified
- Added
- Removed
-
pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/cli.h
r3231 r4299 34 34 * @defgroup PJLIB_UTIL_CLI Command Line Interface Framework 35 35 * @{ 36 * 36 * A CLI framework features an interface for defining command specification, 37 * parsing, and executing a command. 38 * It also features an interface to communicate with various front-ends, 39 * such as console, telnet. 40 * 41 \verbatim 42 | vid help Show this help screen | 43 | vid enable|disable Enable or disable video in next offer/answer | 44 | vid call add Add video stream for current call | 45 | vid call cap N ID Set capture dev ID for stream #N in current call | 46 | disable_codec g711|g722 Show this help screen | 47 <CMD name='vid' id='0' desc=""> 48 <CMD name='help' id='0' desc='' /> 49 <CMD name='enable' id='0' desc='' /> 50 <CMD name='disable' id='0' desc='' /> 51 <CMD name='call' id='0' desc='' > 52 <CMD name='add' id='101' desc='...' /> 53 <CMD name='cap' id='102' desc='...' > 54 <ARG name='streamno' type='int' desc='...' id='1'/> 55 <ARG name='devid' type='int' optional='1' id='2'/> 56 </CMD> 57 </CMD> 58 </CMD> 59 <CMD name='disable_codec' id=0 desc=""> 60 <ARG name='codec_list' type='choice' id='3'> 61 <CHOICE value='g711'/> 62 <CHOICE value='g722'/> 63 </ARG> 64 </CMD> 65 \endverbatim 37 66 */ 38 67 … … 50 79 51 80 /** 52 * Reserved command id constants.53 */54 typedef enum pj_cli_std_cmd_id55 {56 /**57 * Constant to indicate an invalid command id.58 */59 PJ_CLI_INVALID_CMD_ID = -1,60 61 /**62 * A special command id to indicate that a command id denotes63 * a command group.64 */65 PJ_CLI_CMD_ID_GROUP = -266 67 } pj_cli_std_cmd_id;68 69 70 /**71 81 * This describes the parameters to be specified when creating a CLI 72 82 * application with pj_cli_create(). Application MUST initialize this … … 96 106 } pj_cli_cfg; 97 107 98 99 /** 100 * This describes the type of an argument (pj_cli_arg_spec). 101 */ 102 typedef enum pj_cli_arg_type 103 { 104 /** 105 * Unformatted string. 106 */ 107 PJ_CLI_ARG_TEXT, 108 109 /** 110 * An integral number. 111 */ 112 PJ_CLI_ARG_INT 113 114 } pj_cli_arg_type; 115 116 /** 117 * This structure describe the specification of a command argument. 118 */ 119 typedef struct pj_cli_arg_spec 120 { 121 /** 122 * Argument name. 123 */ 124 pj_str_t name; 125 126 /** 127 * Helpful description of the argument. This text will be used when 128 * displaying help texts for the command/argument. 129 */ 130 pj_str_t desc; 131 132 /** 133 * Argument type, which will be used for rendering the argument and 134 * to perform basic validation against an input value. 135 */ 136 pj_cli_arg_type type; 137 138 } pj_cli_arg_spec; 139 108 /** 109 * Type of argument id. 110 */ 111 typedef int pj_cli_arg_id; 140 112 141 113 /** … … 153 125 */ 154 126 typedef struct pj_cli_front_end pj_cli_front_end; 127 128 /** 129 * Forward declaration for CLI argument spec structure. 130 */ 131 typedef struct pj_cli_arg_spec pj_cli_arg_spec; 155 132 156 133 /** … … 173 150 } pj_cli_cmd_val; 174 151 175 176 177 /** 178 * This specifies the callback type for command handlers, which will be 179 * executed when the specified command is invoked. 180 * 181 * @param sess The CLI session where the command is invoked. 182 * @param cmd_val The command that is specified by the user. 183 * 184 * @return Return the status of the command execution. 185 */ 186 typedef pj_status_t (*pj_cli_cmd_handler)(pj_cli_cmd_val *cval); 187 188 /** 189 * This structure describes the full specification of a CLI command. A CLI 190 * command mainly consists of the name of the command, zero or more arguments, 191 * and a callback function to be called to execute the command. 192 * 193 * Application can create this specification by forming an XML document and 194 * calling pj_cli_create_cmd_from_xml() to instantiate the spec. A sample XML 195 * document containing a command spec is as follows: 196 * 197 \verbatim 198 <CMD name='makecall' id='101' sc='m,mc' desc='Make outgoing call'> 199 <ARGS> 200 <ARG name='target' type='text' desc='The destination'/> 201 </ARGS> 202 </CMD> 203 \endverbatim 204 205 */ 206 struct pj_cli_cmd_spec 152 /** 153 * This structure contains the hints information for the end user. 154 * This structure could contain either command or argument information. 155 * The front-end will format the information and present it to the user. 156 */ 157 typedef struct pj_cli_hint_info 207 158 { 208 159 /** 209 * To make list of child cmds. 210 */ 211 PJ_DECL_LIST_MEMBER(struct pj_cli_cmd_spec); 212 213 /** 214 * Command ID assigned to this command by the application during command 215 * creation. If this value is PJ_CLI_CMD_ID_GROUP (-2), then this is 216 * a command group and it can't be executed. 217 */ 218 pj_cli_cmd_id id; 219 220 /** 221 * The command name. 160 * The hint value. 222 161 */ 223 162 pj_str_t name; 224 163 225 164 /** 226 * The full description of the command. 165 * The hint type. 166 */ 167 pj_str_t type; 168 169 /** 170 * Helpful description of the hint value. 227 171 */ 228 172 pj_str_t desc; 229 173 230 /** 231 * Number of optional shortcuts 232 */ 233 unsigned sc_cnt; 234 235 /** 236 * Optional array of shortcuts, if any. Shortcut is a short name version 237 * of the command. If the command doesn't have any shortcuts, this 238 * will be initialized to NULL. 239 */ 240 pj_str_t *sc; 241 242 /** 243 * The command handler, to be executed when a command matching this command 244 * specification is invoked by the end user. The value may be NULL if this 245 * is a command group. 246 */ 247 pj_cli_cmd_handler handler; 248 249 /** 250 * Number of arguments. 251 */ 252 unsigned arg_cnt; 253 254 /** 255 * Array of arguments. 256 */ 257 pj_cli_arg_spec *arg; 258 259 /** 260 * Child commands, if any. A command will only have subcommands if it is 261 * a group. If the command doesn't have subcommands, this field will be 262 * initialized with NULL. 263 */ 264 pj_cli_cmd_spec *sub_cmd; 265 }; 266 267 268 /** 269 * This contains extra parameters to be specified when calling pj_cli_exec(). 174 } pj_cli_hint_info; 175 176 /** 177 * This structure contains extra information returned by pj_cli_sess_exec()/ 178 * pj_cli_sess_parse(). 270 179 * Upon return from the function, various other fields in this structure will 271 180 * be set by the function. 272 *273 * Application must call pj_cli_exec_info_default() to initialize this274 * structure with its default values.275 181 */ 276 182 typedef struct pj_cli_exec_info … … 296 202 297 203 /** 298 * If pj_cli_exec() fails because an argument is missing (the function 299 * returned PJ_CLI_EMISSINGARG error), this field will be set to the 300 * index of the missing argument. This is useful to give more helpful 301 * error info to the end user, or to facilitate a more interactive 302 * input display. 303 */ 304 int arg_idx; 204 * The number of hint elements 205 **/ 206 unsigned hint_cnt; 207 208 /** 209 * If pj_cli_sess_parse() fails because of a missing argument or ambigous 210 * command/argument, the function returned PJ_CLI_EMISSINGARG or 211 * PJ_CLI_EAMBIGUOUS error. 212 * This field will contain the hint information. This is useful to give 213 * helpful information to the end_user. 214 */ 215 pj_cli_hint_info hint[PJ_CLI_MAX_HINTS]; 305 216 306 217 } pj_cli_exec_info; 307 218 219 /** 220 * This structure contains the information returned from the dynamic 221 * argument callback. 222 */ 223 typedef struct pj_cli_arg_choice_val 224 { 225 /** 226 * The argument choice value 227 */ 228 pj_str_t value; 229 230 /** 231 * Helpful description of the choice value. This text will be used when 232 * displaying the help texts for the choice value 233 */ 234 pj_str_t desc; 235 236 } pj_cli_arg_choice_val; 237 238 /** 239 * This structure contains the parameters for pj_cli_arg_get_dyn_choice_val 240 */ 241 typedef struct pj_cli_dyn_choice_param 242 { 243 /** 244 * The session on which the command was executed on. 245 */ 246 pj_cli_sess *sess; 247 248 /** 249 * The command being processed. 250 */ 251 pj_cli_cmd_spec *cmd; 252 253 /** 254 * The argument id. 255 */ 256 pj_cli_arg_id arg_id; 257 258 /** 259 * The maximum number of values that the choice can hold. 260 */ 261 unsigned max_cnt; 262 263 /** 264 * The pool to allocate memory from. 265 */ 266 pj_pool_t *pool; 267 268 /** 269 * The choice values count. 270 */ 271 unsigned cnt; 272 273 /** 274 * Array containing the valid choice values. 275 */ 276 pj_cli_arg_choice_val choice[PJ_CLI_MAX_CHOICE_VAL]; 277 } pj_cli_dyn_choice_param; 278 279 /** 280 * This specifies the callback type for argument handlers, which will be 281 * called to get the valid values of the choice type arguments. 282 */ 283 typedef void (*pj_cli_arg_get_dyn_choice_val) (pj_cli_dyn_choice_param *param); 284 285 /** 286 * This specifies the function to get the id of the specified command 287 * 288 * @param cmd The specified command. 289 * 290 * @return The command id 291 */ 292 PJ_DECL(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd); 293 294 /** 295 * This specifies the callback type for command handlers, which will be 296 * executed when the specified command is invoked. 297 * 298 * @param cmd_val The command that is specified by the user. 299 * 300 * @return Return the status of the command execution. 301 */ 302 typedef pj_status_t (*pj_cli_cmd_handler)(pj_cli_cmd_val *cval); 308 303 309 304 /** … … 313 308 */ 314 309 PJ_DECL(void) pj_cli_cfg_default(pj_cli_cfg *param); 315 316 /**317 * Initialize pj_cli_exec_info with its default values.318 *319 * @param param The param to be initialized.320 */321 PJ_DECL(void) pj_cli_exec_info_default(pj_cli_exec_info *param);322 310 323 311 /** … … 336 324 337 325 /** 326 * Write a log message to the specific CLI session. 327 * 328 * @param sess The CLI active session. 329 * @param buffer The message itself. 330 * @param len Length of this message. 331 */ 332 PJ_DECL(void) pj_cli_sess_write_msg(pj_cli_sess *sess, 333 const char *buffer, 334 int len); 335 336 /** 338 337 * Create a new CLI application instance. 339 338 * … … 345 344 PJ_DECL(pj_status_t) pj_cli_create(pj_cli_cfg *cfg, 346 345 pj_cli_t **p_cli); 347 348 346 /** 349 347 * Get the internal parameter of the CLI instance. … … 354 352 */ 355 353 PJ_DECL(pj_cli_cfg*) pj_cli_get_param(pj_cli_t *cli); 356 357 354 358 355 /** … … 361 358 * application. 362 359 * 363 * See also pj_cli_ end_session() to end a session instead of quitting the360 * See also pj_cli_sess_end_session() to end a session instead of quitting the 364 361 * whole application. 365 362 * … … 371 368 PJ_DECL(void) pj_cli_quit(pj_cli_t *cli, pj_cli_sess *req, 372 369 pj_bool_t restart); 373 374 370 /** 375 371 * Check if application shutdown or restart has been requested. … … 380 376 */ 381 377 PJ_DECL(pj_bool_t) pj_cli_is_quitting(pj_cli_t *cli); 382 383 378 384 379 /** … … 392 387 PJ_DECL(pj_bool_t) pj_cli_is_restarting(pj_cli_t *cli); 393 388 394 395 389 /** 396 390 * Destroy a CLI application instance. This would also close all sessions … … 401 395 PJ_DECL(void) pj_cli_destroy(pj_cli_t *cli); 402 396 403 404 397 /** 405 398 * End the specified session, and destroy it to release all resources used … … 410 403 * @param sess The CLI session to be destroyed. 411 404 */ 412 PJ_DECL(void) pj_cli_ end_session(pj_cli_sess *sess);405 PJ_DECL(void) pj_cli_sess_end_session(pj_cli_sess *sess); 413 406 414 407 /** … … 435 428 * @param p_cmd Optional pointer to store the newly created 436 429 * specification. 430 * @param get_choice Function handler for the argument. Specify this for 431 * dynamic choice type arguments. 437 432 * 438 433 * @return PJ_SUCCESS on success, or the appropriate error code. … … 442 437 const pj_str_t *xml, 443 438 pj_cli_cmd_handler handler, 444 pj_cli_cmd_spec *p_cmd); 439 pj_cli_cmd_spec **p_cmd, 440 pj_cli_arg_get_dyn_choice_val get_choice); 441 /** 442 * Initialize pj_cli_exec_info with its default values. 443 * 444 * @param param The param to be initialized. 445 */ 446 PJ_DECL(void) pj_cli_exec_info_default(pj_cli_exec_info *param); 445 447 446 448 /** … … 465 467 * @param cmdline The command line string to be parsed. 466 468 * @param val Structure to store the parsing result. 469 * @param pool The pool to allocate memory from. 467 470 * @param info Additional info to be returned regarding the parsing. 468 471 * … … 472 475 * - PJ_EINVAL: invalid parameter to this function. 473 476 * - PJ_ENOTFOUND: command is not found. 477 * - PJ_CLI_EAMBIGUOUS: command/argument is ambiguous. 474 478 * - PJ_CLI_EMISSINGARG: missing argument. 475 479 * - PJ_CLI_EINVARG: invalid command argument. … … 478 482 * application to end it's main loop. 479 483 */ 480 PJ_DECL(pj_status_t) pj_cli_parse(pj_cli_sess *sess, 481 char *cmdline, 482 pj_cli_cmd_val *val, 483 pj_cli_exec_info *info); 484 PJ_DECL(pj_status_t) pj_cli_sess_parse(pj_cli_sess *sess, 485 char *cmdline, 486 pj_cli_cmd_val *val, 487 pj_pool_t *pool, 488 pj_cli_exec_info *info); 484 489 485 490 /** … … 487 492 * the appropriate command and verify whether the string matches the command 488 493 * specifications. If matches, the command will be executed, and the return 489 * value of the command will be set in the \a cmd_ret field of the \a eparam494 * value of the command will be set in the \a cmd_ret field of the \a info 490 495 * argument, if specified. 491 496 * 492 * Please also see pj_cli_parse() for more info regarding the cmdline format. 497 * Please also see pj_cli_sess_parse() for more info regarding the cmdline 498 * format. 493 499 * 494 500 * @param sess The CLI session. 495 * @param cmdline The command line string to be executed. See the 496 * description of pj_cli_parse() API for more info 497 * regarding the cmdline format. 501 * @param cmdline The command line string to be executed. 502 * @param pool The pool to allocate memory from. 498 503 * @param info Optional pointer to receive additional information 499 504 * related to the execution of the command (such as … … 504 509 * of the handler itself will be returned in \a info 505 510 * argument, if specified). Please see the return value 506 * of pj_cli_ parse() for possible return values.507 */ 508 pj_status_t pj_cli_exec(pj_cli_sess *sess,509 510 pj_cli_exec_info *info); 511 511 * of pj_cli_sess_parse() for possible return values. 512 */ 513 PJ_DECL(pj_status_t) pj_cli_sess_exec(pj_cli_sess *sess, 514 char *cmdline, 515 pj_pool_t *pool, 516 pj_cli_exec_info *info); 512 517 513 518 /** -
pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/cli_console.h
r3231 r4299 51 51 int log_level; 52 52 53 /** 54 * Specify text message as a prompt string to user. 55 * 56 * Default: empty 57 */ 58 pj_str_t prompt_str; 59 53 60 } pj_cli_console_cfg; 54 61 … … 83 90 84 91 /** 85 * Retrieve a cmdline from console stdin. Application should use this 86 * instead of the standard gets() or fgets(), since unlike these functions, 87 * this is able to end the blocking when pj_cli_quit() is called by other 88 * session. This function requires a thread support. 89 * 90 * Note that this function would also remove the trailing newlines from the 91 * input, if any. 92 * Retrieve a cmdline from console stdin and process the input accordingly. 92 93 * 93 94 * @param sess The CLI session. … … 97 98 * @return PJ_SUCCESS if an input was read 98 99 */ 99 PJ_DECL(pj_status_t) pj_cli_console_readline(pj_cli_sess *sess, 100 char *buf, 101 unsigned maxlen); 100 PJ_DECL(pj_status_t) pj_cli_console_process(pj_cli_sess *sess); 102 101 103 102 /** -
pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/cli_telnet.h
r3231 r4299 79 79 pj_str_t welcome_msg; 80 80 81 /** 82 * Specify text message as a prompt string to user. 83 * 84 * Default: empty 85 */ 86 pj_str_t prompt_str; 87 81 88 } pj_cli_telnet_cfg; 82 89 -
pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/config.h
r3231 r4299 304 304 305 305 /** 306 * Maximum number of hints. 307 * Default: 32 308 */ 309 #ifndef PJ_CLI_MAX_HINTS 310 # define PJ_CLI_MAX_HINTS 32 311 #endif 312 313 /** 306 314 * Maximum short name version (shortcuts) for a command. 307 315 * Default: 4 … … 343 351 #endif 344 352 353 /** 354 * Maximum number of argument values of choice type. 355 * Default: 16 356 */ 357 #ifndef PJ_CLI_MAX_CHOICE_VAL 358 # define PJ_CLI_MAX_CHOICE_VAL 16 359 #endif 360 361 /** 362 * Maximum number of command history. 363 * Default: 16 364 */ 365 #ifndef PJ_CLI_MAX_CMD_HISTORY 366 # define PJ_CLI_MAX_CMD_HISTORY 16 367 #endif 345 368 346 369 /** -
pjproject/branches/projects/cli/pjlib-util/include/pjlib-util/errno.h
r3231 r4299 394 394 * @hideinitializer 395 395 * End the current session. This is a special error code returned by 396 * pj_cli_ exec() to indicate that "exit" or equivalent command has been396 * pj_cli_sess_exec() to indicate that "exit" or equivalent command has been 397 397 * called to end the current session. 398 398 */ … … 426 426 /** 427 427 * @hideinitializer 428 * CLI command entered by user match with more than one command/argument 429 * specification. 430 */ 431 #define PJ_CLI_EAMBIGUOUS (PJLIB_UTIL_ERRNO_START+207)/* 320207 */ 432 /** 433 * @hideinitializer 428 434 * Telnet connection lost. 429 435 */ -
pjproject/branches/projects/cli/pjlib-util/src/pjlib-util/cli.c
r3231 r4299 43 43 #endif 44 44 45 /** 46 * This structure describes the full specification of a CLI command. A CLI 47 * command mainly consists of the name of the command, zero or more arguments, 48 * and a callback function to be called to execute the command. 49 * 50 * Application can create this specification by forming an XML document and 51 * calling pj_cli_create_cmd_from_xml() to instantiate the spec. A sample XML 52 * document containing a command spec is as follows: 53 */ 54 struct pj_cli_cmd_spec 55 { 56 /** 57 * To make list of child cmds. 58 */ 59 PJ_DECL_LIST_MEMBER(struct pj_cli_cmd_spec); 60 61 /** 62 * Command ID assigned to this command by the application during command 63 * creation. If this value is PJ_CLI_CMD_ID_GROUP (-2), then this is 64 * a command group and it can't be executed. 65 */ 66 pj_cli_cmd_id id; 67 68 /** 69 * The command name. 70 */ 71 pj_str_t name; 72 73 /** 74 * The full description of the command. 75 */ 76 pj_str_t desc; 77 78 /** 79 * Number of optional shortcuts 80 */ 81 unsigned sc_cnt; 82 83 /** 84 * Optional array of shortcuts, if any. Shortcut is a short name version 85 * of the command. If the command doesn't have any shortcuts, this 86 * will be initialized to NULL. 87 */ 88 pj_str_t *sc; 89 90 /** 91 * The command handler, to be executed when a command matching this command 92 * specification is invoked by the end user. The value may be NULL if this 93 * is a command group. 94 */ 95 pj_cli_cmd_handler handler; 96 97 /** 98 * Number of arguments. 99 */ 100 unsigned arg_cnt; 101 102 /** 103 * Array of arguments. 104 */ 105 pj_cli_arg_spec *arg; 106 107 /** 108 * Child commands, if any. A command will only have subcommands if it is 109 * a group. If the command doesn't have subcommands, this field will be 110 * initialized with NULL. 111 */ 112 pj_cli_cmd_spec *sub_cmd; 113 }; 114 45 115 struct pj_cli_t 46 116 { … … 55 125 }; 56 126 127 /** 128 * Reserved command id constants. 129 */ 130 typedef enum pj_cli_std_cmd_id 131 { 132 /** 133 * Constant to indicate an invalid command id. 134 */ 135 PJ_CLI_INVALID_CMD_ID = -1, 136 137 /** 138 * A special command id to indicate that a command id denotes 139 * a command group. 140 */ 141 PJ_CLI_CMD_ID_GROUP = -2 142 143 } pj_cli_std_cmd_id; 144 145 /** 146 * This describes the type of an argument (pj_cli_arg_spec). 147 */ 148 typedef enum pj_cli_arg_type 149 { 150 /** 151 * Unformatted string. 152 */ 153 PJ_CLI_ARG_TEXT, 154 155 /** 156 * An integral number. 157 */ 158 PJ_CLI_ARG_INT, 159 160 /** 161 * Choice type 162 */ 163 PJ_CLI_ARG_CHOICE 164 165 } pj_cli_arg_type; 166 167 static const struct 168 { 169 const pj_str_t msg; 170 } arg_type[] = 171 { 172 {"Text", 4}, 173 {"Int", 3}, 174 {"Choice", 6} 175 }; 176 177 /** 178 * This structure describe the specification of a command argument. 179 */ 180 struct pj_cli_arg_spec 181 { 182 /** 183 * Argument id 184 */ 185 pj_cli_arg_id id; 186 187 /** 188 * Argument name. 189 */ 190 pj_str_t name; 191 192 /** 193 * Helpful description of the argument. This text will be used when 194 * displaying help texts for the command/argument. 195 */ 196 pj_str_t desc; 197 198 /** 199 * Argument type, which will be used for rendering the argument and 200 * to perform basic validation against an input value. 201 */ 202 pj_cli_arg_type type; 203 204 /** 205 * Argument status 206 */ 207 pj_bool_t optional; 208 209 /** 210 * Static Choice Values count 211 */ 212 unsigned stat_choice_cnt; 213 214 /** 215 * Static Choice Values 216 */ 217 pj_cli_arg_choice_val *stat_choice_val; 218 219 /** 220 * Argument callback to get the valid values 221 */ 222 pj_cli_arg_get_dyn_choice_val get_dyn_choice; 223 224 }; 225 226 /** 227 * This describe the parse mode of the command line 228 */ 229 typedef enum pj_cli_parse_mode { 230 PARSE_NONE, 231 PARSE_COMPLETION, /* Complete the command line */ 232 PARSE_NEXT_AVAIL, /* Find the next available command line */ 233 PARSE_EXEC /* Exec the command line */ 234 } pj_cli_parse_mode; 235 236 /** 237 * This is used to get the matched command/argument from the 238 * command/argument structure. 239 * 240 * @param sess The session on which the command is execute on. 241 * @param cmd The active command. 242 * @param cmd_val The command value to match. 243 * @param argc The number of argument that the 244 * current active command have. 245 * @param pool The memory pool to allocate memory. 246 * @param get_cmd Set true to search matching command from sub command. 247 * @param parse_mode The parse mode. 248 * @param info The output information containing any hints for 249 * matching command/arg. 250 * @return This function return the status of the 251 * matching process.Please see the return value 252 * of pj_cli_sess_parse() for possible return values. 253 */ 254 static pj_status_t get_available_cmds(pj_cli_sess *sess, 255 pj_cli_cmd_spec *cmd, 256 pj_str_t *cmd_val, 257 unsigned argc, 258 pj_pool_t *pool, 259 pj_bool_t get_cmd, 260 pj_cli_parse_mode parse_mode, 261 pj_cli_cmd_spec **p_cmd, 262 pj_cli_exec_info *info); 263 264 PJ_DEF(pj_cli_cmd_id) pj_cli_get_cmd_id(const pj_cli_cmd_spec *cmd) 265 { 266 return cmd->id; 267 } 268 57 269 PJ_DEF(void) pj_cli_cfg_default(pj_cli_cfg *param) 58 270 { … … 86 298 fe = fe->next; 87 299 } 300 } 301 302 PJ_DECL(void) pj_cli_sess_write_msg(pj_cli_sess *sess, 303 const char *buffer, 304 int len) 305 { 306 struct pj_cli_front_end *fe; 307 308 pj_assert(sess); 309 310 fe = sess->fe; 311 if (fe->op && fe->op->on_write_log) 312 (*fe->op->on_write_log)(fe, 0, buffer, len); 88 313 } 89 314 … … 102 327 return PJ_SUCCESS; 103 328 case CLI_CMD_EXIT: 104 pj_cli_ end_session(cval->sess);329 pj_cli_sess_end_session(cval->sess); 105 330 return PJ_CLI_EEXIT; 106 331 default: … … 109 334 } 110 335 336 #define print_(arg) \ 337 do { \ 338 unsigned d = pj_log_get_decor(); \ 339 pj_log_set_decor(0); \ 340 PJ_LOG(1, arg); \ 341 pj_log_set_decor(d); \ 342 } while (0) 343 344 /** 345 * Example to send the command structure to all cli session 346 **/ 347 PJ_DEF(void) pj_cli_log_command_struct(pj_cli_t *cli, pj_str_t *indent, pj_cli_cmd_spec *in_cmd) 348 { 349 static const pj_str_t CMD_SIGN = {"+", 1}; 350 static const pj_str_t ARG_SIGN = {"-", 1}; 351 352 if (cli) { 353 pj_cli_cmd_spec *cmd; 354 pj_pool_t *pool = pj_pool_create(cli->cfg.pf, "log_cmd", 64, 64, NULL); 355 cmd = (in_cmd)?in_cmd:&cli->root; 356 if (pool) { 357 pj_str_t modif_indent; 358 pj_cli_cmd_spec *child_cmd; 359 unsigned i; 360 char *indent_data; 361 pj_str_t *print_indent; 362 363 if (!indent) { 364 print_indent = PJ_POOL_ALLOC_T(pool, pj_str_t); 365 pj_strdup2(pool, print_indent, ""); 366 } else { 367 print_indent = indent; 368 } 369 indent_data = (char *)pj_pool_alloc(pool, print_indent->slen + 2); 370 modif_indent.ptr = indent_data; 371 modif_indent.slen = 0; 372 373 if (&cli->root != cmd) { 374 print_(("", "%.*s%.*s%.*s\r\n", 375 (int)print_indent->slen, print_indent->ptr, 376 (int)CMD_SIGN.slen, CMD_SIGN.ptr, 377 (int)cmd->name.slen, cmd->name.ptr)); 378 } 379 pj_strcpy(&modif_indent, print_indent); 380 pj_strcat2(&modif_indent, " "); 381 382 //print child commands 383 if (cmd->sub_cmd) { 384 child_cmd = cmd->sub_cmd->next; 385 while(child_cmd != cmd->sub_cmd) { 386 pj_cli_log_command_struct(cli, &modif_indent, child_cmd); 387 child_cmd = child_cmd->next; 388 } 389 } 390 391 //print argumen 392 for (i=0; i<cmd->arg_cnt;++i) { 393 pj_cli_arg_spec *arg = &cmd->arg[i]; 394 print_(("", "%.*s%.*s%.*s\r\n", 395 (int)modif_indent.slen, modif_indent.ptr, 396 (int)ARG_SIGN.slen, ARG_SIGN.ptr, 397 (int)arg->name.slen, arg->name.ptr)); 398 } 399 pj_pool_release(pool); 400 } 401 } 402 } 403 111 404 PJ_DEF(pj_status_t) pj_cli_create(pj_cli_cfg *cfg, 112 405 pj_cli_t **p_cli) 113 406 { 114 407 pj_pool_t *pool; 115 pj_cli_t *cli; 408 pj_cli_t *cli; 116 409 unsigned i; 410 /* This is an example of the command structure */ 117 411 char* cmd_xmls[] = { 118 412 "<CMD name='log' id='30000' sc='' desc='Change log level'>" 119 " <ARGS>" 120 " <ARG name='level' type='int' desc='Log level'/>" 121 " </ARGS>" 122 "</CMD>", 123 "<CMD name='exit' id='30001' sc='' desc='Exit session'>" 413 " <ARG name='level' type='int' optional='0' desc='Log level'/>" 414 "</CMD>", 415 "<CMD name='exit' id='30001' sc='' desc='Exit session'>" 124 416 "</CMD>", 125 417 }; … … 146 438 pj_str_t xml = pj_str(cmd_xmls[i]); 147 439 148 if (pj_cli_add_cmd_from_xml(cli, NULL, &xml, &cmd_handler, NULL ) !=440 if (pj_cli_add_cmd_from_xml(cli, NULL, &xml, &cmd_handler, NULL, NULL) != 149 441 PJ_SUCCESS) 150 442 TRACE_((THIS_FILE, "Failed to add command #%d", i)); 151 443 } 152 444 153 *p_cli = cli; 445 *p_cli = cli; 154 446 155 447 return PJ_SUCCESS; … … 224 516 } 225 517 226 PJ_DEF(void) pj_cli_ end_session(pj_cli_sess *sess)518 PJ_DEF(void) pj_cli_sess_end_session(pj_cli_sess *sess) 227 519 { 228 520 pj_assert(sess); … … 239 531 } 240 532 241 PJ_DEF(pj_status_t) pj_cli_add_cmd_from_xml(pj_cli_t *cli, 242 pj_cli_cmd_spec *group, 243 const pj_str_t *xml, 244 pj_cli_cmd_handler handler, 245 pj_cli_cmd_spec *p_cmd) 246 { 247 #define ERROR_(STATUS) \ 248 do {status = STATUS; goto on_exit;} while(0) 249 pj_pool_t *pool; 250 pj_xml_node *root; 533 /** 534 * This method is to parse and add the choice type 535 * argument values to command structure. 536 **/ 537 static pj_status_t pj_cli_add_choice_node(pj_cli_t *cli, 538 pj_xml_node *xml_node, 539 pj_cli_arg_spec *arg, 540 pj_cli_arg_get_dyn_choice_val get_choice) 541 { 542 pj_xml_node *choice_node; 543 pj_xml_node *sub_node; 544 pj_cli_arg_choice_val choice_values[PJ_CLI_MAX_CHOICE_VAL]; 545 pj_status_t status = PJ_SUCCESS; 546 547 sub_node = xml_node; 548 arg->type = PJ_CLI_ARG_CHOICE; 549 arg->get_dyn_choice = get_choice; 550 551 choice_node = sub_node->node_head.next; 552 while (choice_node != (pj_xml_node*)&sub_node->node_head) { 553 pj_xml_attr *choice_attr; 554 pj_cli_arg_choice_val *choice_val = &choice_values[arg->stat_choice_cnt]; 555 pj_bzero(choice_val, sizeof(*choice_val)); 556 557 choice_attr = choice_node->attr_head.next; 558 while (choice_attr != &choice_node->attr_head) { 559 if (!pj_stricmp2(&choice_attr->name, "value")) { 560 pj_strassign(&choice_val->value, &choice_attr->value); 561 } else if (!pj_stricmp2(&choice_attr->name, "desc")) { 562 pj_strassign(&choice_val->desc, &choice_attr->value); 563 } 564 } 565 ++(arg->stat_choice_cnt); 566 choice_node = choice_node->next; 567 } 568 if (arg->stat_choice_cnt > 0) { 569 unsigned i; 570 571 arg->stat_choice_val = (pj_cli_arg_choice_val *)pj_pool_zalloc(cli->pool, 572 arg->stat_choice_cnt * 573 sizeof(pj_cli_arg_choice_val)); 574 for (i = 0; i < arg->stat_choice_cnt; i++) { 575 pj_strdup(cli->pool, &arg->stat_choice_val[i].value, &choice_values[i].value); 576 pj_strdup(cli->pool, &arg->stat_choice_val[i].desc, &choice_values[i].desc); 577 } 578 } 579 return status; 580 } 581 582 /** 583 * This method is to parse and add the argument attribute to command structure. 584 **/ 585 static pj_status_t pj_cli_add_arg_node(pj_cli_t *cli, 586 pj_xml_node *xml_node, 587 pj_cli_cmd_spec *cmd, 588 pj_cli_arg_spec *arg, 589 pj_cli_arg_get_dyn_choice_val get_choice) 590 { 591 pj_xml_attr *attr; 592 pj_status_t status = PJ_SUCCESS; 593 pj_xml_node *sub_node = xml_node; 594 595 if (cmd->arg_cnt >= PJ_CLI_MAX_ARGS) 596 return PJ_CLI_ETOOMANYARGS; 597 598 pj_bzero(arg, sizeof(*arg)); 599 attr = sub_node->attr_head.next; 600 arg->optional = PJ_FALSE; 601 while (attr != &sub_node->attr_head) { 602 if (!pj_stricmp2(&attr->name, "name")) { 603 pj_strassign(&arg->name, &attr->value); 604 } else if (!pj_stricmp2(&attr->name, "id")) { 605 arg->id = pj_strtol(&attr->value); 606 } else if (!pj_stricmp2(&attr->name, "type")) { 607 if (!pj_stricmp2(&attr->value, "text")) { 608 arg->type = PJ_CLI_ARG_TEXT; 609 } else if (!pj_stricmp2(&attr->value, "int")) { 610 arg->type = PJ_CLI_ARG_INT; 611 } else if (!pj_stricmp2(&attr->value, "CHOICE")) { 612 /* Get choice value */ 613 pj_cli_add_choice_node(cli, xml_node, arg, get_choice); 614 } 615 } else if (!pj_stricmp2(&attr->name, "desc")) { 616 pj_strassign(&arg->desc, &attr->value); 617 } else if (!pj_stricmp2(&attr->name, "optional")) { 618 if (!pj_strcmp2(&attr->value, "1")) { 619 arg->optional = PJ_TRUE; 620 } 621 } 622 attr = attr->next; 623 } 624 cmd->arg_cnt++; 625 return status; 626 } 627 628 /** 629 * This method is to parse and add the command attribute to command structure. 630 **/ 631 static pj_status_t pj_cli_add_cmd_node(pj_cli_t *cli, 632 pj_cli_cmd_spec *group, 633 pj_xml_node *xml_node, 634 pj_cli_cmd_handler handler, 635 pj_cli_cmd_spec **p_cmd, 636 pj_cli_arg_get_dyn_choice_val get_choice) 637 { 638 pj_xml_node *root = xml_node; 251 639 pj_xml_attr *attr; 252 640 pj_xml_node *sub_node; … … 256 644 pj_status_t status = PJ_SUCCESS; 257 645 258 PJ_ASSERT_RETURN(cli && xml, PJ_EINVAL);259 260 /* Parse the xml */261 pool = pj_pool_create(cli->cfg.pf, "xml", 1024, 1024, NULL);262 if (!pool)263 return PJ_ENOMEM;264 root = pj_xml_parse(pool, xml->ptr, xml->slen);265 if (!root) {266 TRACE_((THIS_FILE, "Error: unable to parse XML"));267 ERROR_(PJ_CLI_EBADXML);268 }269 270 646 if (pj_stricmp2(&root->name, "CMD")) 271 ERROR_(PJ_EINVAL);647 return PJ_EINVAL; 272 648 273 649 /* Initialize the command spec */ … … 279 655 if (!pj_stricmp2(&attr->name, "name")) { 280 656 pj_strltrim(&attr->value); 281 pj_strrtrim(&attr->value);282 /* Check whether command with the specified name already exists */283 657 if (!attr->value.slen || 284 658 pj_hash_get(cli->hash, attr->value.ptr, 285 659 attr->value.slen, NULL)) 286 660 { 287 ERROR_(PJ_CLI_EBADNAME);661 return PJ_CLI_EBADNAME; 288 662 } 289 290 663 pj_strdup(cli->pool, &cmd->name, &attr->value); 291 664 } else if (!pj_stricmp2(&attr->name, "id")) { … … 324 697 PJ_CATCH_ANY { 325 698 pj_scan_fini(&scanner); 326 ERROR_(PJ_GET_EXCEPTION());699 return (PJ_GET_EXCEPTION()); 327 700 } 328 701 PJ_END; … … 331 704 pj_strdup(cli->pool, &cmd->desc, &attr->value); 332 705 } 333 334 706 attr = attr->next; 335 707 } 336 708 337 /* Get the command arguments */709 /* Get the command childs/arguments */ 338 710 sub_node = root->node_head.next; 339 711 while (sub_node != (pj_xml_node*)&root->node_head) { 340 if (!pj_stricmp2(&sub_node->name, "ARGS")) { 341 pj_xml_node *arg_node; 342 343 arg_node = sub_node->node_head.next; 344 while (arg_node != (pj_xml_node*)&sub_node->node_head) { 345 if (!pj_stricmp2(&arg_node->name, "ARG")) { 346 pj_cli_arg_spec *arg; 347 348 if (cmd->arg_cnt >= PJ_CLI_MAX_ARGS) 349 ERROR_(PJ_CLI_ETOOMANYARGS); 350 arg = &args[cmd->arg_cnt]; 351 pj_bzero(arg, sizeof(*arg)); 352 attr = arg_node->attr_head.next; 353 while (attr != &arg_node->attr_head) { 354 if (!pj_stricmp2(&attr->name, "name")) { 355 pj_strassign(&arg->name, &attr->value); 356 } else if (!pj_stricmp2(&attr->name, "type")) { 357 if (!pj_stricmp2(&attr->value, "text")) { 358 arg->type = PJ_CLI_ARG_TEXT; 359 } else if (!pj_stricmp2(&attr->value, "int")) { 360 arg->type = PJ_CLI_ARG_INT; 361 } 362 } else if (!pj_stricmp2(&attr->name, "desc")) { 363 pj_strassign(&arg->desc, &attr->value); 364 } 365 366 attr = attr->next; 367 } 368 cmd->arg_cnt++; 369 } 370 371 arg_node = arg_node->next; 372 } 712 if (!pj_stricmp2(&sub_node->name, "CMD")) { 713 status = pj_cli_add_cmd_node(cli, cmd, sub_node, handler, NULL, 714 get_choice); 715 if (status != PJ_SUCCESS) 716 return status; 717 } else if (!pj_stricmp2(&sub_node->name, "ARG")) { 718 /* Get argument attribute */ 719 status = pj_cli_add_arg_node(cli, sub_node, cmd, &args[cmd->arg_cnt], 720 get_choice); 721 if (status != PJ_SUCCESS) 722 return status; 373 723 } 374 724 sub_node = sub_node->next; 375 725 } 376 726 377 if (cmd->id == PJ_CLI_CMD_ID_GROUP) {378 /* Command group shouldn't have any shortcuts nor arguments */379 if (!cmd->sc_cnt || !cmd->arg_cnt)380 ERROR_(PJ_EINVAL);381 cmd->sub_cmd = PJ_POOL_ALLOC_T(cli->pool, struct pj_cli_cmd_spec);382 pj_list_init(cmd->sub_cmd);383 }384 385 727 if (!cmd->name.slen) 386 ERROR_(PJ_CLI_EBADNAME);728 return PJ_CLI_EBADNAME; 387 729 388 730 if (cmd->arg_cnt) { … … 395 737 pj_strdup(cli->pool, &cmd->arg[i].desc, &args[i].desc); 396 738 cmd->arg[i].type = args[i].type; 739 cmd->arg[i].optional = args[i].optional; 397 740 } 398 741 } … … 414 757 cmd->handler = handler; 415 758 416 if (group) 759 if (group) { 760 if (!group->sub_cmd) { 761 group->sub_cmd = PJ_POOL_ALLOC_T(cli->pool, struct pj_cli_cmd_spec); 762 pj_list_init(group->sub_cmd); 763 } 417 764 pj_list_push_back(group->sub_cmd, cmd); 418 else765 } else { 419 766 pj_list_push_back(cli->root.sub_cmd, cmd); 767 } 420 768 421 769 if (p_cmd) 422 p_cmd = cmd; 423 424 on_exit: 770 *p_cmd = cmd; 771 772 return status; 773 } 774 775 PJ_DEF(pj_status_t) pj_cli_add_cmd_from_xml(pj_cli_t *cli, 776 pj_cli_cmd_spec *group, 777 const pj_str_t *xml, 778 pj_cli_cmd_handler handler, 779 pj_cli_cmd_spec **p_cmd, 780 pj_cli_arg_get_dyn_choice_val get_choice) 781 { 782 pj_pool_t *pool; 783 pj_xml_node *root; 784 pj_status_t status = PJ_SUCCESS; 785 786 PJ_ASSERT_RETURN(cli && xml, PJ_EINVAL); 787 788 /* Parse the xml */ 789 pool = pj_pool_create(cli->cfg.pf, "xml", 1024, 1024, NULL); 790 if (!pool) 791 return PJ_ENOMEM; 792 793 root = pj_xml_parse(pool, xml->ptr, xml->slen); 794 if (!root) { 795 TRACE_((THIS_FILE, "Error: unable to parse XML")); 796 pj_pool_release(pool); 797 return PJ_CLI_EBADXML; 798 } 799 status = pj_cli_add_cmd_node(cli, group, root, handler, p_cmd, get_choice); 425 800 pj_pool_release(pool); 426 427 801 return status; 428 #undef ERROR_ 429 } 430 431 PJ_DEF(pj_status_t) pj_cli_parse(pj_cli_sess *sess,432 char *cmdline,433 pj_cli_cmd_val *val,434 pj_cli_exec_info *info)435 { 802 } 803 804 PJ_DEF(pj_status_t) pj_cli_sess_parse(pj_cli_sess *sess, 805 char *cmdline, 806 pj_cli_cmd_val *val, 807 pj_pool_t *pool, 808 pj_cli_exec_info *info) 809 { 436 810 pj_scanner scanner; 437 811 pj_str_t str; 438 int len; 439 pj_cli_exec_info einfo; 812 int len; 813 pj_cli_cmd_spec *cmd; 814 pj_cli_cmd_spec *next_cmd; 815 pj_status_t status = PJ_SUCCESS; 816 pj_cli_parse_mode parse_mode = PARSE_NONE; 440 817 441 818 PJ_USE_EXCEPTION; … … 443 820 PJ_ASSERT_RETURN(sess && cmdline && val, PJ_EINVAL); 444 821 445 if (!info) 446 info = &einfo; 822 PJ_UNUSED_ARG(pool); 823 824 str.slen = 0; 447 825 pj_cli_exec_info_default(info); 448 826 449 /* Parse the command line. */827 /* Set the parse mode based on the latest char. */ 450 828 len = pj_ansi_strlen(cmdline); 451 if (len > 0 && cmdline[len - 1] == '\ n') {829 if (len > 0 && cmdline[len - 1] == '\r') { 452 830 cmdline[--len] = 0; 453 if (len > 0 && cmdline[len - 1] == '\r') 454 cmdline[--len] = 0; 455 } 456 pj_scan_init(&scanner, cmdline, len, PJ_SCAN_AUTOSKIP_WS, 457 &on_syntax_error); 458 PJ_TRY { 459 pj_scan_get_until_chr(&scanner, " \t\r\n", &str); 460 461 /* Find the command from the hash table */ 462 val->cmd = pj_hash_get(sess->fe->cli->hash, str.ptr, 463 str.slen, NULL); 464 465 /* Error, command not found */ 466 if (!val->cmd) 467 PJ_THROW(PJ_ENOTFOUND); 468 469 info->cmd_id = val->cmd->id; 470 471 /* Parse the command arguments */ 472 val->argc = 1; 473 pj_strassign(&val->argv[0], &str); 474 while (!pj_scan_is_eof(&scanner)) { 475 if (*scanner.curptr == '\'' || *scanner.curptr == '"' || 476 *scanner.curptr == '[' || *scanner.curptr == '{') 477 { 478 pj_scan_get_quotes(&scanner, "'\"[{", "'\"]}", 4, &str); 479 /* Remove the quotes */ 480 str.ptr++; 481 str.slen -= 2; 482 } else { 483 pj_scan_get_until_chr(&scanner, " \t\r\n", &str); 484 } 485 if (val->argc == PJ_CLI_MAX_ARGS) 486 PJ_THROW(PJ_CLI_ETOOMANYARGS); 487 pj_strassign(&val->argv[val->argc++], &str); 488 } 489 490 if (!pj_scan_is_eof(&scanner)) { 491 PJ_THROW(PJ_CLI_EINVARG); 492 } 493 } 494 PJ_CATCH_ANY { 495 pj_scan_fini(&scanner); 496 return PJ_GET_EXCEPTION(); 497 } 498 PJ_END; 499 500 if ((val->argc - 1) < (int)val->cmd->arg_cnt) { 501 info->arg_idx = val->argc; 502 return PJ_CLI_EMISSINGARG; 503 } else if ((val->argc - 1) > (int)val->cmd->arg_cnt) { 504 info->arg_idx = val->cmd->arg_cnt + 1; 505 return PJ_CLI_ETOOMANYARGS; 506 } 507 831 parse_mode = PARSE_EXEC; 832 } else if (len > 0 && 833 (cmdline[len - 1] == '\t' || cmdline[len - 1] == '?')) { 834 835 cmdline[--len] = 0; 836 if (len == 0) { 837 parse_mode = PARSE_NEXT_AVAIL; 838 } else { 839 if (cmdline[len - 1] == ' ') 840 parse_mode = PARSE_NEXT_AVAIL; 841 else 842 parse_mode = PARSE_COMPLETION; 843 } 844 } 845 val->argc = 0; 846 info->err_pos = 0; 847 cmd = &sess->fe->cli->root; 848 if (len > 0) { 849 pj_scan_init(&scanner, cmdline, len, PJ_SCAN_AUTOSKIP_WS, 850 &on_syntax_error); 851 PJ_TRY { 852 val->argc = 0; 853 while (!pj_scan_is_eof(&scanner)) { 854 info->err_pos = scanner.curptr - scanner.begin; 855 if (*scanner.curptr == '\'' || *scanner.curptr == '"' || 856 *scanner.curptr == '[' || *scanner.curptr == '{') 857 { 858 pj_scan_get_quotes(&scanner, "'\"[{", "'\"]}", 4, &str); 859 /* Remove the quotes */ 860 str.ptr++; 861 str.slen -= 2; 862 } else { 863 pj_scan_get_until_chr(&scanner, " \t\r\n", &str); 864 } 865 ++val->argc; 866 if (val->argc == PJ_CLI_MAX_ARGS) 867 PJ_THROW(PJ_CLI_ETOOMANYARGS); 868 869 status = get_available_cmds(sess, cmd, &str, val->argc-1, 870 pool, PJ_TRUE, parse_mode, 871 &next_cmd, info); 872 873 if (status != PJ_SUCCESS) 874 PJ_THROW(status); 875 876 if (cmd != next_cmd) { 877 /* Found new command, set it as the active command */ 878 cmd = next_cmd; 879 val->argc = 1; 880 val->cmd = cmd; 881 } 882 if (parse_mode == PARSE_EXEC) 883 pj_strassign(&val->argv[val->argc-1], &info->hint->name); 884 else 885 pj_strassign(&val->argv[val->argc-1], &str); 886 887 } 888 889 if (!pj_scan_is_eof(&scanner)) { 890 PJ_THROW(PJ_CLI_EINVARG); 891 } 892 } 893 PJ_CATCH_ANY { 894 pj_scan_fini(&scanner); 895 return PJ_GET_EXCEPTION(); 896 } 897 PJ_END; 898 } 899 900 if ((parse_mode == PARSE_NEXT_AVAIL) || (parse_mode == PARSE_EXEC)) { 901 /* If exec mode, just get the matching argument */ 902 status = get_available_cmds(sess, cmd, NULL, val->argc, pool, 903 (parse_mode==PARSE_NEXT_AVAIL), 904 parse_mode, 905 NULL, info); 906 if ((status != PJ_SUCCESS) && (status != PJ_CLI_EINVARG)) { 907 pj_str_t data = pj_str(cmdline); 908 pj_strrtrim(&data); 909 data.ptr[data.slen] = ' '; 910 data.ptr[data.slen+1] = 0; 911 912 info->err_pos = pj_ansi_strlen(cmdline); 913 } 914 915 } else if (parse_mode == PARSE_COMPLETION) { 916 if (info->hint[0].name.slen > str.slen) { 917 pj_str_t *hint_info = &info->hint[0].name; 918 pj_memmove(&hint_info->ptr[0], &hint_info->ptr[str.slen], 919 info->hint[0].name.slen-str.slen); 920 hint_info->slen = info->hint[0].name.slen-str.slen; 921 } else { 922 info->hint[0].name.slen = 0; 923 } 924 } 508 925 val->sess = sess; 509 510 return PJ_SUCCESS; 511 } 512 513 pj_status_t pj_cli_exec(pj_cli_sess *sess,514 char *cmdline,515 926 return status; 927 } 928 929 PJ_DECL(pj_status_t) pj_cli_sess_exec(pj_cli_sess *sess, 930 char *cmdline, 931 pj_pool_t *pool, 932 pj_cli_exec_info *info) 516 933 { 517 934 pj_cli_cmd_val val; 518 935 pj_status_t status; 519 936 pj_cli_exec_info einfo; 937 pj_str_t cmd; 520 938 521 939 PJ_ASSERT_RETURN(sess && cmdline, PJ_EINVAL); 940 941 PJ_UNUSED_ARG(pool); 942 943 cmd.ptr = cmdline; 944 cmd.slen = pj_ansi_strlen(cmdline); 945 946 if (pj_strtrim(&cmd)->slen == 0) 947 return PJ_SUCCESS; 522 948 523 949 if (!info) 524 950 info = &einfo; 525 status = pj_cli_ parse(sess, cmdline, &val, info);951 status = pj_cli_sess_parse(sess, cmdline, &val, pool, info); 526 952 if (status != PJ_SUCCESS) 527 953 return status; 528 954 529 if ( val.cmd->handler) {955 if ((val.argc > 0) && (val.cmd->handler)) { 530 956 info->cmd_ret = (*val.cmd->handler)(&val); 531 957 if (info->cmd_ret == PJ_CLI_EINVARG || … … 536 962 return PJ_SUCCESS; 537 963 } 964 965 static pj_status_t pj_cli_insert_new_hint(pj_pool_t *pool, 966 const pj_str_t *name, 967 const pj_str_t *desc, 968 const pj_str_t *type, 969 pj_cli_exec_info *info) 970 { 971 pj_cli_hint_info *hint = &info->hint[info->hint_cnt]; 972 PJ_ASSERT_RETURN(pool && info, PJ_EINVAL); 973 974 pj_strdup(pool, &hint->name, name); 975 976 if (desc && (desc->slen > 0)) { 977 pj_strdup(pool, &hint->desc, desc); 978 } else { 979 hint->desc.slen = 0; 980 } 981 982 if (type && (type->slen > 0)) { 983 pj_strdup(pool, &hint->type, type); 984 } else { 985 hint->type.slen = 0; 986 } 987 988 ++info->hint_cnt; 989 return PJ_SUCCESS; 990 } 991 992 static pj_status_t get_match_cmds(pj_cli_cmd_spec *cmd, 993 const pj_str_t *cmd_val, 994 pj_pool_t *pool, 995 pj_cli_cmd_spec **p_cmd, 996 pj_cli_exec_info *info) 997 { 998 pj_status_t status = PJ_SUCCESS; 999 PJ_ASSERT_RETURN(cmd && pool && info && cmd_val, PJ_EINVAL); 1000 1001 if (p_cmd) 1002 *p_cmd = cmd; 1003 1004 /* Get matching command */ 1005 if (cmd->sub_cmd) { 1006 pj_cli_cmd_spec *child_cmd = cmd->sub_cmd->next; 1007 while (child_cmd != cmd->sub_cmd) { 1008 unsigned i; 1009 pj_bool_t found = PJ_FALSE; 1010 if (!pj_strnicmp(&child_cmd->name, cmd_val, cmd_val->slen)) { 1011 status = pj_cli_insert_new_hint(pool, &child_cmd->name, 1012 &child_cmd->desc, NULL, info); 1013 if (status != PJ_SUCCESS) 1014 return status; 1015 1016 found = PJ_TRUE; 1017 } 1018 for (i=0; i < child_cmd->sc_cnt; ++i) { 1019 static const pj_str_t SHORTCUT = {"SC", 2}; 1020 pj_str_t *sc = &child_cmd->sc[i]; 1021 PJ_ASSERT_RETURN(sc, PJ_EINVAL); 1022 1023 if (!pj_strnicmp(sc, cmd_val, cmd_val->slen)) { 1024 status = pj_cli_insert_new_hint(pool, sc, &child_cmd->desc, 1025 &SHORTCUT, info); 1026 if (status != PJ_SUCCESS) 1027 return status; 1028 1029 found = PJ_TRUE; 1030 } 1031 } 1032 if (found && p_cmd) { 1033 *p_cmd = child_cmd; 1034 } 1035 1036 child_cmd = child_cmd->next; 1037 } 1038 } 1039 return status; 1040 } 1041 1042 static pj_status_t get_match_args(pj_cli_sess *sess, 1043 pj_cli_cmd_spec *cmd, 1044 const pj_str_t *cmd_val, 1045 unsigned argc, 1046 pj_pool_t *pool, 1047 pj_cli_parse_mode parse_mode, 1048 pj_cli_exec_info *info) 1049 { 1050 pj_cli_arg_spec *arg; 1051 pj_status_t status = PJ_SUCCESS; 1052 1053 PJ_ASSERT_RETURN(cmd && pool && cmd_val && info, PJ_EINVAL); 1054 1055 if ((argc > cmd->arg_cnt) && (!cmd->sub_cmd)) { 1056 if (cmd_val->slen > 0) 1057 return PJ_CLI_ETOOMANYARGS; 1058 else 1059 return PJ_SUCCESS; 1060 } 1061 1062 if (cmd->arg_cnt > 0) { 1063 arg = &cmd->arg[argc-1]; 1064 PJ_ASSERT_RETURN(arg, PJ_EINVAL); 1065 if (arg->type == PJ_CLI_ARG_CHOICE) { 1066 unsigned j; 1067 pj_cli_dyn_choice_param dyn_choice_param; 1068 1069 for (j=0; j < arg->stat_choice_cnt; ++j) { 1070 pj_cli_arg_choice_val *choice_val = &arg->stat_choice_val[j]; 1071 1072 PJ_ASSERT_RETURN(choice_val, PJ_EINVAL); 1073 1074 if (!pj_strnicmp(&choice_val->value, cmd_val, cmd_val->slen)) { 1075 status = pj_cli_insert_new_hint(pool, &choice_val->value, 1076 &choice_val->desc, 1077 &arg_type[PJ_CLI_ARG_CHOICE].msg, 1078 info); 1079 if (status != PJ_SUCCESS) 1080 return status; 1081 } 1082 } 1083 /* Get the dynamic choice values */ 1084 dyn_choice_param.sess = sess; 1085 dyn_choice_param.cmd = cmd; 1086 dyn_choice_param.arg_id = arg->id; 1087 dyn_choice_param.max_cnt = PJ_CLI_MAX_CHOICE_VAL; 1088 dyn_choice_param.pool = pool; 1089 dyn_choice_param.cnt = 0; 1090 1091 (*arg->get_dyn_choice)(&dyn_choice_param); 1092 for (j=0; j < dyn_choice_param.cnt; ++j) { 1093 pj_cli_arg_choice_val *choice = &dyn_choice_param.choice[j]; 1094 pj_strassign(&info->hint[info->hint_cnt].name, &choice->value); 1095 pj_strassign(&info->hint[info->hint_cnt].desc, &choice->desc); 1096 ++info->hint_cnt; 1097 } 1098 } else { 1099 if (cmd_val->slen == 0) { 1100 if (info->hint_cnt == 0) { 1101 if (!((parse_mode == PARSE_EXEC) && (arg->optional))) { 1102 /* If exec mode, don't need to insert the hint if optional */ 1103 status = pj_cli_insert_new_hint(pool, &arg->name, &arg->desc, 1104 &arg_type[arg->type].msg, info); 1105 if (status != PJ_SUCCESS) 1106 return status; 1107 } 1108 if (!arg->optional) 1109 return PJ_CLI_EMISSINGARG; 1110 } 1111 } else { 1112 return pj_cli_insert_new_hint(pool, cmd_val, 1113 NULL, NULL, info); 1114 } 1115 } 1116 } 1117 return status; 1118 } 1119 1120 static pj_status_t get_available_cmds(pj_cli_sess *sess, 1121 pj_cli_cmd_spec *cmd, 1122 pj_str_t *cmd_val, 1123 unsigned argc, 1124 pj_pool_t *pool, 1125 pj_bool_t get_cmd, 1126 pj_cli_parse_mode parse_mode, 1127 pj_cli_cmd_spec **p_cmd, 1128 pj_cli_exec_info *info) 1129 { 1130 pj_status_t status = PJ_SUCCESS; 1131 pj_str_t *prefix; 1132 pj_str_t EMPTY_STR = {NULL, 0}; 1133 1134 prefix = cmd_val?(pj_strtrim(cmd_val)):(&EMPTY_STR); 1135 1136 info->hint_cnt = 0; 1137 1138 if (get_cmd) 1139 status = get_match_cmds(cmd, prefix, pool, p_cmd, info); 1140 if (argc > 0) 1141 status = get_match_args(sess, cmd, prefix, argc, pool, parse_mode, info); 1142 1143 if (status == PJ_SUCCESS) { 1144 if (prefix->slen > 0) { 1145 if (info->hint_cnt == 0) { 1146 status = PJ_CLI_EINVARG; 1147 } else if (info->hint_cnt > 1) { 1148 status = PJ_CLI_EAMBIGUOUS; 1149 } 1150 } else { 1151 if (info->hint_cnt > 0) 1152 status = PJ_CLI_EAMBIGUOUS; 1153 } 1154 } 1155 1156 return status; 1157 } 1158 -
pjproject/branches/projects/cli/pjlib-util/src/pjlib-util/cli_console.c
r3231 r4299 28 28 #include <pjlib-util/errno.h> 29 29 30 #if defined(PJ_LINUX) && PJ_LINUX != 0 || \ 31 defined(PJ_DARWINOS) && PJ_DARWINOS != 0 32 #include <termios.h> 33 34 static struct termios old, new; 35 36 /* Initialize new terminal i/o settings */ 37 void initTermios(int echo) 38 { 39 tcgetattr(0, &old); 40 new = old; 41 new.c_lflag &= ~ICANON; 42 new.c_lflag &= echo ? ECHO : ~ECHO; 43 tcsetattr(0, TCSANOW, &new); 44 } 45 46 /* Restore old terminal i/o settings */ 47 void resetTermios(void) 48 { 49 tcsetattr(0, TCSANOW, &old); 50 } 51 52 /* Read 1 character - echo defines echo mode */ 53 char getch_(int echo) 54 { 55 char ch; 56 initTermios(echo); 57 ch = getchar(); 58 resetTermios(); 59 return ch; 60 } 61 62 /* Read 1 character without echo */ 63 char getch(void) 64 { 65 return getch_(0); 66 } 67 68 #endif 69 70 /** 71 * This specify the state of input character parsing. 72 */ 73 typedef enum cmd_parse_state 74 { 75 ST_NORMAL, 76 ST_SCANMODE, 77 ST_ESC, 78 ST_ARROWMODE 79 } cmd_parse_state; 80 81 /** 82 * This structure contains the command line shown to the user. 83 */ 84 typedef struct console_recv_buf { 85 /** 86 * Buffer containing the characters, NULL terminated. 87 */ 88 unsigned char rbuf[PJ_CLI_MAX_CMDBUF]; 89 90 /** 91 * Current length of the command line. 92 */ 93 unsigned len; 94 95 /** 96 * Current cursor position. 97 */ 98 unsigned cur_pos; 99 } console_recv_buf; 100 101 typedef struct cmd_history 102 { 103 PJ_DECL_LIST_MEMBER(struct cmd_history); 104 pj_str_t command; 105 } cmd_history; 106 30 107 struct cli_console_fe 31 108 { … … 35 112 pj_thread_t *input_thread; 36 113 pj_bool_t thread_quit; 37 pj_sem_t *thread_sem; 114 pj_sem_t *thread_sem; 115 pj_cli_console_cfg cfg; 38 116 39 117 struct async_input_t 40 { 41 char *buf; 42 unsigned maxlen; 43 pj_sem_t *sem; 118 { 119 console_recv_buf recv_buf; 120 pj_sem_t *sem; 44 121 } input; 122 123 cmd_history *history; 124 cmd_history *active_history; 45 125 }; 126 127 static unsigned recv_buf_right_len(console_recv_buf *recv_buf) 128 { 129 return (recv_buf->len - recv_buf->cur_pos); 130 } 131 132 static pj_bool_t recv_buf_insert(console_recv_buf *recv_buf, 133 unsigned char *data) 134 { 135 if (recv_buf->len+1 >= PJ_CLI_MAX_CMDBUF) { 136 return PJ_FALSE; 137 } else { 138 if (*data == '\t' || *data == '?' || *data == '\r') { 139 /* Always insert to the end of line */ 140 recv_buf->rbuf[recv_buf->len] = *data; 141 } else { 142 /* Insert based on the current cursor pos */ 143 unsigned cur_pos = recv_buf->cur_pos; 144 unsigned rlen = recv_buf_right_len(recv_buf); 145 if (rlen > 0) { 146 /* Shift right characters */ 147 pj_memmove(&recv_buf->rbuf[cur_pos+1], 148 &recv_buf->rbuf[cur_pos], 149 rlen+1); 150 } 151 recv_buf->rbuf[cur_pos] = *data; 152 } 153 ++recv_buf->cur_pos; 154 ++recv_buf->len; 155 recv_buf->rbuf[recv_buf->len] = 0; 156 } 157 return PJ_TRUE; 158 } 159 160 static pj_bool_t recv_buf_backspace(console_recv_buf *recv_buf) 161 { 162 if ((recv_buf->cur_pos == 0) || (recv_buf->len == 0)) { 163 return PJ_FALSE; 164 } else { 165 unsigned rlen = recv_buf_right_len(recv_buf); 166 if (rlen) { 167 unsigned cur_pos = recv_buf->cur_pos; 168 pj_memmove(&recv_buf->rbuf[cur_pos-1], &recv_buf->rbuf[cur_pos], 169 rlen); 170 } 171 --recv_buf->cur_pos; 172 --recv_buf->len; 173 recv_buf->rbuf[recv_buf->len] = 0; 174 } 175 return PJ_TRUE; 176 } 46 177 47 178 static void cli_console_write_log(pj_cli_front_end *fe, int level, … … 87 218 pj_assert(param); 88 219 89 param->log_level = PJ_CLI_CONSOLE_LOG_LEVEL; 220 param->log_level = PJ_CLI_CONSOLE_LOG_LEVEL; 90 221 } 91 222 … … 109 240 sess = PJ_POOL_ZALLOC_T(pool, pj_cli_sess); 110 241 fe = PJ_POOL_ZALLOC_T(pool, struct cli_console_fe); 242 fe->history = PJ_POOL_ZALLOC_T(pool, struct cmd_history); 243 pj_list_init(fe->history); 244 fe->active_history = fe->history; 111 245 112 246 if (!param) { … … 129 263 pj_cli_register_front_end(cli, &fe->base); 130 264 265 if (fe->cfg.prompt_str.slen == 0) { 266 pj_str_t prompt_sign = pj_str(">>> "); 267 char *prompt_data = pj_pool_alloc(fe->pool, 5); 268 fe->cfg.prompt_str.ptr = prompt_data; 269 270 pj_strcpy(&fe->cfg.prompt_str, &prompt_sign); 271 prompt_data[4] = 0; 272 } 273 131 274 *p_sess = sess; 132 275 if (p_fe) … … 136 279 } 137 280 138 static int readline_thread(void * p) 139 { 281 static void send_prompt_str(pj_cli_sess *sess) 282 { 283 pj_str_t send_data; 284 char data_str[128]; 285 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; 286 287 send_data.ptr = &data_str[0]; 288 send_data.slen = 0; 289 290 pj_strcat(&send_data, &fe->cfg.prompt_str); 291 send_data.ptr[send_data.slen] = 0; 292 293 printf("%s", send_data.ptr); 294 } 295 296 static void send_err_arg(pj_cli_sess *sess, 297 const pj_cli_exec_info *info, 298 const pj_str_t *msg, 299 pj_bool_t with_return, 300 pj_bool_t with_last_cmd) 301 { 302 pj_str_t send_data; 303 char data_str[256]; 304 unsigned len; 305 unsigned i; 306 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; 307 console_recv_buf *recv_buf = &fe->input.recv_buf; 308 309 send_data.ptr = &data_str[0]; 310 send_data.slen = 0; 311 312 if (with_return) 313 pj_strcat2(&send_data, "\r\n"); 314 315 len = fe->cfg.prompt_str.slen + info->err_pos; 316 317 for (i=0;i<len;++i) { 318 pj_strcat2(&send_data, " "); 319 } 320 pj_strcat2(&send_data, "^"); 321 pj_strcat2(&send_data, "\r\n"); 322 pj_strcat(&send_data, msg); 323 pj_strcat(&send_data, &fe->cfg.prompt_str); 324 if (with_last_cmd) 325 pj_strcat2(&send_data, (char *)&recv_buf->rbuf[0]); 326 327 send_data.ptr[send_data.slen] = 0; 328 printf("%s", send_data.ptr); 329 } 330 331 static void send_return_key(pj_cli_sess *sess) 332 { 333 PJ_UNUSED_ARG(sess); 334 printf("\r\n"); 335 } 336 337 static void send_inv_arg(pj_cli_sess *sess, 338 const pj_cli_exec_info *info, 339 pj_bool_t with_return, 340 pj_bool_t with_last_cmd) 341 { 342 static const pj_str_t ERR_MSG = {"%Error : Invalid Arguments\r\n", 28}; 343 send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd); 344 } 345 346 static void send_too_many_arg(pj_cli_sess *sess, 347 const pj_cli_exec_info *info, 348 pj_bool_t with_return, 349 pj_bool_t with_last_cmd) 350 { 351 static const pj_str_t ERR_MSG = {"%Error : Too Many Arguments\r\n", 29}; 352 send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd); 353 } 354 355 static void send_ambi_arg(pj_cli_sess *sess, 356 const pj_cli_exec_info *info, 357 pj_bool_t with_return, 358 pj_bool_t with_last_cmd) 359 { 360 unsigned i; 361 pj_ssize_t j; 362 unsigned len; 363 pj_str_t send_data; 364 char data[1028]; 365 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; 366 console_recv_buf *recv_buf = &fe->input.recv_buf; 367 const pj_cli_hint_info *hint = info->hint; 368 pj_bool_t is_process_sc = PJ_FALSE; 369 pj_bool_t valid_type = PJ_FALSE; 370 pj_ssize_t max_length = 0; 371 const pj_str_t sc_type = pj_str("SC"); 372 send_data.ptr = &data[0]; 373 send_data.slen = 0; 374 375 if (with_return) 376 pj_strcat2(&send_data, "\r\n"); 377 378 len = fe->cfg.prompt_str.slen + info->err_pos; 379 380 for (i=0;i<len;++i) { 381 pj_strcat2(&send_data, " "); 382 } 383 pj_strcat2(&send_data, "^"); 384 385 /* Get the max length of the command name */ 386 for (i=0;i<info->hint_cnt;++i) { 387 if (hint[i].name.slen > max_length) { 388 max_length = hint[i].name.slen; 389 } 390 } 391 392 for (i=0;i<info->hint_cnt;++i) { 393 if ((&hint[i].type) && (hint[i].type.slen > 0)) { 394 valid_type = PJ_TRUE; 395 if (pj_stricmp(&hint[i].type, &sc_type) == 0) { 396 if (is_process_sc) { 397 pj_strcat2(&send_data, ", "); 398 } else { 399 pj_strcat2(&send_data, "\r\n\t Shorcut: "); 400 is_process_sc = PJ_TRUE; 401 } 402 pj_strcat(&send_data, &hint[i].name); 403 } else { 404 is_process_sc = PJ_FALSE; 405 } 406 } else { 407 valid_type = PJ_FALSE; 408 is_process_sc = PJ_FALSE; 409 } 410 411 if (!is_process_sc) { 412 pj_strcat2(&send_data, "\r\n\t"); 413 414 if (valid_type) { 415 pj_strcat2(&send_data, "<"); 416 pj_strcat(&send_data, &hint[i].type); 417 pj_strcat2(&send_data, ">"); 418 } else { 419 pj_strcat(&send_data, &hint[i].name); 420 } 421 422 if ((&hint[i].desc) && (hint[i].desc.slen > 0)) { 423 if (!valid_type) { 424 for (j=0;j<(max_length-hint[i].name.slen);++j) { 425 pj_strcat2(&send_data, " "); 426 } 427 } 428 pj_strcat2(&send_data, "\t"); 429 pj_strcat(&send_data, &hint[i].desc); 430 } 431 } 432 } 433 pj_strcat2(&send_data, "\r\n"); 434 pj_strcat(&send_data, &fe->cfg.prompt_str); 435 if (with_last_cmd) 436 pj_strcat2(&send_data, (char *)&recv_buf->rbuf[0]); 437 438 send_data.ptr[send_data.slen] = 0; 439 printf("%s", send_data.ptr); 440 } 441 442 static void send_comp_arg(pj_cli_exec_info *info) 443 { 444 pj_str_t send_data; 445 char data[128]; 446 447 pj_strcat2(&info->hint[0].name, " "); 448 449 send_data.ptr = &data[0]; 450 send_data.slen = 0; 451 452 pj_strcat(&send_data, &info->hint[0].name); 453 454 send_data.ptr[send_data.slen] = 0; 455 printf("%s", send_data.ptr); 456 } 457 458 static int compare_str(void *value, const pj_list_type *nd) 459 { 460 cmd_history *node = (cmd_history*)nd; 461 return (pj_strcmp((pj_str_t *)value, &node->command)); 462 } 463 464 static pj_status_t insert_history(pj_cli_sess *sess, 465 char *cmd_val) 466 { 467 cmd_history *in_history; 468 pj_str_t cmd; 469 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; 470 cmd.ptr = cmd_val; 471 cmd.slen = pj_ansi_strlen(cmd_val)-1; 472 473 if (cmd.slen == 0) 474 return PJ_SUCCESS; 475 476 PJ_ASSERT_RETURN(sess, PJ_EINVAL); 477 478 /* Find matching history */ 479 in_history = pj_list_search(fe->history, (void*)&cmd, compare_str); 480 if (!in_history) { 481 if (pj_list_size(fe->history) < PJ_CLI_MAX_CMD_HISTORY) { 482 char *data_history; 483 in_history = PJ_POOL_ZALLOC_T(fe->pool, cmd_history); 484 pj_list_init(in_history); 485 data_history = (char *)pj_pool_calloc(fe->pool, 486 sizeof(char), PJ_CLI_MAX_CMDBUF); 487 in_history->command.ptr = data_history; 488 in_history->command.slen = 0; 489 } else { 490 /* Get the oldest history */ 491 in_history = fe->history->prev; 492 } 493 } else { 494 pj_list_insert_nodes_after(in_history->prev, in_history->next); 495 } 496 pj_strcpy(&in_history->command, pj_strtrim(&cmd)); 497 pj_list_push_front(fe->history, in_history); 498 fe->active_history = fe->history; 499 500 return PJ_SUCCESS; 501 } 502 503 static pj_str_t* get_prev_history(pj_cli_sess *sess, pj_bool_t is_forward) 504 { 505 pj_str_t *retval; 506 pj_size_t history_size; 507 cmd_history *node; 508 cmd_history *root; 509 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; 510 511 PJ_ASSERT_RETURN(sess && fe, NULL); 512 513 node = fe->active_history; 514 root = fe->history; 515 history_size = pj_list_size(fe->history); 516 517 if (history_size == 0) { 518 return NULL; 519 } else { 520 if (is_forward) { 521 node = (node->next==root)?node->next->next:node->next; 522 } else { 523 node = (node->prev==root)?node->prev->prev:node->prev; 524 } 525 retval = &node->command; 526 fe->active_history = node; 527 } 528 return retval; 529 } 530 531 static pj_bool_t handle_alfa_num(console_recv_buf *recv_buf, 532 unsigned char *cdata) 533 { 534 if (recv_buf_right_len(recv_buf) > 0) { 535 char out_str[255]; 536 pj_memset(&out_str[0], 0, 255); 537 out_str[0] = *cdata; 538 pj_memcpy(&out_str[1], &recv_buf->rbuf[recv_buf->cur_pos], 539 recv_buf_right_len(recv_buf)); 540 pj_memset(&out_str[recv_buf_right_len(recv_buf)+1], '\b', 541 recv_buf_right_len(recv_buf)); 542 printf("%s", out_str); 543 } else { 544 printf("%c", *cdata); 545 } 546 return PJ_TRUE; 547 } 548 549 static pj_bool_t handle_backspace(console_recv_buf *recv_buf) 550 { 551 if (recv_buf_backspace(recv_buf)) { 552 if(recv_buf_right_len(recv_buf) > 0) { 553 char out_str[255]; 554 pj_memset(&out_str[0], 0, 255); 555 out_str[0] = '\b'; 556 pj_memcpy(&out_str[1], &recv_buf->rbuf[recv_buf->cur_pos], 557 recv_buf_right_len(recv_buf)); 558 out_str[recv_buf_right_len(recv_buf)+1] = ' '; 559 pj_memset(&out_str[recv_buf_right_len(recv_buf)+2], '\b', 560 recv_buf_right_len(recv_buf)+1); 561 printf("%s", out_str); 562 } else { 563 char out_str[4]; 564 out_str[0] = '\b'; 565 out_str[1] = ' '; 566 out_str[2] = '\b'; 567 out_str[3] = 0; 568 printf("%s", out_str); 569 } 570 return PJ_TRUE; 571 } 572 return PJ_FALSE; 573 } 574 575 static pj_bool_t handle_tab(pj_cli_sess *sess) 576 { 577 pj_status_t status; 578 pj_bool_t retval = PJ_TRUE; 579 unsigned len; 580 581 pj_pool_t *pool; 582 pj_cli_cmd_val *cmd_val; 583 pj_cli_exec_info info; 584 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; 585 console_recv_buf *recv_buf = &fe->input.recv_buf; 586 pj_cli_t *cli = sess->fe->cli; 587 588 pool = pj_pool_create(pj_cli_get_param(cli)->pf, "handle_tab", 589 PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC, 590 NULL); 591 592 cmd_val = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_val); 593 594 status = pj_cli_sess_parse(sess, (char *)recv_buf->rbuf, cmd_val, 595 pool, &info); 596 597 len = pj_ansi_strlen((char *)recv_buf->rbuf); 598 599 switch (status) { 600 case PJ_CLI_EINVARG: 601 send_inv_arg(sess, &info, PJ_TRUE, PJ_TRUE); 602 break; 603 case PJ_CLI_ETOOMANYARGS: 604 send_too_many_arg(sess, &info, PJ_TRUE, PJ_TRUE); 605 break; 606 case PJ_CLI_EMISSINGARG: 607 case PJ_CLI_EAMBIGUOUS: 608 send_ambi_arg(sess, &info, PJ_TRUE, PJ_TRUE); 609 break; 610 case PJ_SUCCESS: 611 if (len > recv_buf->cur_pos) 612 { 613 /* Send the cursor to EOL */ 614 unsigned char *data_sent = &recv_buf->rbuf[recv_buf->cur_pos-1]; 615 printf("%s", data_sent); 616 } 617 if (info.hint_cnt > 0) { 618 /* Compelete command */ 619 send_comp_arg(&info); 620 621 pj_memcpy(&recv_buf->rbuf[len], 622 &info.hint[0].name.ptr[0], info.hint[0].name.slen); 623 624 len += info.hint[0].name.slen; 625 recv_buf->rbuf[len] = 0; 626 } else { 627 retval = PJ_FALSE; 628 } 629 break; 630 } 631 recv_buf->len = len; 632 recv_buf->cur_pos = len; 633 634 pj_pool_release(pool); 635 return retval; 636 } 637 638 static pj_bool_t handle_return(pj_cli_sess *sess) 639 { 640 pj_status_t status; 641 pj_bool_t retval = PJ_TRUE; 642 643 pj_pool_t *pool; 644 pj_cli_exec_info info; 645 pj_cli_t *cli = sess->fe->cli; 646 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; 647 console_recv_buf *recv_buf = &fe->input.recv_buf; 648 649 send_return_key(sess); 650 insert_history(sess, (char *)&recv_buf->rbuf[0]); 651 652 pool = pj_pool_create(pj_cli_get_param(cli)->pf, "handle_return", 653 PJ_CLI_CONSOLE_POOL_SIZE, PJ_CLI_CONSOLE_POOL_INC, 654 NULL); 655 656 status = pj_cli_sess_exec(sess, (char *)&recv_buf->rbuf[0], 657 pool, &info); 658 659 switch (status) { 660 case PJ_CLI_EINVARG: 661 send_inv_arg(sess, &info, PJ_FALSE, PJ_FALSE); 662 break; 663 case PJ_CLI_ETOOMANYARGS: 664 send_too_many_arg(sess, &info, PJ_FALSE, PJ_FALSE); 665 break; 666 case PJ_CLI_EAMBIGUOUS: 667 case PJ_CLI_EMISSINGARG: 668 send_ambi_arg(sess, &info, PJ_FALSE, PJ_FALSE); 669 break; 670 case PJ_CLI_EEXIT: 671 retval = PJ_FALSE; 672 break; 673 case PJ_SUCCESS: 674 send_prompt_str(sess); 675 break; 676 } 677 if (retval) { 678 recv_buf->rbuf[0] = 0; 679 recv_buf->len = 0; 680 recv_buf->cur_pos = 0; 681 } 682 683 pj_pool_release(pool); 684 return retval; 685 } 686 687 static pj_bool_t handle_up_down(pj_cli_sess *sess, pj_bool_t is_up) 688 { 689 pj_str_t *history; 690 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; 691 console_recv_buf *recv_buf = &fe->input.recv_buf; 692 693 PJ_ASSERT_RETURN(sess && fe, PJ_FALSE); 694 695 history = get_prev_history(sess, is_up); 696 if (history) { 697 pj_str_t send_data; 698 char str[PJ_CLI_MAX_CMDBUF]; 699 send_data.ptr = &str[0]; 700 send_data.slen = 0; 701 702 if (recv_buf->cur_pos > 0) { 703 pj_memset(send_data.ptr, 0x08, recv_buf->cur_pos); 704 send_data.slen = recv_buf->cur_pos; 705 } 706 707 if (recv_buf->len > (unsigned)history->slen) { 708 unsigned buf_len = recv_buf->len; 709 pj_memset(&send_data.ptr[send_data.slen], 0x20, buf_len); 710 send_data.slen += buf_len; 711 pj_memset(&send_data.ptr[send_data.slen], 0x08, buf_len); 712 send_data.slen += buf_len; 713 } 714 /* Send data */ 715 pj_strcat(&send_data, history); 716 send_data.ptr[send_data.slen] = 0; 717 printf("%s", send_data.ptr); 718 pj_ansi_strncpy((char*)&recv_buf->rbuf, history->ptr, history->slen); 719 recv_buf->rbuf[history->slen] = 0; 720 recv_buf->len = history->slen; 721 recv_buf->cur_pos = recv_buf->len; 722 return PJ_TRUE; 723 } 724 return PJ_FALSE; 725 } 726 727 static pj_bool_t handle_left_key(console_recv_buf *recv_buf) 728 { 729 const static unsigned char BACK_SPACE = 0x08; 730 if (recv_buf->cur_pos) { 731 printf("%c", BACK_SPACE); 732 --recv_buf->cur_pos; 733 return PJ_TRUE; 734 } 735 return PJ_FALSE; 736 } 737 738 static pj_bool_t handle_right_key(console_recv_buf *recv_buf) 739 { 740 if (recv_buf_right_len(recv_buf)) { 741 unsigned char *data = &recv_buf->rbuf[recv_buf->cur_pos++]; 742 printf("%c", *data); 743 return PJ_TRUE; 744 } 745 return PJ_FALSE; 746 } 747 748 static int readchar_thread(void * p) 749 { 140 750 struct cli_console_fe * fe = (struct cli_console_fe *)p; 141 int i; 751 cmd_parse_state parse_state = ST_NORMAL; 752 753 printf("%s", fe->cfg.prompt_str.ptr); 142 754 143 755 while (!fe->thread_quit) { 144 fgets(fe->input.buf, fe->input.maxlen, stdin); 145 for (i = pj_ansi_strlen(fe->input.buf) - 1; i >= 0; i--) { 146 if (fe->input.buf[i] == '\n' || fe->input.buf[i] == '\r') 147 fe->input.buf[i] = 0; 148 else 149 break; 150 } 151 pj_sem_post(fe->input.sem); 152 /* Sleep until the next call of pj_cli_console_readline() */ 756 unsigned char cdata; 757 console_recv_buf *recv_buf = &fe->input.recv_buf; 758 pj_bool_t is_valid = PJ_TRUE; 759 760 cdata = (unsigned char)getch(); 761 762 switch (parse_state) { 763 case ST_NORMAL: 764 if (cdata == '\b') { 765 is_valid = handle_backspace(recv_buf); 766 } else if (cdata == 224) { 767 parse_state = ST_SCANMODE; 768 } else if (cdata == 27) { 769 parse_state = ST_ESC; 770 } else { 771 if (cdata == '\n') 772 cdata = '\r'; 773 if (recv_buf_insert(recv_buf, &cdata)) { 774 if (cdata == '\r') { 775 is_valid = handle_return(fe->sess); 776 } else if ((cdata == '\t') || (cdata == '?')) { 777 is_valid = handle_tab(fe->sess); 778 } else if (cdata > 31 && cdata < 127) { 779 is_valid = handle_alfa_num(recv_buf, &cdata); 780 } 781 } else { 782 is_valid = PJ_FALSE; 783 } 784 } 785 break; 786 case ST_SCANMODE: 787 switch (cdata) { 788 case 72: 789 //UP 790 case 80: 791 //DOwN 792 is_valid = handle_up_down(fe->sess, (cdata==72)); 793 break; 794 case 75: 795 is_valid = handle_left_key(recv_buf); 796 //LEFT 797 break; 798 case 77: 799 is_valid = handle_right_key(recv_buf); 800 //RIGHT 801 break; 802 }; 803 parse_state = ST_NORMAL; 804 break; 805 case ST_ESC: 806 parse_state = (cdata == 91)?ST_ARROWMODE:ST_NORMAL; 807 break; 808 case ST_ARROWMODE: 809 switch (cdata) { 810 case 65: 811 //UP 812 case 66: 813 //DOwN 814 is_valid = handle_up_down(fe->sess, (cdata==65)); 815 break; 816 case 68: 817 is_valid = handle_left_key(recv_buf); 818 //LEFT 819 break; 820 case 67: 821 is_valid = handle_right_key(recv_buf); 822 //RIGHT 823 break; 824 }; 825 parse_state = ST_NORMAL; 826 break; 827 }; 828 829 pj_sem_post(fe->input.sem); 153 830 pj_sem_wait(fe->thread_sem); 154 831 } … … 158 835 } 159 836 160 PJ_DEF(pj_status_t) pj_cli_console_readline(pj_cli_sess *sess, 161 char *buf, 162 unsigned maxlen) 837 PJ_DEF(pj_status_t) pj_cli_console_process(pj_cli_sess *sess) 163 838 { 164 839 struct cli_console_fe *fe = (struct cli_console_fe *)sess->fe; 165 840 166 PJ_ASSERT_RETURN(sess && buf, PJ_EINVAL); 167 168 fe->input.buf = buf; 169 fe->input.maxlen = maxlen; 841 PJ_ASSERT_RETURN(sess, PJ_EINVAL); 170 842 171 843 if (!fe->input_thread) { 172 844 pj_status_t status; 173 845 174 status = pj_thread_create(fe->pool, NULL, &read line_thread, fe,846 status = pj_thread_create(fe->pool, NULL, &readchar_thread, fe, 175 847 0, 0, &fe->input_thread); 176 848 if (status != PJ_SUCCESS) 177 849 return status; 178 850 } else { 179 /* Wake up read linethread */851 /* Wake up readchar thread */ 180 852 pj_sem_post(fe->thread_sem); 181 853 } -
pjproject/branches/projects/cli/pjlib-util/src/pjlib-util/cli_telnet.c
r3232 r4299 42 42 #endif 43 43 44 struct cli_telnet_sess 45 { 46 pj_cli_sess base; 47 pj_pool_t *pool; 48 pj_activesock_t *asock; 49 pj_bool_t authorized; 50 pj_ioqueue_op_key_t op_key; 51 pj_mutex_t *smutex; 52 53 char rcmd[PJ_CLI_MAX_CMDBUF]; 54 int len; 55 char buf[CLI_TELNET_BUF_SIZE + MAX_CUT_MSG_LEN]; 56 unsigned buf_len; 44 #define MAX_CLI_TELNET_OPTIONS 256 45 46 /** 47 * This specify the state for the telnet option negotiation. 48 */ 49 enum cli_telnet_option_states 50 { 51 OPT_DISABLE, /* Option disable */ 52 OPT_ENABLE, /* Option enable */ 53 OPT_EXPECT_DISABLE, /* Already send disable req, expecting resp */ 54 OPT_EXPECT_ENABLE, /* Already send enable req, expecting resp */ 55 OPT_EXPECT_DISABLE_REV, /* Already send disable req, expecting resp, 56 * need to send enable req */ 57 OPT_EXPECT_ENABLE_REV /* Already send enable req, expecting resp, 58 * need to send disable req */ 57 59 }; 60 61 /** 62 * This structure contains information for telnet session option negotiation. 63 * It contains the local/peer option config and the option negotiation state. 64 */ 65 typedef struct cli_telnet_sess_option 66 { 67 /** 68 * Local setting for the option. 69 * Default: FALSE; 70 */ 71 pj_bool_t local_is_enable; 72 73 /** 74 * Remote setting for the option. 75 * Default: FALSE; 76 */ 77 pj_bool_t peer_is_enable; 78 79 /** 80 * Local state of the option negotiation. 81 */ 82 enum cli_telnet_option_states local_state; 83 84 /** 85 * Remote state of the option negotiation. 86 */ 87 enum cli_telnet_option_states peer_state; 88 } cli_telnet_sess_option; 89 90 /** 91 * This specify the state of input character parsing. 92 */ 93 typedef enum cmd_parse_state 94 { 95 ST_NORMAL, 96 ST_CR, 97 ST_ESC, 98 ST_VT100, 99 ST_IAC, 100 ST_DO, 101 ST_DONT, 102 ST_WILL, 103 ST_WONT 104 } cmd_parse_state; 105 106 typedef enum cli_telnet_command 107 { 108 SUBNEGO_END = 240, /* End of subnegotiation parameters. */ 109 NOP = 241, /* No operation. */ 110 DATA_MARK = 242, /* Marker for NVT cleaning. */ 111 BREAK = 243, /* Indicates that the "break" key was hit. */ 112 INT_PROCESS = 244, /* Suspend, interrupt or abort the process. */ 113 ABORT_OUTPUT = 245, /* Abort output, abort output stream. */ 114 ARE_YOU_THERE = 246, /* Are you there. */ 115 ERASE_CHAR = 247, /* Erase character, erase the current char. */ 116 ERASE_LINE = 248, /* Erase line, erase the current line. */ 117 GO_AHEAD = 249, /* Go ahead, other end can transmit. */ 118 SUBNEGO_BEGIN = 250, /* Subnegotiation begin. */ 119 WILL = 251, /* Accept the use of option. */ 120 WONT = 252, /* Refuse the use of option. */ 121 DO = 253, /* Request to use option. */ 122 DONT = 254, /* Request to not use option. */ 123 IAC = 255 /* Interpret as command */ 124 } cli_telnet_command; 125 126 enum cli_telnet_options 127 { 128 TRANSMIT_BINARY = 0, /* Transmit Binary. */ 129 ECHO = 1, /* Echo. */ 130 RECONNECT = 2, /* Reconnection. */ 131 SUPPRESS_GA = 3, /* Suppress Go Aheah. */ 132 MESSAGE_SIZE_NEGO = 4, /* Approx Message Size Negotiation. */ 133 STATUS = 5, /* Status. */ 134 TIMING_MARK = 6, /* Timing Mark. */ 135 RTCE_OPTION = 7, /* Remote Controlled Trans and Echo. */ 136 OUTPUT_LINE_WIDTH = 8, /* Output Line Width. */ 137 OUTPUT_PAGE_SIZE = 9, /* Output Page Size. */ 138 CR_DISPOSITION = 10, /* Carriage-Return Disposition. */ 139 HORI_TABSTOPS = 11, /* Horizontal Tabstops. */ 140 HORI_TAB_DISPO = 12, /* Horizontal Tab Disposition. */ 141 FF_DISP0 = 13, /* Formfeed Disposition. */ 142 VERT_TABSTOPS = 14, /* Vertical Tabstops. */ 143 VERT_TAB_DISPO = 15, /* Vertical Tab Disposition. */ 144 LF_DISP0 = 16, /* Linefeed Disposition. */ 145 EXT_ASCII = 17, /* Extended ASCII. */ 146 LOGOUT = 18, /* Logout. */ 147 BYTE_MACRO = 19, /* Byte Macro. */ 148 DE_TERMINAL = 20, /* Data Entry Terminal. */ 149 SUPDUP_PROTO = 21, /* SUPDUP Protocol. */ 150 SUPDUP_OUTPUT = 22, /* SUPDUP Output. */ 151 SEND_LOC = 23, /* Send Location. */ 152 TERM_TYPE = 24, /* Terminal Type. */ 153 EOR = 25, /* End of Record. */ 154 TACACS_UID = 26, /* TACACS User Identification. */ 155 OUTPUT_MARKING = 27, /* Output Marking. */ 156 TTYLOC = 28, /* Terminal Location Number. */ 157 USE_3270_REGIME = 29, /* Telnet 3270 Regime. */ 158 USE_X3_PAD = 30, /* X.3 PAD. */ 159 WINDOW_SIZE = 31, /* Window Size. */ 160 TERM_SPEED = 32, /* Terminal Speed. */ 161 REM_FLOW_CONTROL = 33, /* Remote Flow Control. */ 162 LINE_MODE = 34, /* Linemode. */ 163 X_DISP_LOC = 35, /* X Display Location. */ 164 ENVIRONMENT = 36, /* Environment. */ 165 AUTH = 37, /* Authentication. */ 166 ENCRYPTION = 38, /* Encryption Option. */ 167 NEW_ENVIRONMENT = 39, /* New Environment. */ 168 TN_3270E = 40, /* TN3270E. */ 169 XAUTH = 41, /* XAUTH. */ 170 CHARSET = 42, /* CHARSET. */ 171 REM_SERIAL_PORT = 43, /* Telnet Remote Serial Port. */ 172 COM_PORT_CONTROL = 44, /* Com Port Control. */ 173 SUPP_LOCAL_ECHO = 45, /* Telnet Suppress Local Echo. */ 174 START_TLS = 46, /* Telnet Start TLS. */ 175 KERMIT = 47, /* KERMIT. */ 176 SEND_URL = 48, /* SEND-URL. */ 177 FWD_X = 49, /* FORWARD_X. */ 178 EXT_OPTIONS = 255 /* Extended-Options-List */ 179 }; 180 181 enum terminal_cmd 182 { 183 TC_ESC = 27, 184 TC_UP = 65, 185 TC_DOWN = 66, 186 TC_RIGHT = 67, 187 TC_LEFT = 68, 188 TC_END = 70, 189 TC_HOME = 72, 190 TC_CTRL_C = 3, 191 TC_CR = 13, 192 TC_BS = 8, 193 TC_TAB = 9, 194 TC_QM = 63, 195 TC_BELL = 7, 196 TC_DEL = 127 197 }; 198 199 /** 200 * This structure contains the command line shown to the user. 201 */ 202 typedef struct telnet_recv_buf { 203 /** 204 * Buffer containing the characters, NULL terminated. 205 */ 206 unsigned char rbuf[PJ_CLI_MAX_CMDBUF]; 207 208 /** 209 * Current length of the command line. 210 */ 211 unsigned len; 212 213 /** 214 * Current cursor position. 215 */ 216 unsigned cur_pos; 217 } telnet_recv_buf; 218 219 typedef struct cmd_history 220 { 221 PJ_DECL_LIST_MEMBER(struct cmd_history); 222 pj_str_t command; 223 } cmd_history; 224 225 typedef struct cli_telnet_sess 226 { 227 pj_cli_sess base; 228 pj_pool_t *pool; 229 pj_activesock_t *asock; 230 pj_bool_t authorized; 231 pj_ioqueue_op_key_t op_key; 232 pj_mutex_t *smutex; 233 cmd_parse_state parse_state; 234 cli_telnet_sess_option telnet_option[MAX_CLI_TELNET_OPTIONS]; 235 cmd_history *history; 236 cmd_history *active_history; 237 238 telnet_recv_buf *rcmd; 239 unsigned char buf[CLI_TELNET_BUF_SIZE + MAX_CUT_MSG_LEN]; 240 unsigned buf_len; 241 } cli_telnet_sess; 58 242 59 243 struct cli_telnet_fe … … 71 255 }; 72 256 257 /* Forward Declaration */ 258 static pj_status_t telnet_sess_send2(cli_telnet_sess *sess, 259 const unsigned char *str, int len); 260 261 static pj_status_t telnet_sess_send(cli_telnet_sess *sess, 262 const pj_str_t *str); 263 264 /** 265 * Return the number of characters between the current cursor position 266 * to the end of line. 267 */ 268 static unsigned recv_buf_right_len(telnet_recv_buf *recv_buf) 269 { 270 return (recv_buf->len - recv_buf->cur_pos); 271 } 272 273 static pj_bool_t recv_buf_insert(telnet_recv_buf *recv_buf, 274 unsigned char *data) 275 { 276 if (recv_buf->len+1 >= PJ_CLI_MAX_CMDBUF) { 277 return PJ_FALSE; 278 } else { 279 if (*data == '\t' || *data == '?' || *data == '\r') { 280 /* Always insert to the end of line */ 281 recv_buf->rbuf[recv_buf->len] = *data; 282 } else { 283 /* Insert based on the current cursor pos */ 284 unsigned cur_pos = recv_buf->cur_pos; 285 unsigned rlen = recv_buf_right_len(recv_buf); 286 if (rlen > 0) { 287 /* Shift right characters */ 288 pj_memmove(&recv_buf->rbuf[cur_pos+1], 289 &recv_buf->rbuf[cur_pos], 290 rlen+1); 291 } 292 recv_buf->rbuf[cur_pos] = *data; 293 } 294 ++recv_buf->cur_pos; 295 ++recv_buf->len; 296 recv_buf->rbuf[recv_buf->len] = 0; 297 } 298 return PJ_TRUE; 299 } 300 301 static pj_bool_t recv_buf_delete(telnet_recv_buf *recv_buf) 302 { 303 if ((recv_buf->len == 0) || (recv_buf_right_len(recv_buf) == 0)) { 304 return PJ_FALSE; 305 } else { 306 unsigned rlen; 307 unsigned cur_pos = recv_buf->cur_pos; 308 rlen = recv_buf_right_len(recv_buf)-1; 309 pj_memmove(&recv_buf->rbuf[cur_pos], &recv_buf->rbuf[cur_pos+1], 310 rlen); 311 --recv_buf->cur_pos; 312 --recv_buf->len; 313 recv_buf->rbuf[recv_buf->len] = 0; 314 } 315 return PJ_TRUE; 316 } 317 318 static pj_bool_t recv_buf_backspace(telnet_recv_buf *recv_buf) 319 { 320 if ((recv_buf->cur_pos == 0) || (recv_buf->len == 0)) { 321 return PJ_FALSE; 322 } else { 323 unsigned rlen = recv_buf_right_len(recv_buf); 324 if (rlen) { 325 unsigned cur_pos = recv_buf->cur_pos; 326 pj_memmove(&recv_buf->rbuf[cur_pos-1], &recv_buf->rbuf[cur_pos], 327 rlen); 328 } 329 --recv_buf->cur_pos; 330 --recv_buf->len; 331 recv_buf->rbuf[recv_buf->len] = 0; 332 } 333 return PJ_TRUE; 334 } 335 336 static int compare_str(void *value, const pj_list_type *nd) 337 { 338 cmd_history *node = (cmd_history*)nd; 339 return (pj_strcmp((pj_str_t *)value, &node->command)); 340 } 341 342 static pj_status_t insert_history(cli_telnet_sess *sess, 343 char *cmd_val) 344 { 345 cmd_history *in_history; 346 pj_str_t cmd; 347 cmd.ptr = cmd_val; 348 cmd.slen = pj_ansi_strlen(cmd_val)-1; 349 350 if (cmd.slen == 0) 351 return PJ_SUCCESS; 352 353 PJ_ASSERT_RETURN(sess, PJ_EINVAL); 354 355 /* Find matching history */ 356 in_history = pj_list_search(sess->history, (void*)&cmd, compare_str); 357 if (!in_history) { 358 if (pj_list_size(sess->history) < PJ_CLI_MAX_CMD_HISTORY) { 359 char *data_history; 360 in_history = PJ_POOL_ZALLOC_T(sess->pool, cmd_history); 361 pj_list_init(in_history); 362 data_history = (char *)pj_pool_calloc(sess->pool, 363 sizeof(char), PJ_CLI_MAX_CMDBUF); 364 in_history->command.ptr = data_history; 365 in_history->command.slen = 0; 366 } else { 367 /* Get the oldest history */ 368 in_history = sess->history->prev; 369 } 370 } else { 371 pj_list_insert_nodes_after(in_history->prev, in_history->next); 372 } 373 pj_strcpy(&in_history->command, pj_strtrim(&cmd)); 374 pj_list_push_front(sess->history, in_history); 375 sess->active_history = sess->history; 376 377 return PJ_SUCCESS; 378 } 379 380 static pj_str_t* get_prev_history(cli_telnet_sess *sess, pj_bool_t is_forward) 381 { 382 pj_str_t *retval; 383 pj_size_t history_size; 384 cmd_history *node; 385 cmd_history *root; 386 387 PJ_ASSERT_RETURN(sess, NULL); 388 389 node = sess->active_history; 390 root = sess->history; 391 history_size = pj_list_size(sess->history); 392 393 if (history_size == 0) { 394 return NULL; 395 } else { 396 if (is_forward) { 397 node = (node->next==root)?node->next->next:node->next; 398 } else { 399 node = (node->prev==root)?node->prev->prev:node->prev; 400 } 401 retval = &node->command; 402 sess->active_history = node; 403 } 404 return retval; 405 } 406 407 static pj_bool_t send_telnet_cmd(cli_telnet_sess *sess, 408 cli_telnet_command cmd, 409 unsigned char option) 410 { 411 unsigned char buf[3]; 412 PJ_ASSERT_RETURN(sess, PJ_FALSE); 413 414 buf[0] = IAC; 415 buf[1] = cmd; 416 buf[2] = option; 417 telnet_sess_send2(sess, buf, 3); 418 419 return PJ_TRUE; 420 } 421 422 /** 423 * This method will handle sending telnet's ENABLE option negotiation. 424 * For local option: send WILL. 425 * For remote option: send DO. 426 * This method also handle the state transition of the ENABLE 427 * negotiation process. 428 */ 429 static pj_bool_t send_enable_option(cli_telnet_sess *sess, 430 pj_bool_t is_local, 431 unsigned char option) 432 { 433 cli_telnet_sess_option *sess_option; 434 enum cli_telnet_option_states *state; 435 PJ_ASSERT_RETURN(sess, PJ_FALSE); 436 437 sess_option = &sess->telnet_option[option]; 438 state = is_local?(&sess_option->local_state):(&sess_option->peer_state); 439 switch (*state) { 440 case OPT_ENABLE: 441 /* Ignore if already enabled */ 442 break; 443 case OPT_DISABLE: 444 *state = OPT_EXPECT_ENABLE; 445 send_telnet_cmd(sess, (is_local?WILL:DO), option); 446 break; 447 case OPT_EXPECT_ENABLE: 448 *state = OPT_DISABLE; 449 break; 450 case OPT_EXPECT_DISABLE: 451 *state = OPT_EXPECT_DISABLE_REV; 452 break; 453 case OPT_EXPECT_ENABLE_REV: 454 *state = OPT_EXPECT_ENABLE; 455 break; 456 case OPT_EXPECT_DISABLE_REV: 457 *state = OPT_DISABLE; 458 break; 459 default: 460 return PJ_FALSE; 461 } 462 return PJ_TRUE; 463 } 464 465 /** 466 * This method will handle sending telnet's DISABLE option negotiation. 467 * For local option: send WON'T. 468 * For remote option: send DON'T. 469 * This method also handle the state transition of the DISABLE 470 * negotiation process. 471 */ 472 static pj_bool_t send_disable_option(cli_telnet_sess *sess, 473 pj_bool_t is_local, 474 unsigned char option) 475 { 476 cli_telnet_sess_option *sess_option; 477 enum cli_telnet_option_states *state; 478 PJ_ASSERT_RETURN(sess, PJ_FALSE); 479 480 sess_option = &sess->telnet_option[option]; 481 state = is_local?(&sess_option->local_state):(&sess_option->peer_state); 482 switch (*state) { 483 case OPT_ENABLE: 484 *state = OPT_EXPECT_DISABLE; 485 send_telnet_cmd(sess, (is_local?WONT:DONT), option); 486 break; 487 case OPT_DISABLE: 488 /* Ignore if already disabled */ 489 break; 490 case OPT_EXPECT_ENABLE: 491 *state = OPT_EXPECT_ENABLE_REV; 492 break; 493 case OPT_EXPECT_DISABLE: 494 *state = OPT_ENABLE; 495 break; 496 case OPT_EXPECT_ENABLE_REV: 497 *state = OPT_ENABLE; 498 break; 499 case OPT_EXPECT_DISABLE_REV: 500 *state = OPT_EXPECT_DISABLE; 501 break; 502 default: 503 return PJ_FALSE; 504 } 505 return PJ_TRUE; 506 } 507 508 static pj_bool_t send_cmd_do(cli_telnet_sess *sess, 509 unsigned char option) 510 { 511 return send_enable_option(sess, PJ_FALSE, option); 512 } 513 514 static pj_bool_t send_cmd_dont(cli_telnet_sess *sess, 515 unsigned char option) 516 { 517 return send_disable_option(sess, PJ_FALSE, option); 518 } 519 520 static pj_bool_t send_cmd_will(cli_telnet_sess *sess, 521 unsigned char option) 522 { 523 return send_enable_option(sess, PJ_TRUE, option); 524 } 525 526 static pj_bool_t send_cmd_wont(cli_telnet_sess *sess, 527 unsigned char option) 528 { 529 return send_disable_option(sess, PJ_TRUE, option); 530 } 531 532 /** 533 * This method will handle receiving telnet's ENABLE option negotiation. 534 * This method also handle the state transition of the ENABLE 535 * negotiation process. 536 */ 537 static pj_bool_t receive_enable_option(cli_telnet_sess *sess, 538 pj_bool_t is_local, 539 unsigned char option) 540 { 541 cli_telnet_sess_option *sess_opt; 542 enum cli_telnet_option_states *state; 543 pj_bool_t opt_ena; 544 PJ_ASSERT_RETURN(sess, PJ_FALSE); 545 546 sess_opt = &sess->telnet_option[option]; 547 state = is_local?(&sess_opt->local_state):(&sess_opt->peer_state); 548 opt_ena = is_local?sess_opt->local_is_enable:sess_opt->peer_is_enable; 549 switch (*state) { 550 case OPT_ENABLE: 551 /* Ignore if already enabled */ 552 break; 553 case OPT_DISABLE: 554 if (opt_ena) { 555 *state = OPT_ENABLE; 556 send_telnet_cmd(sess, is_local?WILL:DO, option); 557 } else { 558 send_telnet_cmd(sess, is_local?WONT:DONT, option); 559 } 560 break; 561 case OPT_EXPECT_ENABLE: 562 *state = OPT_ENABLE; 563 break; 564 case OPT_EXPECT_DISABLE: 565 *state = OPT_DISABLE; 566 break; 567 case OPT_EXPECT_ENABLE_REV: 568 *state = OPT_EXPECT_DISABLE; 569 send_telnet_cmd(sess, is_local?WONT:DONT, option); 570 break; 571 case OPT_EXPECT_DISABLE_REV: 572 *state = OPT_EXPECT_DISABLE; 573 break; 574 default: 575 return PJ_FALSE; 576 } 577 return PJ_TRUE; 578 } 579 580 /** 581 * This method will handle receiving telnet's DISABLE option negotiation. 582 * This method also handle the state transition of the DISABLE 583 * negotiation process. 584 */ 585 static pj_bool_t receive_disable_option(cli_telnet_sess *sess, 586 pj_bool_t is_local, 587 unsigned char option) 588 { 589 cli_telnet_sess_option *sess_opt; 590 enum cli_telnet_option_states *state; 591 pj_bool_t opt_ena; 592 PJ_ASSERT_RETURN(sess, PJ_FALSE); 593 594 sess_opt = &sess->telnet_option[option]; 595 state = is_local?(&sess_opt->local_state):(&sess_opt->peer_state); 596 opt_ena = is_local?sess_opt->local_is_enable:sess_opt->peer_is_enable; 597 switch (*state) { 598 case OPT_ENABLE: 599 /* Disabling option always need to be accepted */ 600 *state = OPT_DISABLE; 601 send_telnet_cmd(sess, is_local?WONT:DONT, option); 602 break; 603 case OPT_DISABLE: 604 /* Ignore if already enabled */ 605 break; 606 case OPT_EXPECT_ENABLE: 607 case OPT_EXPECT_DISABLE: 608 *state = OPT_DISABLE; 609 break; 610 case OPT_EXPECT_ENABLE_REV: 611 *state = OPT_DISABLE; 612 send_telnet_cmd(sess, is_local?WONT:DONT, option); 613 break; 614 case OPT_EXPECT_DISABLE_REV: 615 *state = OPT_EXPECT_ENABLE; 616 send_telnet_cmd(sess, is_local?WILL:DO, option); 617 break; 618 default: 619 return PJ_FALSE; 620 } 621 return PJ_TRUE; 622 } 623 624 static pj_bool_t receive_do(cli_telnet_sess *sess, unsigned char option) 625 { 626 return receive_enable_option(sess, PJ_TRUE, option); 627 } 628 629 static pj_bool_t receive_dont(cli_telnet_sess *sess, unsigned char option) 630 { 631 return receive_disable_option(sess, PJ_TRUE, option); 632 } 633 634 static pj_bool_t receive_will(cli_telnet_sess *sess, unsigned char option) 635 { 636 return receive_enable_option(sess, PJ_FALSE, option); 637 } 638 639 static pj_bool_t receive_wont(cli_telnet_sess *sess, unsigned char option) 640 { 641 return receive_disable_option(sess, PJ_FALSE, option); 642 } 643 644 static void set_local_option(cli_telnet_sess *sess, 645 unsigned char option, 646 pj_bool_t enable) 647 { 648 sess->telnet_option[option].local_is_enable = enable; 649 } 650 651 static void set_peer_option(cli_telnet_sess *sess, 652 unsigned char option, 653 pj_bool_t enable) 654 { 655 sess->telnet_option[option].peer_is_enable = enable; 656 } 657 658 static pj_bool_t is_local_option_state_ena(cli_telnet_sess *sess, 659 unsigned char option) 660 { 661 return (sess->telnet_option[option].local_state == OPT_ENABLE); 662 } 663 664 static pj_bool_t is_peer_option_state_ena(cli_telnet_sess *sess, 665 unsigned char option) 666 { 667 return (sess->telnet_option[option].peer_state == OPT_ENABLE); 668 } 669 670 static void send_return_key(cli_telnet_sess *sess) 671 { 672 telnet_sess_send2(sess, (unsigned char*)"\r\n", 2); 673 } 674 675 static void send_bell(cli_telnet_sess *sess) { 676 unsigned char bell[1] = {0x07}; 677 telnet_sess_send2(sess, &bell[0], 1); 678 } 679 680 static void send_prompt_str(cli_telnet_sess *sess) 681 { 682 pj_str_t send_data; 683 char data_str[128]; 684 struct cli_telnet_fe *fe = (struct cli_telnet_fe *)sess->base.fe; 685 686 send_data.ptr = &data_str[0]; 687 send_data.slen = 0; 688 689 pj_strcat(&send_data, &fe->cfg.prompt_str); 690 691 telnet_sess_send(sess, &send_data); 692 } 693 694 static void send_err_arg(cli_telnet_sess *sess, 695 const pj_cli_exec_info *info, 696 const pj_str_t *msg, 697 pj_bool_t with_return, 698 pj_bool_t with_last_cmd) 699 { 700 pj_str_t send_data; 701 char data_str[256]; 702 unsigned len; 703 unsigned i; 704 struct cli_telnet_fe *fe = (struct cli_telnet_fe *)sess->base.fe; 705 706 send_data.ptr = &data_str[0]; 707 send_data.slen = 0; 708 709 if (with_return) 710 pj_strcat2(&send_data, "\r\n"); 711 712 len = fe->cfg.prompt_str.slen + info->err_pos; 713 714 for (i=0;i<len;++i) { 715 pj_strcat2(&send_data, " "); 716 } 717 pj_strcat2(&send_data, "^"); 718 pj_strcat2(&send_data, "\r\n"); 719 pj_strcat(&send_data, msg); 720 pj_strcat(&send_data, &fe->cfg.prompt_str); 721 if (with_last_cmd) 722 pj_strcat2(&send_data, (char *)&sess->rcmd->rbuf[0]); 723 724 telnet_sess_send(sess, &send_data); 725 } 726 727 static void send_inv_arg(cli_telnet_sess *sess, 728 const pj_cli_exec_info *info, 729 pj_bool_t with_return, 730 pj_bool_t with_last_cmd) 731 { 732 static const pj_str_t ERR_MSG = {"%Error : Invalid Arguments\r\n", 28}; 733 send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd); 734 } 735 736 static void send_too_many_arg(cli_telnet_sess *sess, 737 const pj_cli_exec_info *info, 738 pj_bool_t with_return, 739 pj_bool_t with_last_cmd) 740 { 741 static const pj_str_t ERR_MSG = {"%Error : Too Many Arguments\r\n", 29}; 742 send_err_arg(sess, info, &ERR_MSG, with_return, with_last_cmd); 743 } 744 745 static void send_ambi_arg(cli_telnet_sess *sess, 746 const pj_cli_exec_info *info, 747 pj_bool_t with_return, 748 pj_bool_t with_last_cmd) 749 { 750 unsigned i; 751 pj_ssize_t j; 752 unsigned len; 753 pj_str_t send_data; 754 char data[1028]; 755 struct cli_telnet_fe *fe = (struct cli_telnet_fe *)sess->base.fe; 756 const pj_cli_hint_info *hint = info->hint; 757 pj_bool_t is_process_sc = PJ_FALSE; 758 pj_bool_t valid_type = PJ_FALSE; 759 pj_ssize_t max_length = 0; 760 const pj_str_t sc_type = pj_str("SC"); 761 send_data.ptr = &data[0]; 762 send_data.slen = 0; 763 764 if (with_return) 765 pj_strcat2(&send_data, "\r\n"); 766 767 len = fe->cfg.prompt_str.slen + info->err_pos; 768 769 for (i=0;i<len;++i) { 770 pj_strcat2(&send_data, " "); 771 } 772 pj_strcat2(&send_data, "^"); 773 /* Get the max length of the command name */ 774 for (i=0;i<info->hint_cnt;++i) { 775 if (hint[i].name.slen > max_length) { 776 max_length = hint[i].name.slen; 777 } 778 } 779 780 for (i=0;i<info->hint_cnt;++i) { 781 if ((&hint[i].type) && (hint[i].type.slen > 0)) { 782 valid_type = PJ_TRUE; 783 if (pj_stricmp(&hint[i].type, &sc_type) == 0) { 784 if (is_process_sc) { 785 pj_strcat2(&send_data, ", "); 786 } else { 787 pj_strcat2(&send_data, "\r\n\t Shorcut: "); 788 is_process_sc = PJ_TRUE; 789 } 790 pj_strcat(&send_data, &hint[i].name); 791 } else { 792 is_process_sc = PJ_FALSE; 793 } 794 } else { 795 valid_type = PJ_FALSE; 796 is_process_sc = PJ_FALSE; 797 } 798 799 if (!is_process_sc) { 800 pj_strcat2(&send_data, "\r\n\t"); 801 802 if (valid_type) { 803 pj_strcat2(&send_data, "<"); 804 pj_strcat(&send_data, &hint[i].type); 805 pj_strcat2(&send_data, ">"); 806 } else { 807 pj_strcat(&send_data, &hint[i].name); 808 } 809 810 if ((&hint[i].desc) && (hint[i].desc.slen > 0)) { 811 if (!valid_type) { 812 for (j=0;j<(max_length-hint[i].name.slen);++j) { 813 pj_strcat2(&send_data, " "); 814 } 815 } 816 pj_strcat2(&send_data, "\t"); 817 pj_strcat(&send_data, &hint[i].desc); 818 } 819 } 820 } 821 pj_strcat2(&send_data, "\r\n"); 822 pj_strcat(&send_data, &fe->cfg.prompt_str); 823 if (with_last_cmd) 824 pj_strcat2(&send_data, (char *)&sess->rcmd->rbuf[0]); 825 826 telnet_sess_send(sess, &send_data); 827 } 828 829 static void send_comp_arg(cli_telnet_sess *sess, 830 pj_cli_exec_info *info) 831 { 832 pj_str_t send_data; 833 char data[128]; 834 835 pj_strcat2(&info->hint[0].name, " "); 836 837 send_data.ptr = &data[0]; 838 send_data.slen = 0; 839 840 pj_strcat(&send_data, &info->hint[0].name); 841 842 telnet_sess_send(sess, &send_data); 843 } 844 845 static pj_bool_t handle_alfa_num(cli_telnet_sess *sess, unsigned char *data) 846 { 847 if (is_local_option_state_ena(sess, ECHO)) { 848 if (recv_buf_right_len(sess->rcmd) > 0) { 849 /* Insert character */ 850 unsigned char echo[5] = {0x1b, 0x5b, 0x31, 0x40, 0x00}; 851 echo[4] = *data; 852 telnet_sess_send2(sess, &echo[0], 5); 853 } else { 854 /* Append character */ 855 telnet_sess_send2(sess, data, 1); 856 } 857 return PJ_TRUE; 858 } 859 return PJ_FALSE; 860 } 861 862 static pj_bool_t handle_backspace(cli_telnet_sess *sess, unsigned char *data) 863 { 864 unsigned rlen = recv_buf_right_len(sess->rcmd); 865 if (recv_buf_backspace(sess->rcmd)) { 866 if (rlen) { 867 /* Cursor is not at the end of line */ 868 unsigned char echo[5] = {0x00, 0x1b, 0x5b, 0x31, 0x50}; 869 echo[0] = *data; 870 telnet_sess_send2(sess, &echo[0], 5); 871 } else { 872 const static unsigned char echo[3] = {0x08, 0x20, 0x08}; 873 telnet_sess_send2(sess, &echo[0], 3); 874 } 875 return PJ_TRUE; 876 } 877 return PJ_FALSE; 878 } 879 880 static pj_bool_t handle_tab(cli_telnet_sess *sess) 881 { 882 pj_status_t status; 883 pj_bool_t retval = PJ_TRUE; 884 unsigned len; 885 886 pj_pool_t *pool; 887 pj_cli_cmd_val *cmd_val; 888 pj_cli_exec_info info; 889 pool = pj_pool_create(sess->pool->factory, "handle_tab", 890 PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC, 891 NULL); 892 893 cmd_val = PJ_POOL_ZALLOC_T(pool, pj_cli_cmd_val); 894 895 status = pj_cli_sess_parse(&sess->base, (char *)&sess->rcmd->rbuf, cmd_val, 896 pool, &info); 897 898 len = pj_ansi_strlen((char *)&sess->rcmd->rbuf[0]); 899 900 switch (status) { 901 case PJ_CLI_EINVARG: 902 send_inv_arg(sess, &info, PJ_TRUE, PJ_TRUE); 903 break; 904 case PJ_CLI_ETOOMANYARGS: 905 send_too_many_arg(sess, &info, PJ_TRUE, PJ_TRUE); 906 break; 907 case PJ_CLI_EMISSINGARG: 908 case PJ_CLI_EAMBIGUOUS: 909 send_ambi_arg(sess, &info, PJ_TRUE, PJ_TRUE); 910 break; 911 case PJ_SUCCESS: 912 if (len > sess->rcmd->cur_pos) 913 { 914 /* Send the cursor to EOL */ 915 unsigned rlen = len - sess->rcmd->cur_pos+1; 916 unsigned char *data_sent = &sess->rcmd->rbuf[sess->rcmd->cur_pos-1]; 917 telnet_sess_send2(sess, data_sent, rlen); 918 } 919 if (info.hint_cnt > 0) { 920 /* Complete command */ 921 send_comp_arg(sess, &info); 922 923 pj_memcpy(&sess->rcmd->rbuf[len], 924 &info.hint[0].name.ptr[0], info.hint[0].name.slen); 925 926 len += info.hint[0].name.slen; 927 sess->rcmd->rbuf[len] = 0; 928 } else { 929 retval = PJ_FALSE; 930 } 931 break; 932 } 933 sess->rcmd->len = len; 934 sess->rcmd->cur_pos = sess->rcmd->len; 935 936 pj_pool_release(pool); 937 return retval; 938 } 939 940 static pj_bool_t handle_return(cli_telnet_sess *sess) 941 { 942 pj_status_t status; 943 pj_bool_t retval = PJ_TRUE; 944 945 pj_pool_t *pool; 946 pj_cli_exec_info info; 947 948 send_return_key(sess); 949 insert_history(sess, (char *)&sess->rcmd->rbuf); 950 951 pool = pj_pool_create(sess->pool->factory, "handle_return", 952 PJ_CLI_TELNET_POOL_SIZE, PJ_CLI_TELNET_POOL_INC, 953 NULL); 954 955 status = pj_cli_sess_exec(&sess->base, (char *)&sess->rcmd->rbuf, 956 pool, &info); 957 958 switch (status) { 959 case PJ_CLI_EINVARG: 960 send_inv_arg(sess, &info, PJ_FALSE, PJ_FALSE); 961 break; 962 case PJ_CLI_ETOOMANYARGS: 963 send_too_many_arg(sess, &info, PJ_FALSE, PJ_FALSE); 964 break; 965 case PJ_CLI_EAMBIGUOUS: 966 case PJ_CLI_EMISSINGARG: 967 send_ambi_arg(sess, &info, PJ_FALSE, PJ_FALSE); 968 break; 969 case PJ_CLI_EEXIT: 970 retval = PJ_FALSE; 971 break; 972 case PJ_SUCCESS: 973 send_prompt_str(sess); 974 break; 975 } 976 if (retval) { 977 sess->rcmd->rbuf[0] = 0; 978 sess->rcmd->len = 0; 979 sess->rcmd->cur_pos = sess->rcmd->len; 980 } 981 982 pj_pool_release(pool); 983 return retval; 984 } 985 986 static pj_bool_t handle_right_key(cli_telnet_sess *sess) 987 { 988 if (recv_buf_right_len(sess->rcmd)) { 989 unsigned char *data = &sess->rcmd->rbuf[sess->rcmd->cur_pos++]; 990 telnet_sess_send2(sess, data, 1); 991 return PJ_TRUE; 992 } 993 return PJ_FALSE; 994 } 995 996 static pj_bool_t handle_left_key(cli_telnet_sess *sess) 997 { 998 const static unsigned char BACK_SPACE = 0x08; 999 if (sess->rcmd->cur_pos) { 1000 telnet_sess_send2(sess, &BACK_SPACE, 1); 1001 --sess->rcmd->cur_pos; 1002 return PJ_TRUE; 1003 } 1004 return PJ_FALSE; 1005 } 1006 1007 static pj_bool_t handle_up_down(cli_telnet_sess *sess, pj_bool_t is_up) 1008 { 1009 pj_str_t *history; 1010 1011 PJ_ASSERT_RETURN(sess, PJ_FALSE); 1012 1013 history = get_prev_history(sess, is_up); 1014 if (history) { 1015 pj_str_t send_data; 1016 char str[PJ_CLI_MAX_CMDBUF]; 1017 send_data.ptr = &str[0]; 1018 send_data.slen = 0; 1019 1020 if (sess->rcmd->cur_pos > 0) { 1021 pj_memset(send_data.ptr, 0x08, sess->rcmd->cur_pos); 1022 send_data.slen = sess->rcmd->cur_pos; 1023 } 1024 1025 if (sess->rcmd->len > (unsigned)history->slen) { 1026 unsigned buf_len = sess->rcmd->len; 1027 pj_memset(&send_data.ptr[send_data.slen], 0x20, buf_len); 1028 send_data.slen += buf_len; 1029 pj_memset(&send_data.ptr[send_data.slen], 0x08, buf_len); 1030 send_data.slen += buf_len; 1031 } 1032 /* Send data */ 1033 pj_strcat(&send_data, history); 1034 telnet_sess_send(sess, &send_data); 1035 pj_ansi_strncpy((char*)&sess->rcmd->rbuf, history->ptr, history->slen); 1036 sess->rcmd->rbuf[history->slen] = 0; 1037 sess->rcmd->len = history->slen; 1038 sess->rcmd->cur_pos = sess->rcmd->len; 1039 return PJ_TRUE; 1040 } 1041 return PJ_FALSE; 1042 } 1043 1044 static pj_status_t process_vt100_cmd(cli_telnet_sess *sess, 1045 unsigned char *cmd) 1046 { 1047 pj_status_t status = PJ_TRUE; 1048 switch (*cmd) { 1049 case TC_ESC: 1050 break; 1051 case TC_UP: 1052 status = handle_up_down(sess, PJ_TRUE); 1053 break; 1054 case TC_DOWN: 1055 status = handle_up_down(sess, PJ_FALSE); 1056 break; 1057 case TC_RIGHT: 1058 status = handle_right_key(sess); 1059 break; 1060 case TC_LEFT: 1061 status = handle_left_key(sess); 1062 break; 1063 case TC_END: 1064 break; 1065 case TC_HOME: 1066 break; 1067 case TC_CTRL_C: 1068 break; 1069 case TC_CR: 1070 break; 1071 case TC_BS: 1072 break; 1073 case TC_TAB: 1074 break; 1075 case TC_QM: 1076 break; 1077 case TC_BELL: 1078 break; 1079 case TC_DEL: 1080 break; 1081 }; 1082 return status; 1083 } 1084 73 1085 PJ_DEF(void) pj_cli_telnet_cfg_default(pj_cli_telnet_cfg *param) 74 1086 { … … 80 1092 } 81 1093 82 83 1094 /* Send a message to a telnet session */ 84 static pj_status_t telnet_sess_send( structcli_telnet_sess *sess,1095 static pj_status_t telnet_sess_send(cli_telnet_sess *sess, 85 1096 const pj_str_t *str) 86 1097 { … … 110 1121 pj_memmove(sess->buf + sess->buf_len, str->ptr, clen); 111 1122 if (clen < sz) { 112 pj_ansi_snprintf( sess->buf + CLI_TELNET_BUF_SIZE,1123 pj_ansi_snprintf((char *)sess->buf + CLI_TELNET_BUF_SIZE, 113 1124 MAX_CUT_MSG_LEN, CUT_MSG); 114 1125 sess->buf_len = CLI_TELNET_BUF_SIZE + 115 pj_ansi_strlen( sess->buf +CLI_TELNET_BUF_SIZE);1126 pj_ansi_strlen((char *)sess->buf+CLI_TELNET_BUF_SIZE); 116 1127 } else 117 1128 sess->buf_len += clen; … … 126 1137 } 127 1138 128 static pj_status_t telnet_sess_send2( structcli_telnet_sess *sess,129 const char *str, int len)1139 static pj_status_t telnet_sess_send2(cli_telnet_sess *sess, 1140 const unsigned char *str, int len) 130 1141 { 131 1142 pj_str_t s; … … 137 1148 static void cli_telnet_sess_destroy(pj_cli_sess *sess) 138 1149 { 139 struct cli_telnet_sess *tsess = (structcli_telnet_sess *)sess;1150 cli_telnet_sess *tsess = (cli_telnet_sess *)sess; 140 1151 pj_mutex_t *mutex = ((struct cli_telnet_fe *)sess->fe)->mutex; 141 1152 … … 161 1172 sess = tfe->sess_head.next; 162 1173 while (sess != &tfe->sess_head) { 163 struct cli_telnet_sess *tsess = (structcli_telnet_sess *)sess;164 165 sess = sess->next; 166 if (tsess->base.log_level > level && tsess->authorized)167 telnet_sess_send2(tsess, data, len);1174 cli_telnet_sess *tsess = (cli_telnet_sess *)sess; 1175 1176 sess = sess->next; 1177 if (tsess->base.log_level > level) 1178 telnet_sess_send2(tsess, (unsigned char *)data, len); 168 1179 } 169 1180 … … 214 1225 pj_ssize_t sent) 215 1226 { 216 struct cli_telnet_sess *sess = (structcli_telnet_sess *)217 1227 cli_telnet_sess *sess = (cli_telnet_sess *) 1228 pj_activesock_get_user_data(asock); 218 1229 219 1230 PJ_UNUSED_ARG(op_key); 220 1231 221 1232 if (sent <= 0) { 222 pj_cli_ end_session(&sess->base);1233 pj_cli_sess_end_session(&sess->base); 223 1234 return PJ_FALSE; 224 1235 } … … 232 1243 if (telnet_sess_send2(sess, sess->buf, len) != PJ_SUCCESS) { 233 1244 pj_mutex_unlock(sess->smutex); 234 pj_cli_ end_session(&sess->base);1245 pj_cli_sess_end_session(&sess->base); 235 1246 return PJ_FALSE; 236 1247 } … … 248 1259 pj_size_t *remainder) 249 1260 { 250 struct cli_telnet_sess *sess = (structcli_telnet_sess *)251 1261 cli_telnet_sess *sess = (cli_telnet_sess *) 1262 pj_activesock_get_user_data(asock); 252 1263 struct cli_telnet_fe *tfe = (struct cli_telnet_fe *)sess->base.fe; 253 char *cdata = (char*)data; 1264 unsigned char *cdata = (unsigned char*)data; 1265 pj_status_t is_valid = PJ_TRUE; 254 1266 255 1267 PJ_UNUSED_ARG(size); … … 257 1269 258 1270 if (status != PJ_SUCCESS && status != PJ_EPENDING) { 259 pj_cli_ end_session(&sess->base);1271 pj_cli_sess_end_session(&sess->base); 260 1272 return PJ_FALSE; 261 1273 } … … 266 1278 pj_mutex_lock(sess->smutex); 267 1279 268 if (*cdata == 8) { // Backspace 269 if (sess->len > 0) 270 sess->len--; 271 if (telnet_sess_send2(sess, " \b", 2) != PJ_SUCCESS) 272 goto on_exit; 273 } else { 274 if (sess->len < PJ_CLI_MAX_CMDBUF - 1) 275 sess->rcmd[sess->len++] = *cdata; 276 if (*cdata == '\n') { 277 /* Trim trailing newlines */ 278 for (; sess->len > 0;) { 279 if (sess->rcmd[sess->len - 1] == '\n' || 280 sess->rcmd[sess->len - 1] == '\r') 281 sess->rcmd[--sess->len] = 0; 282 else 283 break; 284 } 285 if (sess->len > 0 && sess->rcmd[sess->len - 1] != 0) 286 sess->rcmd[sess->len++] = 0; 287 288 /* If a password is necessary, check whether the supplied password 289 * is correct. 290 */ 291 if (!sess->authorized && 292 ((struct cli_telnet_fe *)sess->base.fe)->cfg.passwd.slen > 0) 293 { 294 if (pj_strcmp2(&tfe->cfg.passwd, sess->rcmd)) { 295 pj_str_t str = pj_str("Wrong password!\r\n"); 296 297 telnet_sess_send(sess, &str); 298 goto on_exit; 299 } else { 300 /* Password is correct, send welcome message */ 301 if (telnet_sess_send(sess, &tfe->cfg.welcome_msg) 302 != PJ_SUCCESS) 303 { 304 goto on_exit; 305 } 306 sess->authorized = PJ_TRUE; 307 } 308 } else { 309 pj_status_t status; 310 311 pj_mutex_unlock(sess->smutex); 312 status = pj_cli_exec(&sess->base, sess->rcmd, NULL); 313 if (status == PJ_CLI_EEXIT) 314 return PJ_FALSE; 315 pj_mutex_lock(sess->smutex); 316 } 317 sess->len = 0; 318 } else if (!sess->authorized && 319 ((struct cli_telnet_fe *)sess->base.fe)->cfg.passwd.slen > 0 && 320 *cdata != '\r') 321 { 322 if (telnet_sess_send2(sess, "\b ", 2) != PJ_SUCCESS) 323 goto on_exit; 324 } 325 1280 switch (sess->parse_state) { 1281 case ST_CR: 1282 sess->parse_state = ST_NORMAL; 1283 if (*cdata == 0 || *cdata == '\n') 1284 pj_mutex_unlock(sess->smutex); 1285 is_valid = handle_return(sess); 1286 if (!is_valid) 1287 return PJ_FALSE; 1288 pj_mutex_lock(sess->smutex); 1289 break; 1290 case ST_NORMAL: 1291 if (*cdata == IAC) { 1292 sess->parse_state = ST_IAC; 1293 } else if (*cdata == '\b') { 1294 is_valid = handle_backspace(sess, cdata); 1295 } else if (*cdata == 27) { 1296 sess->parse_state = ST_ESC; 1297 } else { 1298 if (recv_buf_insert(sess->rcmd, cdata)) { 1299 if (*cdata == '\r') { 1300 sess->parse_state = ST_CR; 1301 } else if ((*cdata == '\t') || (*cdata == '?')) { 1302 is_valid = handle_tab(sess); 1303 } else if (*cdata > 31 && *cdata < 127) { 1304 is_valid = handle_alfa_num(sess, cdata); 1305 } 1306 } else { 1307 is_valid = PJ_FALSE; 1308 } 1309 } 1310 break; 1311 case ST_ESC: 1312 if (*cdata == 91) { 1313 sess->parse_state = ST_VT100; 1314 } else { 1315 sess->parse_state = ST_NORMAL; 1316 } 1317 break; 1318 case ST_VT100: 1319 sess->parse_state = ST_NORMAL; 1320 is_valid = process_vt100_cmd(sess, cdata); 1321 break; 1322 case ST_IAC: 1323 switch ((unsigned) *cdata) { 1324 case DO: 1325 sess->parse_state = ST_DO; 1326 break; 1327 case DONT: 1328 sess->parse_state = ST_DONT; 1329 break; 1330 case WILL: 1331 sess->parse_state = ST_WILL; 1332 break; 1333 case WONT: 1334 sess->parse_state = ST_WONT; 1335 break; 1336 default: 1337 sess->parse_state = ST_NORMAL; 1338 break; 1339 } 1340 break; 1341 case ST_DO: 1342 receive_do(sess, *cdata); 1343 sess->parse_state = ST_NORMAL; 1344 break; 1345 case ST_DONT: 1346 receive_dont(sess, *cdata); 1347 sess->parse_state = ST_NORMAL; 1348 break; 1349 case ST_WILL: 1350 receive_will(sess, *cdata); 1351 sess->parse_state = ST_NORMAL; 1352 break; 1353 case ST_WONT: 1354 receive_wont(sess, *cdata); 1355 sess->parse_state = ST_NORMAL; 1356 break; 1357 default: 1358 sess->parse_state = ST_NORMAL; 1359 break; 1360 } 1361 if (!is_valid) { 1362 send_bell(sess); 326 1363 } 327 1364 … … 329 1366 330 1367 return PJ_TRUE; 331 332 on_exit:333 pj_mutex_unlock(sess->smutex);334 pj_cli_end_session(&sess->base);335 return PJ_FALSE;336 1368 } 337 1369 … … 345 1377 pj_status_t sstatus; 346 1378 pj_pool_t *pool; 347 structcli_telnet_sess *sess;1379 cli_telnet_sess *sess; 348 1380 pj_activesock_cb asock_cb; 349 1381 … … 364 1396 } 365 1397 366 sess = PJ_POOL_ZALLOC_T(pool, structcli_telnet_sess);1398 sess = PJ_POOL_ZALLOC_T(pool, cli_telnet_sess); 367 1399 sess->pool = pool; 368 1400 sess->base.fe = &fe->base; … … 373 1405 asock_cb.on_data_read = &telnet_sess_on_data_read; 374 1406 asock_cb.on_data_sent = &telnet_sess_on_data_sent; 1407 sess->rcmd = PJ_POOL_ZALLOC_T(pool, telnet_recv_buf); 1408 sess->history = PJ_POOL_ZALLOC_T(pool, struct cmd_history); 1409 pj_list_init(sess->history); 1410 sess->active_history = sess->history; 375 1411 376 1412 sstatus = pj_mutex_create_recursive(pool, "mutex_telnet_sess", … … 387 1423 } 388 1424 389 /* Prompt for password if required, otherwise directly send 390 * a welcome message. 391 */ 392 if (fe->cfg.passwd.slen) { 393 pj_str_t pwd = pj_str("Password: "); 394 if (telnet_sess_send(sess, &pwd) != PJ_SUCCESS) 395 goto on_exit; 396 } else { 397 if (pj_strlen(&fe->cfg.welcome_msg)) { 398 if (telnet_sess_send(sess, &fe->cfg.welcome_msg) != PJ_SUCCESS) 399 goto on_exit; 400 } else { 401 if (telnet_sess_send2(sess, " \b", 2) != PJ_SUCCESS) 402 goto on_exit; 403 } 404 sess->authorized = PJ_TRUE; 405 } 1425 pj_memset(sess->telnet_option, 0, sizeof(sess->telnet_option)); 1426 set_local_option(sess, TRANSMIT_BINARY, PJ_TRUE); 1427 set_local_option(sess, STATUS, PJ_TRUE); 1428 set_local_option(sess, SUPPRESS_GA, PJ_TRUE); 1429 set_local_option(sess, TIMING_MARK, PJ_TRUE); 1430 set_local_option(sess, TERM_SPEED, PJ_TRUE); 1431 set_local_option(sess, TERM_TYPE, PJ_TRUE); 1432 1433 set_peer_option(sess, TRANSMIT_BINARY, PJ_TRUE); 1434 set_peer_option(sess, SUPPRESS_GA, PJ_TRUE); 1435 set_peer_option(sess, STATUS, PJ_TRUE); 1436 set_peer_option(sess, TIMING_MARK, PJ_TRUE); 1437 set_peer_option(sess, ECHO, PJ_TRUE); 1438 1439 send_cmd_do(sess, SUPPRESS_GA); 1440 send_cmd_will(sess, ECHO); 1441 send_cmd_will(sess, STATUS); 1442 send_cmd_will(sess, SUPPRESS_GA); 1443 1444 /* Send prompt string */ 1445 telnet_sess_send(sess, &fe->cfg.prompt_str); 406 1446 407 1447 /* Start reading for input from the new telnet session */ … … 462 1502 fe->base.type = PJ_CLI_TELNET_FRONT_END; 463 1503 fe->base.op->on_write_log = &cli_telnet_fe_write_log; 464 // fe->base.op->on_quit = &cli_telnet_fe_quit;465 1504 fe->base.op->on_destroy = &cli_telnet_fe_destroy; 466 1505 fe->pool = pool; … … 497 1536 PJ_LOG(3, (THIS_FILE, "CLI telnet daemon listening at port %d", 498 1537 fe->cfg.port)); 1538 1539 if (fe->cfg.prompt_str.slen == 0) { 1540 pj_str_t prompt_sign = {"> ", 2}; 1541 char *prompt_data = pj_pool_alloc(fe->pool, 1542 pj_gethostname()->slen+2); 1543 fe->cfg.prompt_str.ptr = prompt_data; 1544 1545 pj_strcpy(&fe->cfg.prompt_str, pj_gethostname()); 1546 pj_strcat(&fe->cfg.prompt_str, &prompt_sign); 1547 } 499 1548 } else { 500 1549 PJ_LOG(3, (THIS_FILE, "Failed binding the socket")); -
pjproject/branches/projects/cli/pjsip-apps/src/samples/clidemo.c
r3232 r4299 34 34 * Default: 1 35 35 */ 36 #define USE_RANDOM_PORT 136 #define USE_RANDOM_PORT 0 37 37 38 38 struct cmd_xml_t { … … 82 82 } 83 83 84 static pj_status_t quit (pj_cli_cmd_val *cval)84 static pj_status_t quit_app(pj_cli_cmd_val *cval) 85 85 { 86 86 PJ_UNUSED_ARG(cval); 87 pj_cli_ end_session(cval->sess);87 pj_cli_quit(cval->sess->fe->cli, cval->sess, PJ_FALSE); 88 88 89 89 return PJ_CLI_EEXIT; … … 92 92 static struct cmd_xml_t cmd_xmls[] = { 93 93 {"<CMD name='sayhello' id='1' sc=' ,h , ,, sh ,' desc='Will say hello'>" 94 " <ARGS>"95 94 " <ARG name='whom' type='text' desc='Whom to say hello to'/>" 96 " </ARGS>"97 95 "</CMD>", 98 96 &sayhello}, 99 97 {"<CMD name='saybye' id='2' sc='b,sb' desc='Will say bye'>" 100 " <ARGS>"101 98 " <ARG name='whom' type='text' desc='Whom to say bye to'/>" 102 " </ARGS>"103 99 "</CMD>", 104 100 &saybye}, 105 {"<CMD name=' say ' id='3' sc='s' desc='Will say something'>" 106 " <ARGS>" 101 {"<CMD name='saymsg' id='3' sc='s' desc='Will say something'>" 107 102 " <ARG name='msg' type='text' desc='Message to say'/>" 108 103 " <ARG name='whom' type='text' desc='Whom to say to'/>" 109 " </ARGS>"110 104 "</CMD>", 111 105 &say}, 112 {"<CMD name='quit' id='999' sc='q' desc='Quit the application'>" 113 "</CMD>", 114 &quit}, 106 {"<CMD name='vid' id='1' desc='Video Command'>" 107 " <CMD name='help' id='2' desc='Show Help' />" 108 " <CMD name='enable' id='3' desc='Enable Video' />" 109 " <CMD name='disable' id='4' desc='Disable Video' />" 110 " <CMD name='call' id='5' desc='Video call' >" 111 " <CMD name='add' id='6' desc='Add Call' />" 112 " <CMD name='cap' id='7' desc='Capture Call' >" 113 " <ARG name='streamno' type='int' desc='Stream Number' id='1'/>" 114 " <ARG name='devid' type='int' desc='Device Id' optional='1' id='2'/>" 115 " </CMD>" 116 " </CMD>" 117 "</CMD>", 118 NULL}, 119 {"<CMD name='quit_app' id='999' sc='qa' desc='Quit the application'>" 120 "</CMD>", 121 &quit_app}, 115 122 }; 116 123 … … 128 135 pj_str_t xml; 129 136 pj_status_t status; 130 int i; 137 int i; 131 138 132 139 pj_init(); … … 150 157 */ 151 158 for (i = 0; i < sizeof(cmd_xmls)/sizeof(cmd_xmls[0]); i++) { 152 xml = pj_str(cmd_xmls[i].xml); 159 xml = pj_str(cmd_xmls[i].xml); 153 160 status = pj_cli_add_cmd_from_xml(cli, NULL, &xml, 154 cmd_xmls[i].handler, NULL );161 cmd_xmls[i].handler, NULL, NULL); 155 162 if (status != PJ_SUCCESS) 156 163 goto on_return; … … 210 217 */ 211 218 for (;;) { 212 char cmdline[PJ_CLI_MAX_CMDBUF];213 219 pj_status_t status; 214 220 215 status = pj_cli_console_ readline(sess, cmdline, sizeof(cmdline));221 status = pj_cli_console_process(sess); 216 222 if (status != PJ_SUCCESS) 217 223 break; 218 224 219 // pj_ansi_strcpy(cmdline, "sayhello {Teluu Inc.}"); 220 status = pj_cli_exec(sess, cmdline, NULL); 225 //pj_ansi_strcpy(cmdline, "sayhello {Teluu Inc.}"); 221 226 if (status == PJ_CLI_EEXIT) { 222 227 /* exit is called */ -
pjproject/branches/projects/cli/pjsip-apps/src/samples/debug.c
r3028 r4299 29 29 * #include "playfile.c" 30 30 */ 31 #include " icedemo.c"31 #include "clidemo.c" 32 32 -
pjproject/branches/projects/cli/third_party
- Property svn:externals
-
old new 1 portaudio -r1433 https://www.portaudio.com/repos/portaudio/trunk1 portaudio -r 1433 https://subversion.assembla.com/svn/portaudio/portaudio/trunk
-
- Property svn:externals
Note: See TracChangeset
for help on using the changeset viewer.