summaryrefslogblamecommitdiffstats
path: root/main/common/edit.c
blob: aec8060a4e67c2516fcf7fea824bc24164b9ce12 (plain) (tree)
1
2
3
4


                                                                           
  


































                                                                           






                                                              

                                                           
                                   

                               





                            
 
                     



                             


                     



                                    

                             
                    
                                                                       
      














                                                                        




                                   

                                   
                       







                                                                        
      
      




                           






































































































































































                                                                               
                    




























































                                                                          
      






















































































                                                                             




                                        















                                                        




                              







                            




                         


                       




                          







                         


               




                                                                          



                                                      


















































                                        




                                                               




























































                                                                        

    




                                                   




                                     











                         




                                                                      








































                                              




                                                                




























                                                      




                   



                                          


          

                                                                         



                        




















                                                                      


      
/**************************************************************************
 *
 * Copyright (c) 2013 Alcatel-Lucent
 *
 * Alcatel Lucent licenses this file to You under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.  A copy of the License is contained the
 * file LICENSE at the top level of this repository.
 * You may also obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **************************************************************************
 *
 * edit.c:
 *
 * A simple buffer editor. It allows a user to pull a TFS file into RAM,
 * make some "ed-like" modifications to the ASCII data and then write
 * that data back to TFS.
 *
 * Original author:     Ed Sutter (ed.sutter@alcatel-lucent.com)
 *
 */
#include "config.h"
#if INCLUDE_EDIT
#include "stddefs.h"
#include "genlib.h"
#include "tfs.h"
#include "tfsprivate.h"
#include <ctype.h>
#include "cli.h"

extern char *file_line_edit(char *);
static int  rmCR(char *,int);
static int  gotoline(char *,char *,char **,char *,int);
static int  searchfor(char *,char *,char **,char *);
static int  whatlineisthis(char *,char *);
static int  getrange(char *,int *,int *,char *,char *,char *);
static void lnprbuf(char *,char *,char *);
static void prbuf(char *,char *);
static void prlno(int,int);
static void deletelines(char *,int,char *,char **,char **);
static char *prline(char *,char *);
static char *skipwhite(char *);

#define BREAK           1
#define CONTINUE        2
#define FALLTHROUGH     3
#define INSERT          4
#define COMMAND         5
#define ESCAPE          0x1b

#define ILCMAX      8

void
efree(int size, char *buffer)
{
    if(size != 0) {
        free(buffer);
    }
}

/* Help text while in the editor: */
char *edithelp[] = {
    "Editor commands:",
    "d{LRNG}    delete line",
#if INCLUDE_LINEEDIT
    "e#         edit line # (uses 'ksh/vi-like' command line editing)",
#endif
    "i          enter insert mode (use '.' to exit insert mode)",
    "a          enter append mode (use '.' to exit append mode)",
    "j#         join line '#' with line '#+1'",
    "P[LRNG]    print buffer with line numbers",
    "p          print buffer",
    "q[fname]   quit edit, write file",
    "s[srchstr] goto line containing search string",
    "x          exit edit, do not write file",
    "#          goto line # (use '$' to go to last line)",
    ".+/-#      goto line relative to current position",
    "",
    "Where...",
    "# represents a decimal number;",
    "LRNG represents a line number or inclusive line range (# or #-#);",
    0,
};

/* Help text outside the editor: */

char *EditHelp[] = {
    "Edit file or buffer",
    "-[b:c:f:i:m:rs:t] [filename]",
#if INCLUDE_VERBOSEHELP
    " -b {adr}  specify buffer base address",
    " -c {cmd}  in-line command",
    " -f {flgs} file flags (see tfs)",
    " -i {info} file info (see tfs)",
    " -m {size} specify allocation size for buffer",
    " -r        do not automatically remove carriage returns from file",
    " -s {size} size of buffer",
    " -t        convert tabs to spaces",
#endif
    0,
};

int
Edit(int argc,char *argv[])
{
    int     i, opt, tfd, quit, mode, bsize;
    char    ln[CMDLINESIZE], *lp, *cp, *cp1, *filename, *flags, *info;
    char    *buffer, *bp;   /* Buffer and pointer into buffer. */
    char    *eob;           /* Current EOF  pointer (max distance between */
    /* bp and buffer). */
    char    *inlinecmd[ILCMAX]; /* If non-NULL, it points to a list of */
    /* commands to be executed prior to going */
    /* into interactive mode. */
    int ilcidx;             /* Index into in-line command table. */
    int len;                /* Length of the incoming line. */
    int size;               /* Size of buffer specified on command line. */
    int cmdtot;             /* Running total of incoming lines. */
    int delcnt;             /* Used by :d when a range is specified. */
    int noCR;               /* Set by -r option to remove Carriage Returns */
    TFILE   tstruct, *tfp;

    tfp = (TFILE *)0;
    size = bsize = 0;
    flags = filename = (char *)0;
    buffer = (char *)getAppRamStart();
    info = (char *)0;
    noCR = 1;
    ilcidx = 0;
    for(i=0; i<8; i++) {
        inlinecmd[i] = (char *)0;
    }

    while((opt = getopt(argc, argv, "b:c:f:i:m:rs:")) != -1) {
        switch(opt) {
        case 'b':
            buffer = (char *)strtol(optarg,(char **)0,0);
            break;
        case 'c':
            if(ilcidx < ILCMAX-1) {
                inlinecmd[ilcidx++] = optarg;
            }
            break;
        case 'f':
            flags = optarg;
            break;
        case 'i':
            info = optarg;
            break;
        case 'm':
            bsize = strtol(optarg,0,0);
            break;
        case 'r':
            noCR = 0;
            break;
        case 's':
            size = (int)strtol(optarg,(char **)0,0);
            break;
        default:
            return (CMD_PARAM_ERROR);
        }
    }

    if(bsize != 0) {
        buffer = malloc(bsize);
        if(!buffer) {
            return(CMD_FAILURE);
        }
    }

    if(argc == optind+1) {
        filename = argv[optind];
        if(tfsfstat(filename,&tstruct) != -1) {
            tfp = &tstruct;
            size = TFS_SIZE(tfp);
            if((bsize > 0) && (size > bsize)) {
                printf("Allocation too small\n");
                efree(bsize,buffer);
                return(CMD_FAILURE);
            }
            tfd = tfsopen(filename,TFS_RDONLY,0);
            if(tfd < 0) {
                printf("%s: %s\n",filename,(char *)tfsctrl(TFS_ERRMSG,tfd,0));
                efree(bsize,buffer);
                return(CMD_FAILURE);
            } else {
                tfsread(tfd,buffer,size);
                tfsclose(tfd,0);
            }
        }
    }

    if((noCR) && (size != 0)) {
        if((buffer[size-1] != 0x0d) && (buffer[size-1] != 0x0a)) {
            printf("EOF linefeed inserted.\n");
            buffer[size++] =  0x0a;
            buffer[size] = 0;
        }
        size -= rmCR(buffer,size);
    }

    bp = buffer;
    eob = buffer + size;

    if(!ilcidx) {
        printf("Edit buffer = 0x%lx.\nType '?' for help\n",(ulong)buffer);
    }

    quit = 0;
    cmdtot = 0;
    mode = COMMAND;
    while(!quit) {
        lp = ln;
        if(ilcidx > cmdtot) {
            strcpy(ln,inlinecmd[cmdtot]);
        } else if(ilcidx) {     /* If ILC command set doesn't terminate */
            /* the edit session, force it here. */
            efree(bsize,buffer);
            return(CMD_SUCCESS);
        } else {
            getline(ln,CMDLINESIZE,0);
        }
        cmdtot++;
        if(mode == INSERT) {
            if(!strcmp(lp,".")) {
                mode = COMMAND;
            } else {
                len = strlen(lp) + 1;
                cp1 = eob + len;
                cp = eob;
                while(cp > bp) {
                    *--cp1 = *--cp;
                }
                while(*lp) {
                    *bp++ = *lp++;
                }
                *bp++ = '\n';
                eob += len;
            }
            continue;
        }
        if(!strcmp(ln,"x")) {               /* Exit editor now. */
            efree(bsize,buffer);
            return(CMD_SUCCESS);
        } else if(!strcmp(ln,"?")) {        /* Display help info. */
            char    **eh;

            eh = edithelp;
            while(*eh) {
                printf("%s\n",*eh++);
            }
        } else if(!strcmp(ln,"p")) {        /* Print buffer. */
            prbuf(buffer,eob);
        } else if(!strcmp(ln,"P")) {        /* Print buffer with lines. */
            lnprbuf(buffer,bp,eob);
        } else if(!strcmp(ln,"i")) {        /* Enter insert mode. */
            mode = INSERT;
        } else if(!strcmp(ln,"a")) {        /* Enter insert mode at */
            mode = INSERT;                  /* next line */
            gotoline(".+1",buffer,&bp,eob,0);
        } else if(!strcmp(ln,"$")) {        /* Goto last line. */
            gotoline(ln,buffer,&bp,eob,0);
            gotoline(".-1",buffer,&bp,eob,1);
        } else if(isdigit(ln[0]) || (ln[0]=='.')) { /* Goto line. */
            gotoline(ln,buffer,&bp,eob,1);
        } else if(ln[0] == 's') {           /* Go to line containing string. */
            char *str;
            str = skipwhite(&ln[1]);
            if(!*str) {
                gotoline(".+1",buffer,&bp,eob,0);
            }
            searchfor(str,buffer,&bp,eob);
        }
#if INCLUDE_LINEEDIT
        else if(ln[0] == 'e') {
            char    *copy, *eoc, *cp2, crlf[2];
            int     oldlnsize, newlnsize, toendsize;

            if(gotoline(&ln[1],buffer,&bp,eob,0) == -1) {
                continue;
            }
            copy = malloc(CMDLINESIZE);
            if(!copy) {
                continue;
            }
            eoc = copy + CMDLINESIZE - 3;
            cp = bp;
            cp1 = copy;
            oldlnsize = 0;
            while((*cp != 0x0a) && (*cp != 0x0d) && (cp1 != eoc)) {
                if(*cp == '\t') {   /* Tabs are replaced with spaces to */
                    *cp = ' ';    /* eliminate whitespace confusion in */
                }
                putchar(*cp);       /* the line editor. */
                *cp1++ = *cp++;
                oldlnsize++;
            }
            crlf[0] = *cp;
            if(*cp != *(cp+1)) {
                crlf[1] = *(cp+1);
                if(*(cp+1) == 0x0a || *(cp+1) == 0x0d) {
                    oldlnsize++;
                }
            } else {
                crlf[1] = 0;
            }
            oldlnsize++;
            *cp1++ = ESCAPE;
            *cp1 = 0;
            if(file_line_edit(copy) != (char *)0) {
                newlnsize = strlen(copy);
                toendsize = eob - (bp+oldlnsize);
                copy[newlnsize++] = crlf[0];
                if(crlf[1] == 0x0d || crlf[1] == 0x0a) {
                    copy[newlnsize++] = crlf[1];
                }
                copy[newlnsize] = 0;
                if(oldlnsize == newlnsize) {
                    memcpy(bp,copy,newlnsize);
                } else if(oldlnsize > newlnsize) {
                    strcpy(bp,copy);
                    memcpy(bp+newlnsize,cp+1,toendsize);
                } else {
                    cp1 = eob-1;
                    cp2 = (char *)(eob+(newlnsize-oldlnsize)-1);
                    for(i=0; i<toendsize; i++) {
                        *cp2-- = *cp1--;
                    }
                    memcpy(bp,copy,newlnsize);
                }
                eob -= oldlnsize;
                eob += newlnsize;
            }
            free(copy);
        }
#endif
        else if(ln[0] == 'P') {
            int range, first, last, currentline;

            range = getrange(&ln[1],&first,&last,buffer,bp,eob);
            if(range > 0) {
                char *bpcpy;

                bpcpy = bp;
                gotoline(".",buffer,&bpcpy,eob,0);
                currentline = whatlineisthis(buffer,bpcpy);
                if(gotoline(&ln[1],buffer,&bpcpy,eob,0) == -1) {
                    continue;
                }
                for(i=first; i<=last; i++) {
                    prlno(i,currentline);
                    bpcpy = prline(bpcpy,eob);
                }
            }
        } else if(ln[0] == 'j') {   /* Join line specified with next line */
            char *tmpeob;

            if(gotoline(&ln[1],buffer,&bp,eob,0) == -1) {
                continue;
            }
            tmpeob=eob-1;
            while(bp < tmpeob) {
                if(*bp ==  '\n') {
                    memcpy(bp,bp+1,eob-bp);
                    eob--;
                    break;
                }
                bp++;
            }
        } else if(ln[0] == 'd') {   /* Delete line (or range of lines) */
            delcnt = getrange(&ln[1],0,0,buffer,bp,eob);
            deletelines(&ln[1],delcnt,buffer,&bp,&eob);
        } else if(ln[0] == 'q') {   /* Quit edit, if filename is present */
            lp = &ln[1];            /* use it. */
            lp = skipwhite(lp);
            if(*lp) {
                filename = lp;
            }
            quit = 1;
        } else {
            printf("huh?\n");
        }
    }

    if(filename) {
        int err;
        char    buf[16];

        if(tfp) {
            int link;

            /* If filename and TFS_NAME(tfp) differ, then we are editing
             * a file through a symbolic link.  If it is a link, then we
             * use the info/flags/filename from the source file (using the
             * file pointer and ignoring user input stuff).
             */
            link = strcmp(filename,TFS_NAME(tfp));

            if((!flags) || (link)) {
                flags = (char *)tfsctrl(TFS_FBTOA,TFS_FLAGS(tfp),(long)buf);
            }
            if((!info) || (link)) {
                info = TFS_INFO(tfp);
            }
            if(link) {
                printf("Updating source file '%s' thru link '%s'\n",
                       TFS_NAME(tfp),filename);
                filename = TFS_NAME(tfp);
            }
        }
        if(!flags) {
            flags = (char *)0;
        }
        if(!info) {
            info = (char *)0;
        }
        err = tfsadd(filename,info,flags,(unsigned char *)buffer,eob-buffer);
        if(err != TFS_OKAY) {
            printf("%s: %s\n",filename,(char *)tfsctrl(TFS_ERRMSG,err,0));
        }
    }
    efree(bsize,buffer);
    return(CMD_SUCCESS);
}

static void
lnprbuf(char *buffer,char *bp,char *eob)
{
    int lno, currentline;

    if(buffer == eob) {
        return;
    }

    lno = 1;
    currentline = whatlineisthis(buffer,bp);
    prlno(lno++,currentline);
    while(buffer < eob) {
        putchar(*buffer);
        if((*buffer == '\n') && ((buffer + 1) != eob)) {
            prlno(lno++,currentline);
        }
        buffer++;
    }
}

static void
prlno(int lno,int currentline)
{
    char    *fmt;

    if(lno == currentline) {
        fmt = ">%2d: ";
    } else {
        fmt = "%3d: ";
    }
    printf(fmt,lno);
}

static void
prbuf(char *bp,char *eob)
{
    while(bp < eob) {
        putchar(*bp++);
    }
}

char *
prline(char *bp,char *eob)
{
    while(bp < eob) {
        putchar(*bp);
        if(*bp == '\n') {
            break;
        }
        bp++;
    }
    return(bp+1);
}

/* searchfor():
 *  Step through the buffer starting at 'bp' and search for a memory match
 *  with the incoming string pointed to by 'srch'.  If not found, simply
 *  return after the entire buffer has been searched (wrap to start if
 *  necessary).  If found, then adjust '*bp' to point to the beginning of
 *  of the line that contains the match.
 */
static int
searchfor(char *srch,char *buffer,char **bp,char *eob)
{
    static  char *lastsrchstring;
    char    *startedhere, *tbp;
    int len;

    tbp = *bp;
    if(tbp < eob) {
        startedhere = *bp;
    } else {
        tbp = startedhere = buffer;
    }

    if(*srch) {
        len = strlen(srch);
        if(lastsrchstring) {
            free(lastsrchstring);
        }
        lastsrchstring = malloc(len+1);
        if(lastsrchstring) {
            strcpy(lastsrchstring,srch);
        }
    } else if(lastsrchstring) {
        len = strlen(lastsrchstring);
        srch = lastsrchstring;
    } else {
        return(-1);
    }
    do {
        if((tbp + len) > eob) {
            tbp = buffer;
        } else {
            if(!memcmp(tbp,srch,len)) {
                while(1) {
                    if(tbp <= buffer) {
                        *bp = buffer;
                        break;
                    }
                    if(*tbp == '\n') {
                        *bp = tbp+1;
                        break;
                    }
                    tbp--;
                }
                prline(*bp,eob);
                return(0);
            } else {
                tbp++;
            }
        }
    } while(tbp != startedhere);
    printf("'%s' not found\n",srch);
    return(-1);
}

static int
gotoline(char *ln,char *buffer,char **bp,char *eob,int verbose)
{
    int     lno, i, moveforward;
    char    *tbp, *base;

    base = buffer;
    moveforward = 1;

    /* If destination line number is '.', assume you're already there.
     * If the '.' is followed by a '+' or '-' then the following number
     * is considered an offset from the current line instead of from the
     * start of the buffer.
     */
    if(ln[0] == '.') {
        base = *bp;
        if(ln[1] == '+') {
            lno = atoi(&ln[2]) + 1;
        } else if(ln[1] == '-') {
            lno = atoi(&ln[2]) + 2;
            moveforward = 0;
        } else {
            goto end;
        }
    } else if(ln[0] == '$') {
        lno = 99999999;
    } else {
        lno = atoi(ln);
    }

    if(lno < 1) {
        printf("Invalid line\n");
        return(-1);
    }

    if(moveforward) {
        for(tbp=base,i=1; i<lno&&tbp<eob; tbp++)
            if(*tbp == '\n') {
                i++;
            }
        if(tbp == eob) {
            if(verbose) {
                printf("Pointer set to end of buffer.\n");
            }
            /* If out of range, set pointer to end of buffer. */
            *bp = eob;
            return(-1);
        }
    } else {
        for(tbp=base,i=1; i<lno&&tbp>=buffer; tbp--)
            if(*tbp == '\n') {
                i++;
            }
        if(tbp < buffer) {
            if(verbose) {
                printf("Pointer set to start of buffer.\n");
            }
            /* If out of range, set pointer to beginning of buffer. */
            *bp = buffer;
            return(-1);
        }
        tbp+=2;
    }
    *bp = tbp;

end:
    if(verbose) {
        printf("%3d: ",whatlineisthis(buffer,*bp));
        prline(*bp,eob);
    }
    return(0);
}

static int
whatlineisthis(char *buffer,char *bp)
{
    int     lno;
    char    *cp;
    cp = buffer;

    lno = 1;
    while(cp < bp) {
        if(*cp == '\n') {
            lno++;
        }
        cp++;
    }
    return(lno);
}

static int
getrange(char *cp,int *begin,int *end,char *buffer,char *bp,char *eob)
{
    char    *dash;
    int     b, e, lastline, thisline;

    if(!*cp) {
        return(0);
    }

    lastline = whatlineisthis(buffer,eob) - 1;
    thisline = whatlineisthis(buffer,bp);

    if(*cp == '.') {
        b = thisline;
    } else {
        b = atoi(cp);
    }
    dash = strchr(cp,'-');
    if(dash) {
        if(*(dash+1) == '$') {
            e = lastline;
        } else if(*(dash+1) == '.') {
            e = thisline;
        } else {
            e = atoi(dash+1);
        }
    } else {
        e = b;
    }

    if(e > lastline) {
        e = lastline;
    }
    if(begin) {
        *begin = b;
    }
    if(end) {
        *end = e;
    }
    if((e <= 0) || (b <=0) || (e < b)) {
        printf("Bad range\n");
    }
    return(e - b + 1);
}

static void
deletelines(char *lp,int lcnt,char *buffer,char **bp,char **eob)
{
    char    *cp, *cp1, *t_bp, *t_eob;

    if(lcnt <= 0) {
        return;
    }

    t_bp = *bp;
    t_eob = *eob;

    if(gotoline(lp,buffer,&t_bp,t_eob,0) == -1) {
        return;
    }
    cp = cp1 = t_bp;
    while(lcnt>0) {
        if(cp >= t_eob) {
            printf("Pointer set to end of buffer.\n");
            break;
        }
        if(*cp == '\n') {
            lcnt--;
        }
        cp++;
    }
    while(cp != t_eob) {
        *cp1++ = *cp++;
    }

    *eob = cp1;
    *bp = t_bp;
}

static char *
skipwhite(char *cp)
{
    while((*cp == ' ') || (*cp == '\t')) {
        cp++;
    }
    return(cp);
}

/* rmCR():
 *  Given the source and size of the buffer, remove all instances of 0x0d
 *  (carriage return) from the buffer.  Return the number of CRs removed.
*/
static int
rmCR(char *src,int size)
{
    int i;              /* Index into src array. */
    int tot;            /* Keep track of total # of 0x0d's removed. */
    int remaining;      /* Keep track of how far to go. */

    tot = 0;
    remaining = size;
    for(i=0; i<size; i++) {
        remaining--;
        if(src[i] == 0x0d) {
            src[i] = 0;         /* Make sure memory is writeable. */
            if(src[i] != 0) {
                continue;
            }
            memcpy(&src[i],&src[i+1],remaining);
            tot++;
        }
    }
    if(tot) {
        printf("Removed %d CRs\n",tot);
    }
    return(tot);
}

#endif