"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.19/src/mx/cmd-misc.c" (26 Apr 2020, 17483 Bytes) of package /linux/misc/s-nail-14.9.19.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "cmd-misc.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.18_vs_14.9.19.

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ Miscellaneous user commands, like `echo', `pwd', etc.
    3  *
    4  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
    5  * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
    6  * SPDX-License-Identifier: BSD-3-Clause
    7  */
    8 /*
    9  * Copyright (c) 1980, 1993
   10  *      The Regents of the University of California.  All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  * 1. Redistributions of source code must retain the above copyright
   16  *    notice, this list of conditions and the following disclaimer.
   17  * 2. Redistributions in binary form must reproduce the above copyright
   18  *    notice, this list of conditions and the following disclaimer in the
   19  *    documentation and/or other materials provided with the distribution.
   20  * 3. Neither the name of the University nor the names of its contributors
   21  *    may be used to endorse or promote products derived from this software
   22  *    without specific prior written permission.
   23  *
   24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
   25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
   28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   34  * SUCH DAMAGE.
   35  */
   36 #undef su_FILE
   37 #define su_FILE cmd_misc
   38 #define mx_SOURCE
   39 
   40 #ifndef mx_HAVE_AMALGAMATION
   41 # include "mx/nail.h"
   42 #endif
   43 
   44 #include <sys/utsname.h>
   45 
   46 #include <su/cs.h>
   47 #include <su/mem.h>
   48 #include <su/sort.h>
   49 
   50 #include "mx/child.h"
   51 #include "mx/file-streams.h"
   52 #include "mx/sigs.h"
   53 
   54 /* TODO fake */
   55 #include "su/code-in.h"
   56 
   57 /* Expand the shell escape by expanding unescaped !'s into the last issued
   58  * command where possible */
   59 static char const *a_cmisc_bangexp(char const *cp);
   60 
   61 /* c_n?echo(), c_n?echoerr() */
   62 static int a_cmisc_echo(void *vp, FILE *fp, boole donl);
   63 
   64 /* c_read(), c_readsh() */
   65 static int a_cmisc_read(void *vp, boole atifs);
   66 static boole a_cmisc_read_set(char const *cp, char const *value);
   67 
   68 /* c_version() */
   69 static su_sz a_cmisc_version_cmp(void const *s1, void const *s2);
   70 
   71 static char const *
   72 a_cmisc_bangexp(char const *cp){
   73    static struct str last_bang;
   74 
   75    struct n_string xbang, *bang;
   76    char c;
   77    boole changed;
   78    NYD_IN;
   79 
   80    if(!ok_blook(bang))
   81       goto jleave;
   82 
   83    changed = FAL0;
   84 
   85    for(bang = n_string_creat(&xbang); (c = *cp++) != '\0';){
   86       if(c == '!'){
   87          if(last_bang.l > 0)
   88             bang = n_string_push_buf(bang, last_bang.s, last_bang.l);
   89          changed = TRU1;
   90       }else{
   91          if(c == '\\' && *cp == '!'){
   92             ++cp;
   93             c = '!';
   94             changed = TRU1;
   95          }
   96          bang = n_string_push_c(bang, c);
   97       }
   98    }
   99 
  100    if(last_bang.s != NULL)
  101       n_free(last_bang.s);
  102    last_bang.s = n_string_cp(bang);
  103    last_bang.l = bang->s_len;
  104    bang = n_string_drop_ownership(bang);
  105    n_string_gut(bang);
  106 
  107    cp = last_bang.s;
  108    if(changed)
  109       fprintf(n_stdout, "!%s\n", cp);
  110 jleave:
  111    NYD_OU;
  112    return cp;
  113 }
  114 
  115 static int
  116 a_cmisc_echo(void *vp, FILE *fp, boole donl){
  117    struct n_string s_b, *s;
  118    int rv;
  119    boole doerr;
  120    char const **argv, *varname, **ap, *cp;
  121    NYD2_IN;
  122 
  123    argv = vp;
  124    varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
  125    s = n_string_reserve(n_string_creat_auto(&s_b), 121/* XXX */);
  126 #ifdef mx_HAVE_ERRORS
  127    doerr = (fp == n_stderr &&  (n_psonce & n_PSO_INTERACTIVE));
  128 #else
  129    doerr = FAL0;
  130 #endif
  131 
  132    for(ap = argv; *ap != NULL; ++ap){
  133       if(ap != argv)
  134          s = n_string_push_c(s, ' ');
  135       if((cp = fexpand(*ap, FEXP_NVAR)) == NIL)
  136          cp = *ap;
  137       s = n_string_push_cp(s, cp);
  138    }
  139    if(donl)
  140       s = n_string_push_c(s, '\n');
  141    cp = n_string_cp(s);
  142 
  143    if(varname == NULL){
  144       s32 e;
  145 
  146       e = su_ERR_NONE;
  147       if(doerr){
  148          /* xxx Ensure *log-prefix* will be placed by n_err() for next msg */
  149          if(donl)
  150             cp = n_string_cp(n_string_trunc(s, s->s_len - 1));
  151          n_errx(TRU1, (donl ? "%s\n" : "%s"), cp);
  152       }else if(fputs(cp, fp) == EOF)
  153          e = su_err_no();
  154       if((rv = (fflush(fp) == EOF)))
  155          e = su_err_no();
  156       rv |= ferror(fp) ? 1 : 0;
  157       n_pstate_err_no = e;
  158    }else if(!n_var_vset(varname, (up)cp)){
  159       n_pstate_err_no = su_ERR_NOTSUP;
  160       rv = -1;
  161    }else{
  162       n_pstate_err_no = su_ERR_NONE;
  163       rv = (int)s->s_len;
  164    }
  165    NYD2_OU;
  166    return rv;
  167 }
  168 
  169 static int
  170 a_cmisc_read(void * volatile vp, boole atifs){
  171    struct n_sigman sm;
  172    struct str trim;
  173    struct n_string s_b, *s;
  174    int rv;
  175    char const *ifs, **argv, *cp;
  176    char *linebuf;
  177    uz linesize, i;
  178    NYD2_IN;
  179 
  180    s = n_string_creat_auto(&s_b);
  181    s = n_string_reserve(s, 64 -1);
  182    mx_fs_linepool_aquire(&linebuf, &linesize);
  183 
  184    ifs = atifs ? ok_vlook(ifs) : NIL; /* (ifs has default value) */
  185    argv = vp;
  186 
  187    n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
  188    case 0:
  189       break;
  190    default:
  191       n_pstate_err_no = su_ERR_INTR;
  192       rv = -1;
  193       goto jleave;
  194    }
  195 
  196    n_pstate_err_no = su_ERR_NONE;
  197    rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
  198             ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
  199          n_GO_INPUT_FORCE_STDIN | n_GO_INPUT_NL_ESC |
  200          n_GO_INPUT_PROMPT_NONE /* XXX POSIX: PS2: yes! */),
  201          NIL, &linebuf, &linesize, NIL, NIL);
  202    if(rv < 0){
  203       if(!n_go_input_is_eof())
  204          n_pstate_err_no = su_ERR_BADF;
  205       goto jleave;
  206    }else if(rv == 0){
  207       if(n_go_input_is_eof()){
  208          rv = -1;
  209          goto jleave;
  210       }
  211    }else{
  212       trim.s = linebuf;
  213       trim.l = rv;
  214 
  215       for(; *argv != NIL; ++argv){
  216          if(trim.l == 0 || (atifs && n_str_trim_ifs(&trim, FAL0)->l == 0))
  217             break;
  218 
  219          /* The last variable gets the remaining line less trailing IFS-WS */
  220          if(atifs){
  221             if(argv[1] == NIL){
  222 jitall:
  223                s = n_string_assign_buf(s, trim.s, trim.l);
  224                trim.l = 0;
  225             }else for(cp = trim.s, i = 1;; ++cp, ++i){
  226                if(su_cs_find_c(ifs, *cp) != NIL){
  227                   s = n_string_assign_buf(s, trim.s, i - 1);
  228                   trim.s += i;
  229                   trim.l -= i;
  230                   break;
  231                }
  232 
  233                if(i == trim.l)
  234                   goto jitall;
  235             }
  236          }else{
  237             s = n_string_trunc(s, 0);
  238 jsh_redo:
  239             if(n_shexp_parse_token((n_SHEXP_PARSE_LOG |
  240                      n_SHEXP_PARSE_IFS_VAR | n_SHEXP_PARSE_TRIM_SPACE |
  241                      n_SHEXP_PARSE_TRIM_IFSSPACE), s, &trim, NIL
  242                   ) & n_SHEXP_STATE_STOP)
  243                trim.l = 0;
  244             else if(argv[1] == NIL)
  245                goto jsh_redo;
  246          }
  247 
  248          if(!a_cmisc_read_set(*argv, n_string_cp(s))){
  249             n_pstate_err_no = su_ERR_NOTSUP;
  250             rv = -1;
  251             break;
  252          }
  253       }
  254    }
  255 
  256    /* Set the remains to the empty string */
  257    for(; *argv != NIL; ++argv)
  258       if(!a_cmisc_read_set(*argv, su_empty)){
  259          n_pstate_err_no = su_ERR_NOTSUP;
  260          rv = -1;
  261          break;
  262       }
  263 
  264    n_sigman_cleanup_ping(&sm);
  265 jleave:
  266    mx_fs_linepool_release(linebuf, linesize);
  267 
  268    NYD2_OU;
  269    n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
  270    return rv;
  271 }
  272 
  273 static boole
  274 a_cmisc_read_set(char const *cp, char const *value){
  275    boole rv;
  276    NYD2_IN;
  277 
  278    if(!n_shexp_is_valid_varname(cp, FAL0))
  279       value = N_("not a valid variable name");
  280    else if(!n_var_is_user_writable(cp))
  281       value = N_("variable is read-only");
  282    else if(!n_var_vset(cp, (up)value))
  283       value = N_("failed to update variable value");
  284    else{
  285       rv = TRU1;
  286       goto jleave;
  287    }
  288 
  289    n_err("read: %s: %s\n", V_(value), n_shexp_quote_cp(cp, FAL0));
  290    rv = FAL0;
  291 
  292 jleave:
  293    NYD2_OU;
  294    return rv;
  295 }
  296 
  297 static su_sz
  298 a_cmisc_version_cmp(void const *s1, void const *s2){
  299    su_sz rv;
  300    char const *cp1, *cp2;
  301    NYD2_IN;
  302 
  303    cp1 = s1;
  304    cp2 = s2;
  305    rv = su_cs_cmp(&cp1[1], &cp2[1]);
  306 
  307    NYD2_OU;
  308    return rv;
  309 }
  310 
  311 FL int
  312 c_shell(void *v){
  313    struct mx_child_ctx cc;
  314    sigset_t mask;
  315    int rv;
  316    FILE *fp;
  317    char const **argv, *varname, *varres;
  318    NYD_IN;
  319 
  320    n_pstate_err_no = su_ERR_NONE;
  321    argv = v;
  322    varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NIL;
  323    varres = n_empty;
  324    fp = NIL;
  325 
  326    if(varname != NIL &&
  327          (fp = mx_fs_tmp_open("shell", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
  328                mx_FS_O_REGISTER), NIL)) == NIL){
  329       n_pstate_err_no = su_ERR_CANCELED;
  330       rv = -1;
  331    }else{
  332       sigemptyset(&mask);
  333       mx_child_ctx_setup(&cc);
  334       cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE;
  335       cc.cc_mask = &mask;
  336       if(fp != NIL)
  337          cc.cc_fds[mx_CHILD_FD_OUT] = fileno(fp);
  338       cc.cc_cmd = ok_vlook(SHELL);
  339       cc.cc_args[0] = "-c";
  340       cc.cc_args[1] = a_cmisc_bangexp(*argv);
  341 
  342       if(!mx_child_run(&cc) || (rv = cc.cc_exit_status) < 0){
  343          n_pstate_err_no = cc.cc_error;
  344          rv = -1;
  345       }
  346    }
  347 
  348    if(fp != NIL){
  349       if(rv != -1){
  350          int c;
  351          char *x;
  352          off_t l;
  353 
  354          fflush_rewind(fp);
  355          l = fsize(fp);
  356          if(UCMP(64, l, >=, UZ_MAX -42)){
  357             n_pstate_err_no = su_ERR_NOMEM;
  358             varres = n_empty;
  359          }else if(l > 0){
  360             varres = x = n_autorec_alloc(l +1);
  361 
  362             for(; l > 0 && (c = getc(fp)) != EOF; --l)
  363                *x++ = c;
  364             *x++ = '\0';
  365             if(l != 0){
  366                n_pstate_err_no = su_err_no();
  367                varres = n_empty; /* xxx hmmm */
  368             }
  369          }
  370       }
  371 
  372       mx_fs_close(fp);
  373    }
  374 
  375    if(varname != NIL){
  376       if(!n_var_vset(varname, R(up,varres))){
  377          n_pstate_err_no = su_ERR_NOTSUP;
  378          rv = -1;
  379       }
  380    }else if(rv >= 0 && (n_psonce & n_PSO_INTERACTIVE)){
  381       fprintf(n_stdout, "!\n");
  382       /* Line buffered fflush(n_stdout); */
  383    }
  384    NYD_OU;
  385    return rv;
  386 }
  387 
  388 FL int
  389 c_dosh(void *v){
  390    struct mx_child_ctx cc;
  391    int rv;
  392    NYD_IN;
  393    UNUSED(v);
  394 
  395    mx_child_ctx_setup(&cc);
  396    cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE;
  397    cc.cc_cmd = ok_vlook(SHELL);
  398 
  399    if(mx_child_run(&cc) && (rv = cc.cc_exit_status) >= 0){
  400       putc('\n', n_stdout);
  401       /* Line buffered fflush(n_stdout); */
  402       n_pstate_err_no = su_ERR_NONE;
  403    }else{
  404       n_pstate_err_no = cc.cc_error;
  405       rv = -1;
  406    }
  407 
  408    NYD_OU;
  409    return rv;
  410 }
  411 
  412 FL int
  413 c_cwd(void *v){
  414    struct n_string s_b, *s;
  415    uz l;
  416    char const *varname;
  417    NYD_IN;
  418 
  419    s = n_string_creat_auto(&s_b);
  420    varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *(char const**)v : NULL;
  421    l = PATH_MAX;
  422 
  423    for(;; l += PATH_MAX){
  424       s = n_string_resize(n_string_trunc(s, 0), l);
  425 
  426       if(getcwd(s->s_dat, s->s_len) == NULL){
  427          int e;
  428 
  429          e = su_err_no();
  430          if(e == su_ERR_RANGE)
  431             continue;
  432          n_perr(_("Failed to getcwd(3)"), e);
  433          v = NULL;
  434          break;
  435       }
  436 
  437       if(varname != NULL){
  438          if(!n_var_vset(varname, (up)s->s_dat))
  439             v = NULL;
  440       }else{
  441          l = su_cs_len(s->s_dat);
  442          s = n_string_trunc(s, l);
  443          if(fwrite(s->s_dat, 1, s->s_len, n_stdout) == s->s_len &&
  444                putc('\n', n_stdout) == EOF)
  445             v = NULL;
  446       }
  447       break;
  448    }
  449    NYD_OU;
  450    return (v == NULL);
  451 }
  452 
  453 FL int
  454 c_chdir(void *vp){
  455    char **arglist;
  456    char const *cp;
  457    NYD_IN;
  458 
  459    if(*(arglist = vp) == NIL)
  460       cp = ok_vlook(HOME);
  461    else if((cp = fexpand(*arglist, /*FEXP_NOPROTO |*/ FEXP_LOCAL_FILE |
  462          FEXP_NVAR)) == NIL)
  463       goto jleave;
  464 
  465    if(chdir(cp) == -1){
  466       n_perr(cp, 0);
  467       cp = NIL;
  468    }
  469 
  470 jleave:
  471    NYD_OU;
  472    return (cp == NIL ? n_EXIT_ERR : n_EXIT_OK);
  473 }
  474 
  475 FL int
  476 c_echo(void *v){
  477    int rv;
  478    NYD_IN;
  479 
  480    rv = a_cmisc_echo(v, n_stdout, TRU1);
  481 
  482    NYD_OU;
  483    return rv;
  484 }
  485 
  486 FL int
  487 c_echoerr(void *v){
  488    int rv;
  489    NYD_IN;
  490 
  491    rv = a_cmisc_echo(v, n_stderr, TRU1);
  492 
  493    NYD_OU;
  494    return rv;
  495 }
  496 
  497 FL int
  498 c_echon(void *v){
  499    int rv;
  500    NYD_IN;
  501 
  502    rv = a_cmisc_echo(v, n_stdout, FAL0);
  503 
  504    NYD_OU;
  505    return rv;
  506 }
  507 
  508 FL int
  509 c_echoerrn(void *v){
  510    int rv;
  511    NYD_IN;
  512 
  513    rv = a_cmisc_echo(v, n_stderr, FAL0);
  514 
  515    NYD_OU;
  516    return rv;
  517 }
  518 
  519 FL int
  520 c_read(void *vp){
  521    int rv;
  522    NYD2_IN;
  523 
  524    rv = a_cmisc_read(vp, TRU1);
  525 
  526    NYD2_OU;
  527    return rv;
  528 }
  529 
  530 FL int
  531 c_readsh(void *vp){
  532    int rv;
  533    NYD2_IN;
  534 
  535    rv = a_cmisc_read(vp, FAL0);
  536 
  537    NYD2_OU;
  538    return rv;
  539 }
  540 
  541 FL int
  542 c_readall(void * vp){ /* TODO 64-bit retval */
  543    struct n_sigman sm;
  544    struct n_string s_b, *s;
  545    char *linebuf;
  546    uz linesize;
  547    int rv;
  548    char const **argv;
  549    NYD2_IN;
  550 
  551    s = n_string_creat_auto(&s_b);
  552    s = n_string_reserve(s, 64 -1);
  553 
  554    linesize = 0;
  555    linebuf = NULL;
  556    argv = vp;
  557 
  558    n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
  559    case 0:
  560       break;
  561    default:
  562       n_pstate_err_no = su_ERR_INTR;
  563       rv = -1;
  564       goto jleave;
  565    }
  566 
  567    n_pstate_err_no = su_ERR_NONE;
  568 
  569    for(;;){
  570       rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE
  571                ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) |
  572             n_GO_INPUT_FORCE_STDIN | /*n_GO_INPUT_NL_ESC |*/
  573             n_GO_INPUT_PROMPT_NONE),
  574             NULL, &linebuf, &linesize, NULL, NULL);
  575       if(rv < 0){
  576          if(!n_go_input_is_eof()){
  577             n_pstate_err_no = su_ERR_BADF;
  578             goto jleave;
  579          }
  580          if(s->s_len == 0)
  581             goto jleave;
  582          break;
  583       }
  584 
  585       if(n_pstate & n_PS_READLINE_NL)
  586          linebuf[rv++] = '\n'; /* Replace NUL with it */
  587 
  588       if(UNLIKELY(rv == 0)){ /* xxx will not get*/
  589          if(n_go_input_is_eof()){
  590             if(s->s_len == 0){
  591                rv = -1;
  592                goto jleave;
  593             }
  594             break;
  595          }
  596       }else if(LIKELY(UCMP(32, S32_MAX - s->s_len, >, rv)))
  597          s = n_string_push_buf(s, linebuf, rv);
  598       else{
  599          n_pstate_err_no = su_ERR_OVERFLOW;
  600          rv = -1;
  601          goto jleave;
  602       }
  603    }
  604 
  605    if(!a_cmisc_read_set(argv[0], n_string_cp(s))){
  606       n_pstate_err_no = su_ERR_NOTSUP;
  607       rv = -1;
  608       goto jleave;
  609    }
  610    rv = s->s_len;
  611 
  612    n_sigman_cleanup_ping(&sm);
  613 jleave:
  614    if(linebuf != NIL)
  615       n_free(linebuf);
  616 
  617    NYD2_OU;
  618    n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
  619    return rv;
  620 }
  621 
  622 FL struct n_string *
  623 n_version(struct n_string *s){
  624    NYD_IN;
  625    s = n_string_push_cp(s, n_uagent);
  626    s = n_string_push_c(s, ' ');
  627    s = n_string_push_cp(s, ok_vlook(version));
  628    s = n_string_push_c(s, ',');
  629    s = n_string_push_c(s, ' ');
  630    s = n_string_push_cp(s, ok_vlook(version_date));
  631    s = n_string_push_c(s, ' ');
  632    s = n_string_push_c(s, '(');
  633    s = n_string_push_cp(s, _("built for "));
  634    s = n_string_push_cp(s, ok_vlook(build_os));
  635    s = n_string_push_c(s, ')');
  636    s = n_string_push_c(s, '\n');
  637    NYD_OU;
  638    return s;
  639 }
  640 
  641 FL int
  642 c_version(void *vp){
  643    struct utsname ut;
  644    struct n_string s_b, *s;
  645    int rv;
  646    char *iop;
  647    char const *cp, **arr;
  648    uz i, lnlen, j;
  649    NYD_IN;
  650 
  651    s = n_string_creat_auto(&s_b);
  652    s = n_string_book(s, 1024);
  653 
  654    /* First two lines */
  655    s = n_version(s);
  656    s = n_string_push_cp(s, _("Features included (+) or not (-):\n"));
  657 
  658    /* Some lines with the features.
  659     * *features* starts with dummy byte to avoid + -> *folder* expansions */
  660    i = su_cs_len(cp = &ok_vlook(features)[1]) +1;
  661    iop = n_autorec_alloc(i);
  662    su_mem_copy(iop, cp, i);
  663 
  664    arr = n_autorec_alloc(sizeof(cp) * VAL_FEATURES_CNT);
  665    for(i = 0; (cp = su_cs_sep_c(&iop, ',', TRU1)) != NULL; ++i)
  666       arr[i] = cp;
  667    su_sort_shell_vpp(su_S(void const**,arr), i, &a_cmisc_version_cmp);
  668 
  669    for(lnlen = 0; i-- > 0;){
  670       cp = *(arr++);
  671       j = su_cs_len(cp);
  672 
  673       if((lnlen += j + 1) > 72){
  674          s = n_string_push_c(s, '\n');
  675          lnlen = j + 1;
  676       }
  677       s = n_string_push_c(s, ' ');
  678       s = n_string_push_buf(s, cp, j);
  679    }
  680    s = n_string_push_c(s, '\n');
  681 
  682    /* */
  683    if(n_poption & n_PO_V){
  684       s = n_string_push_cp(s, "Compile: ");
  685       s = n_string_push_cp(s, ok_vlook(build_cc));
  686       s = n_string_push_cp(s, "\nLink: ");
  687       s = n_string_push_cp(s, ok_vlook(build_ld));
  688       if(*(cp = ok_vlook(build_rest)) != '\0'){
  689          s = n_string_push_cp(s, "\nRest: ");
  690          s = n_string_push_cp(s, cp);
  691       }
  692       s = n_string_push_c(s, '\n');
  693 
  694       /* A trailing line with info of the running machine */
  695       uname(&ut);
  696       s = n_string_push_c(s, '@');
  697       s = n_string_push_cp(s, ut.sysname);
  698       s = n_string_push_c(s, ' ');
  699       s = n_string_push_cp(s, ut.release);
  700       s = n_string_push_c(s, ' ');
  701       s = n_string_push_cp(s, ut.version);
  702       s = n_string_push_c(s, ' ');
  703       s = n_string_push_cp(s, ut.machine);
  704       s = n_string_push_c(s, '\n');
  705    }
  706 
  707    /* Done */
  708    cp = n_string_cp(s);
  709 
  710    if(n_pstate & n_PS_ARGMOD_VPUT){
  711       if(n_var_vset(*(char const**)vp, (up)cp))
  712          rv = 0;
  713       else
  714          rv = -1;
  715    }else{
  716       if(fputs(cp, n_stdout) != EOF)
  717          rv = 0;
  718       else{
  719          clearerr(n_stdout);
  720          rv = 1;
  721       }
  722    }
  723 
  724    NYD_OU;
  725    return rv;
  726 }
  727 
  728 #include "su/code-ou.h"
  729 /* s-it-mode */