Subversion Repositories HelenOS

Rev

Rev 1963 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * Copyright (C) 2005 Jakub Jermar
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  *
  9.  * - Redistributions of source code must retain the above copyright
  10.  *   notice, this list of conditions and the following disclaimer.
  11.  * - Redistributions in binary form must reproduce the above copyright
  12.  *   notice, this list of conditions and the following disclaimer in the
  13.  *   documentation and/or other materials provided with the distribution.
  14.  * - The name of the author may not be used to endorse or promote products
  15.  *   derived from this software without specific prior written permission.
  16.  *
  17.  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  18.  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  19.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  20.  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  21.  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  22.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  23.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  24.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  26.  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27.  */
  28.  
  29. /**
  30.  * @file    cmd.c
  31.  * @brief   Kernel console command wrappers.
  32.  *
  33.  * This file is meant to contain all wrapper functions for
  34.  * all kconsole commands. The point is in separating
  35.  * kconsole specific wrappers from kconsole-unaware functions
  36.  * from other subsystems.
  37.  */
  38.  
  39. #include <console/cmd.h>
  40. #include <console/kconsole.h>
  41. #include <print.h>
  42. #include <panic.h>
  43. #include <typedefs.h>
  44. #include <arch/types.h>
  45. #include <adt/list.h>
  46. #include <arch.h>
  47. #include <func.h>
  48. #include <macros.h>
  49. #include <debug.h>
  50. #include <symtab.h>
  51. #include <cpu.h>
  52. #include <mm/tlb.h>
  53. #include <arch/mm/tlb.h>
  54. #include <mm/frame.h>
  55. #include <main/version.h>
  56. #include <mm/slab.h>
  57. #include <proc/scheduler.h>
  58. #include <proc/thread.h>
  59. #include <proc/task.h>
  60.  
  61. /** Data and methods for 'help' command. */
  62. static int cmd_help(cmd_arg_t *argv);
  63. static cmd_info_t help_info = {
  64.     .name = "help",
  65.     .description = "List of supported commands.",
  66.     .func = cmd_help,
  67.     .argc = 0
  68. };
  69.  
  70. static cmd_info_t exit_info = {
  71.     .name = "exit",
  72.     .description ="Exit kconsole",
  73.     .argc = 0
  74. };
  75.  
  76. /** Data and methods for 'description' command. */
  77. static int cmd_desc(cmd_arg_t *argv);
  78. static void desc_help(void);
  79. static char desc_buf[MAX_CMDLINE+1];
  80. static cmd_arg_t desc_argv = {
  81.     .type = ARG_TYPE_STRING,
  82.     .buffer = desc_buf,
  83.     .len = sizeof(desc_buf)
  84. };
  85. static cmd_info_t desc_info = {
  86.     .name = "describe",
  87.     .description = "Describe specified command.",
  88.     .help = desc_help,
  89.     .func = cmd_desc,
  90.     .argc = 1,
  91.     .argv = &desc_argv
  92. };
  93.  
  94. /** Data and methods for 'symaddr' command. */
  95. static int cmd_symaddr(cmd_arg_t *argv);
  96. static char symaddr_buf[MAX_CMDLINE+1];
  97. static cmd_arg_t symaddr_argv = {
  98.     .type = ARG_TYPE_STRING,
  99.     .buffer = symaddr_buf,
  100.     .len = sizeof(symaddr_buf)
  101. };
  102. static cmd_info_t symaddr_info = {
  103.     .name = "symaddr",
  104.     .description = "Return symbol address.",
  105.     .func = cmd_symaddr,
  106.     .argc = 1,
  107.     .argv = &symaddr_argv
  108. };
  109.  
  110. static char set_buf[MAX_CMDLINE+1];
  111. static int cmd_set4(cmd_arg_t *argv);
  112. static cmd_arg_t set4_argv[] = {
  113.     {
  114.         .type = ARG_TYPE_STRING,
  115.         .buffer = set_buf,
  116.         .len = sizeof(set_buf)
  117.     },
  118.     {
  119.         .type = ARG_TYPE_INT
  120.     }
  121. };
  122. static cmd_info_t set4_info = {
  123.     .name = "set4",
  124.     .description = "set <dest_addr> <value> - 4byte version",
  125.     .func = cmd_set4,
  126.     .argc = 2,
  127.     .argv = set4_argv
  128. };
  129.  
  130. /** Data and methods for 'call0' command. */
  131. static char call0_buf[MAX_CMDLINE+1];
  132. static char carg1_buf[MAX_CMDLINE+1];
  133. static char carg2_buf[MAX_CMDLINE+1];
  134. static char carg3_buf[MAX_CMDLINE+1];
  135.  
  136. static int cmd_call0(cmd_arg_t *argv);
  137. static cmd_arg_t call0_argv = {
  138.     .type = ARG_TYPE_STRING,
  139.     .buffer = call0_buf,
  140.     .len = sizeof(call0_buf)
  141. };
  142. static cmd_info_t call0_info = {
  143.     .name = "call0",
  144.     .description = "call0 <function> -> call function().",
  145.     .func = cmd_call0,
  146.     .argc = 1,
  147.     .argv = &call0_argv
  148. };
  149.  
  150. /** Data and methods for 'call1' command. */
  151. static int cmd_call1(cmd_arg_t *argv);
  152. static cmd_arg_t call1_argv[] = {
  153.     {
  154.         .type = ARG_TYPE_STRING,
  155.         .buffer = call0_buf,
  156.         .len = sizeof(call0_buf)
  157.     },
  158.     {
  159.         .type = ARG_TYPE_VAR,
  160.         .buffer = carg1_buf,
  161.         .len = sizeof(carg1_buf)
  162.     }
  163. };
  164. static cmd_info_t call1_info = {
  165.     .name = "call1",
  166.     .description = "call1 <function> <arg1> -> call function(arg1).",
  167.     .func = cmd_call1,
  168.     .argc = 2,
  169.     .argv = call1_argv
  170. };
  171.  
  172. /** Data and methods for 'call2' command. */
  173. static int cmd_call2(cmd_arg_t *argv);
  174. static cmd_arg_t call2_argv[] = {
  175.     {
  176.         .type = ARG_TYPE_STRING,
  177.         .buffer = call0_buf,
  178.         .len = sizeof(call0_buf)
  179.     },
  180.     {
  181.         .type = ARG_TYPE_VAR,
  182.         .buffer = carg1_buf,
  183.         .len = sizeof(carg1_buf)
  184.     },
  185.     {
  186.         .type = ARG_TYPE_VAR,
  187.         .buffer = carg2_buf,
  188.         .len = sizeof(carg2_buf)
  189.     }
  190. };
  191. static cmd_info_t call2_info = {
  192.     .name = "call2",
  193.     .description = "call2 <function> <arg1> <arg2> -> call function(arg1,arg2).",
  194.     .func = cmd_call2,
  195.     .argc = 3,
  196.     .argv = call2_argv
  197. };
  198.  
  199. /** Data and methods for 'call3' command. */
  200. static int cmd_call3(cmd_arg_t *argv);
  201. static cmd_arg_t call3_argv[] = {
  202.     {
  203.         .type = ARG_TYPE_STRING,
  204.         .buffer = call0_buf,
  205.         .len = sizeof(call0_buf)
  206.     },
  207.     {
  208.         .type = ARG_TYPE_VAR,
  209.         .buffer = carg1_buf,
  210.         .len = sizeof(carg1_buf)
  211.     },
  212.     {
  213.         .type = ARG_TYPE_VAR,
  214.         .buffer = carg2_buf,
  215.         .len = sizeof(carg2_buf)
  216.     },
  217.     {
  218.         .type = ARG_TYPE_VAR,
  219.         .buffer = carg3_buf,
  220.         .len = sizeof(carg3_buf)
  221.     }
  222.  
  223. };
  224. static cmd_info_t call3_info = {
  225.     .name = "call3",
  226.     .description = "call3 <function> <arg1> <arg2> <arg3> -> call function(arg1,arg2,arg3).",
  227.     .func = cmd_call3,
  228.     .argc = 4,
  229.     .argv = call3_argv
  230. };
  231.  
  232. /** Data and methods for 'halt' command. */
  233. static int cmd_halt(cmd_arg_t *argv);
  234. static cmd_info_t halt_info = {
  235.     .name = "halt",
  236.     .description = "Halt the kernel.",
  237.     .func = cmd_halt,
  238.     .argc = 0
  239. };
  240.  
  241. /** Data and methods for 'tlb' command. */
  242. static int cmd_tlb(cmd_arg_t *argv);
  243. cmd_info_t tlb_info = {
  244.     .name = "tlb",
  245.     .description = "Print TLB of current processor.",
  246.     .help = NULL,
  247.     .func = cmd_tlb,
  248.     .argc = 0,
  249.     .argv = NULL
  250. };
  251.  
  252. static int cmd_threads(cmd_arg_t *argv);
  253. static cmd_info_t threads_info = {
  254.     .name = "threads",
  255.     .description = "List all threads",
  256.     .func = cmd_threads,
  257.     .argc = 0
  258. };
  259.  
  260. static int cmd_tasks(cmd_arg_t *argv);
  261. static cmd_info_t tasks_info = {
  262.     .name = "tasks",
  263.     .description = "List all tasks",
  264.     .func = cmd_tasks,
  265.     .argc = 0
  266. };
  267.  
  268.  
  269. static int cmd_sched(cmd_arg_t *argv);
  270. static cmd_info_t sched_info = {
  271.     .name = "scheduler",
  272.     .description = "List all scheduler information",
  273.     .func = cmd_sched,
  274.     .argc = 0
  275. };
  276.  
  277. static int cmd_slabs(cmd_arg_t *argv);
  278. static cmd_info_t slabs_info = {
  279.     .name = "slabs",
  280.     .description = "List SLAB caches.",
  281.     .func = cmd_slabs,
  282.     .argc = 0
  283. };
  284.  
  285. /** Data and methods for 'zones' command */
  286. static int cmd_zones(cmd_arg_t *argv);
  287. static cmd_info_t zones_info = {
  288.     .name = "zones",
  289.     .description = "List of memory zones.",
  290.     .func = cmd_zones,
  291.     .argc = 0
  292. };
  293.  
  294. /** Data and methods for 'zone' command */
  295. static int cmd_zone(cmd_arg_t *argv);
  296. static cmd_arg_t zone_argv = {
  297.     .type = ARG_TYPE_INT,
  298. };
  299.  
  300. static cmd_info_t zone_info = {
  301.     .name = "zone",
  302.     .description = "Show memory zone structure.",
  303.     .func = cmd_zone,
  304.     .argc = 1,
  305.     .argv = &zone_argv
  306. };
  307.  
  308. /** Data and methods for 'cpus' command. */
  309. static int cmd_cpus(cmd_arg_t *argv);
  310. cmd_info_t cpus_info = {
  311.     .name = "cpus",
  312.     .description = "List all processors.",
  313.     .help = NULL,
  314.     .func = cmd_cpus,
  315.     .argc = 0,
  316.     .argv = NULL
  317. };
  318.  
  319. /** Data and methods for 'version' command. */
  320. static int cmd_version(cmd_arg_t *argv);
  321. cmd_info_t version_info = {
  322.     .name = "version",
  323.     .description = "Print version information.",
  324.     .help = NULL,
  325.     .func = cmd_version,
  326.     .argc = 0,
  327.     .argv = NULL
  328. };
  329.  
  330. static cmd_info_t *basic_commands[] = {
  331.     &call0_info,
  332.     &call1_info,
  333.     &call2_info,
  334.     &call3_info,
  335.     &cpus_info,
  336.     &desc_info,
  337.     &exit_info,
  338.     &halt_info,
  339.     &help_info,
  340.     &set4_info,
  341.     &slabs_info,
  342.     &symaddr_info,
  343.     &sched_info,
  344.     &threads_info,
  345.     &tasks_info,
  346.     &tlb_info,
  347.     &version_info,
  348.     &zones_info,
  349.     &zone_info,
  350.     NULL
  351. };
  352.  
  353.  
  354. /** Initialize command info structure.
  355.  *
  356.  * @param cmd Command info structure.
  357.  *
  358.  */
  359. void cmd_initialize(cmd_info_t *cmd)
  360. {
  361.     spinlock_initialize(&cmd->lock, "cmd");
  362.     link_initialize(&cmd->link);
  363. }
  364.  
  365. /** Initialize and register commands. */
  366. void cmd_init(void)
  367. {
  368.     int i;
  369.  
  370.     for (i=0;basic_commands[i]; i++) {
  371.         cmd_initialize(basic_commands[i]);
  372.         if (!cmd_register(basic_commands[i]))
  373.             panic("could not register command %s\n",
  374.                   basic_commands[i]->name);
  375.     }
  376. }
  377.  
  378.  
  379. /** List supported commands.
  380.  *
  381.  * @param argv Argument vector.
  382.  *
  383.  * @return 0 on failure, 1 on success.
  384.  */
  385. int cmd_help(cmd_arg_t *argv)
  386. {
  387.     link_t *cur;
  388.  
  389.     spinlock_lock(&cmd_lock);
  390.    
  391.     for (cur = cmd_head.next; cur != &cmd_head; cur = cur->next) {
  392.         cmd_info_t *hlp;
  393.        
  394.         hlp = list_get_instance(cur, cmd_info_t, link);
  395.         spinlock_lock(&hlp->lock);
  396.        
  397.         printf("%s - %s\n", hlp->name, hlp->description);
  398.  
  399.         spinlock_unlock(&hlp->lock);
  400.     }
  401.    
  402.     spinlock_unlock(&cmd_lock);
  403.  
  404.     return 1;
  405. }
  406.  
  407. /** Describe specified command.
  408.  *
  409.  * @param argv Argument vector.
  410.  *
  411.  * @return 0 on failure, 1 on success.
  412.  */
  413. int cmd_desc(cmd_arg_t *argv)
  414. {
  415.     link_t *cur;
  416.  
  417.     spinlock_lock(&cmd_lock);
  418.    
  419.     for (cur = cmd_head.next; cur != &cmd_head; cur = cur->next) {
  420.         cmd_info_t *hlp;
  421.        
  422.         hlp = list_get_instance(cur, cmd_info_t, link);
  423.         spinlock_lock(&hlp->lock);
  424.  
  425.         if (strncmp(hlp->name, (const char *) argv->buffer, strlen(hlp->name)) == 0) {
  426.             printf("%s - %s\n", hlp->name, hlp->description);
  427.             if (hlp->help)
  428.                 hlp->help();
  429.             spinlock_unlock(&hlp->lock);
  430.             break;
  431.         }
  432.  
  433.         spinlock_unlock(&hlp->lock);
  434.     }
  435.    
  436.     spinlock_unlock(&cmd_lock);
  437.  
  438.     return 1;
  439. }
  440.  
  441. /** Search symbol table */
  442. int cmd_symaddr(cmd_arg_t *argv)
  443. {
  444.     symtab_print_search(argv->buffer);
  445.    
  446.     return 1;
  447. }
  448.  
  449. /** Call function with zero parameters */
  450. int cmd_call0(cmd_arg_t *argv)
  451. {
  452.     __address symaddr;
  453.     char *symbol;
  454.     __native (*f)(void);
  455.  
  456.     symaddr = get_symbol_addr(argv->buffer);
  457.     if (!symaddr)
  458.         printf("Symbol %s not found.\n", argv->buffer);
  459.     else if (symaddr == (__address) -1) {
  460.         symtab_print_search(argv->buffer);
  461.         printf("Duplicate symbol, be more specific.\n");
  462.     } else {
  463.         symbol = get_symtab_entry(symaddr);
  464.         printf("Calling f(): %.*p: %s\n", sizeof(__address) * 2, symaddr, symbol);
  465.         f =  (__native (*)(void)) symaddr;
  466.         printf("Result: %#zx\n", f());
  467.     }
  468.    
  469.     return 1;
  470. }
  471.  
  472. /** Call function with one parameter */
  473. int cmd_call1(cmd_arg_t *argv)
  474. {
  475.     __address symaddr;
  476.     char *symbol;
  477.     __native (*f)(__native,...);
  478.     __native arg1 = argv[1].intval;
  479.  
  480.     symaddr = get_symbol_addr(argv->buffer);
  481.     if (!symaddr)
  482.         printf("Symbol %s not found.\n", argv->buffer);
  483.     else if (symaddr == (__address) -1) {
  484.         symtab_print_search(argv->buffer);
  485.         printf("Duplicate symbol, be more specific.\n");
  486.     } else {
  487.         symbol = get_symtab_entry(symaddr);
  488.         printf("Calling f(0x%zX): %.*p: %s\n", arg1, sizeof(__address) * 2, symaddr, symbol);
  489.         f =  (__native (*)(__native,...)) symaddr;
  490.         printf("Result: %#zx\n", f(arg1));
  491.     }
  492.    
  493.     return 1;
  494. }
  495.  
  496. /** Call function with two parameters */
  497. int cmd_call2(cmd_arg_t *argv)
  498. {
  499.     __address symaddr;
  500.     char *symbol;
  501.     __native (*f)(__native,__native,...);
  502.     __native arg1 = argv[1].intval;
  503.     __native arg2 = argv[2].intval;
  504.  
  505.     symaddr = get_symbol_addr(argv->buffer);
  506.     if (!symaddr)
  507.         printf("Symbol %s not found.\n", argv->buffer);
  508.     else if (symaddr == (__address) -1) {
  509.         symtab_print_search(argv->buffer);
  510.         printf("Duplicate symbol, be more specific.\n");
  511.     } else {
  512.         symbol = get_symtab_entry(symaddr);
  513.         printf("Calling f(0x%zx,0x%zx): %.*p: %s\n",
  514.                arg1, arg2, sizeof(__address) * 2, symaddr, symbol);
  515.         f =  (__native (*)(__native,__native,...)) symaddr;
  516.         printf("Result: %#zx\n", f(arg1, arg2));
  517.     }
  518.    
  519.     return 1;
  520. }
  521.  
  522. /** Call function with three parameters */
  523. int cmd_call3(cmd_arg_t *argv)
  524. {
  525.     __address symaddr;
  526.     char *symbol;
  527.     __native (*f)(__native,__native,__native,...);
  528.     __native arg1 = argv[1].intval;
  529.     __native arg2 = argv[2].intval;
  530.     __native arg3 = argv[3].intval;
  531.  
  532.     symaddr = get_symbol_addr(argv->buffer);
  533.     if (!symaddr)
  534.         printf("Symbol %s not found.\n", argv->buffer);
  535.     else if (symaddr == (__address) -1) {
  536.         symtab_print_search(argv->buffer);
  537.         printf("Duplicate symbol, be more specific.\n");
  538.     } else {
  539.         symbol = get_symtab_entry(symaddr);
  540.         printf("Calling f(0x%zx,0x%zx, 0x%zx): %.*p: %s\n",
  541.                arg1, arg2, arg3, sizeof(__address) * 2, symaddr, symbol);
  542.         f =  (__native (*)(__native,__native,__native,...)) symaddr;
  543.         printf("Result: %#zx\n", f(arg1, arg2, arg3));
  544.     }
  545.    
  546.     return 1;
  547. }
  548.  
  549.  
  550. /** Print detailed description of 'describe' command. */
  551. void desc_help(void)
  552. {
  553.     printf("Syntax: describe command_name\n");
  554. }
  555.  
  556. /** Halt the kernel.
  557.  *
  558.  * @param argv Argument vector (ignored).
  559.  *
  560.  * @return 0 on failure, 1 on success (never returns).
  561.  */
  562. int cmd_halt(cmd_arg_t *argv)
  563. {
  564.     halt();
  565.     return 1;
  566. }
  567.  
  568. /** Command for printing TLB contents.
  569.  *
  570.  * @param argv Not used.
  571.  *
  572.  * @return Always returns 1.
  573.  */
  574. int cmd_tlb(cmd_arg_t *argv)
  575. {
  576.     tlb_print();
  577.     return 1;
  578. }
  579.  
  580. /** Write 4 byte value to address */
  581. int cmd_set4(cmd_arg_t *argv)
  582. {
  583.     __u32 *addr ;
  584.     __u32 arg1 = argv[1].intval;
  585.     bool pointer = false;
  586.  
  587.     if (((char *)argv->buffer)[0] == '*') {
  588.         addr = (__u32 *) get_symbol_addr(argv->buffer+1);
  589.         pointer = true;
  590.     } else if (((char *)argv->buffer)[0] >= '0' &&
  591.            ((char *)argv->buffer)[0] <= '9')
  592.         addr = (__u32 *)atoi((char *)argv->buffer);
  593.     else
  594.         addr = (__u32 *)get_symbol_addr(argv->buffer);
  595.  
  596.     if (!addr)
  597.         printf("Symbol %s not found.\n", argv->buffer);
  598.     else if (addr == (__u32 *) -1) {
  599.         symtab_print_search(argv->buffer);
  600.         printf("Duplicate symbol, be more specific.\n");
  601.     } else {
  602.         if (pointer)
  603.             addr = (__u32 *)(*(__native *)addr);
  604.         printf("Writing 0x%x -> %.*p\n", arg1, sizeof(__address) * 2, addr);
  605.         *addr = arg1;
  606.        
  607.     }
  608.    
  609.     return 1;
  610. }
  611.  
  612. /** Command for listings SLAB caches
  613.  *
  614.  * @param argv Ignores
  615.  *
  616.  * @return Always 1
  617.  */
  618. int cmd_slabs(cmd_arg_t * argv) {
  619.     slab_print_list();
  620.     return 1;
  621. }
  622.  
  623.  
  624. /** Command for listings Thread information
  625.  *
  626.  * @param argv Ignores
  627.  *
  628.  * @return Always 1
  629.  */
  630. int cmd_threads(cmd_arg_t * argv) {
  631.     thread_print_list();
  632.     return 1;
  633. }
  634.  
  635. /** Command for listings Task information
  636.  *
  637.  * @param argv Ignores
  638.  *
  639.  * @return Always 1
  640.  */
  641. int cmd_tasks(cmd_arg_t * argv) {
  642.     task_print_list();
  643.     return 1;
  644. }
  645.  
  646. /** Command for listings Thread information
  647.  *
  648.  * @param argv Ignores
  649.  *
  650.  * @return Always 1
  651.  */
  652. int cmd_sched(cmd_arg_t * argv) {
  653.     sched_print_list();
  654.     return 1;
  655. }
  656.  
  657. /** Command for listing memory zones
  658.  *
  659.  * @param argv Ignored
  660.  *
  661.  * return Always 1
  662.  */
  663. int cmd_zones(cmd_arg_t * argv) {
  664.     zone_print_list();
  665.     return 1;
  666. }
  667.  
  668. /** Command for memory zone details
  669.  *
  670.  * @param argv Integer argument from cmdline expected
  671.  *
  672.  * return Always 1
  673.  */
  674. int cmd_zone(cmd_arg_t * argv) {
  675.     zone_print_one(argv[0].intval);
  676.     return 1;
  677. }
  678.  
  679. /** Command for listing processors.
  680.  *
  681.  * @param argv Ignored.
  682.  *
  683.  * return Always 1.
  684.  */
  685. int cmd_cpus(cmd_arg_t *argv)
  686. {
  687.     cpu_list();
  688.     return 1;
  689. }
  690.  
  691. /** Command for printing kernel version.
  692.  *
  693.  * @param argv Ignored.
  694.  *
  695.  * return Always 1.
  696.  */
  697. int cmd_version(cmd_arg_t *argv)
  698. {
  699.     version_print();
  700.     return 1;
  701. }
  702.