root/usr.bin/mg/cscope.c
/*      $OpenBSD: cscope.c,v 1.22 2023/03/08 04:43:11 guenther Exp $    */

/*
 * This file is in the public domain.
 *
 * Author: Sunil Nimmagadda <sunil@openbsd.org>
 */

#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "def.h"

#define CSSYMBOL      0
#define CSDEFINITION  1
#define CSCALLEDFUNCS 2
#define CSCALLERFUNCS 3
#define CSTEXT        4
#define CSEGREP       6
#define CSFINDFILE    7
#define CSINCLUDES    8

struct cstokens {
        const char *fname;
        const char *function;
        const char *lineno;
        const char *pattern;
};

struct csmatch {
        TAILQ_ENTRY(csmatch) entry;
        int lineno;
};

struct csrecord {
        TAILQ_ENTRY(csrecord) entry;
        char *filename;
        TAILQ_HEAD(matches, csmatch) matches;
};

static TAILQ_HEAD(csrecords, csrecord) csrecords = TAILQ_HEAD_INITIALIZER(csrecords);
static struct csrecord *addentryr;
static struct csrecord *currecord;
static struct csmatch  *curmatch;
static const char      *addentryfn;
static const char      *csprompt[] = {
        "Find this symbol: ",
        "Find this global definition: ",
        "Find functions called by this function: ",
        "Find functions calling this function: ",
        "Find this text string: ",
        "Change this text string: ",
        "Find this egrep pattern: ",
        "Find this file: ",
        "Find files #including this file: "
};

static int  addentry(struct buffer *, char *);
static void csflush(void);
static int  do_cscope(int);
static int  csexists(const char *);
static int  getattr(char *, struct cstokens *);
static int  jumptomatch(void);
static void prettyprint(struct buffer *, struct cstokens *);
static const char *ltrim(const char *);

/*
 * Find this symbol. Bound to C-c s s
 */
int
cssymbol(int f, int n)
{
        return (do_cscope(CSSYMBOL));
}

/*
 * Find this global definition. Bound to C-c s d
 */
int
csdefinition(int f, int n)
{
        return (do_cscope(CSDEFINITION));
}

/*
 * Find functions called by this function. Bound to C-c s l
 */
int
csfuncalled(int f, int n)
{
        return (do_cscope(CSCALLEDFUNCS));
}

/*
 * Find functions calling this function. Bound to C-c s c
 */
int
cscallerfuncs(int f, int n)
{
        return (do_cscope(CSCALLERFUNCS));
}

/*
 * Find this text. Bound to C-c s t
 */
int
csfindtext(int f, int n)
{
        return (do_cscope(CSTEXT));
}

/*
 * Find this egrep pattern. Bound to C-c s e
 */
int
csegrep(int f, int n)
{
        return (do_cscope(CSEGREP));
}

/*
 * Find this file. Bound to C-c s f
 */
int
csfindfile(int f, int n)
{
        return (do_cscope(CSFINDFILE));
}

/*
 * Find files #including this file. Bound to C-c s i
 */
int
csfindinc(int f, int n)
{
        return (do_cscope(CSINCLUDES));
}

/*
 * Create list of files to index in the given directory
 * using cscope-indexer.
 */
int
cscreatelist(int f, int n)
{
        struct buffer *bp;
        struct stat sb;
        FILE *fpipe;
        char dir[NFILEN], cmd[BUFSIZ], title[BUFSIZ], *line, *bufp;
        size_t sz;
        ssize_t len;
        int clen;

        line = NULL;
        sz = 0;

        if (getbufcwd(dir, sizeof(dir)) == FALSE)
                dir[0] = '\0';

        bufp = eread("Index files in directory: ", dir,
            sizeof(dir), EFCR | EFDEF | EFNEW | EFNUL);

        if (bufp == NULL)
                return (ABORT);
        else if (bufp[0] == '\0')
                return (FALSE);

        if (stat(dir, &sb) == -1)
                return(dobeep_msgs("stat:", strerror(errno)));
        else if (S_ISDIR(sb.st_mode) == 0)
                return(dobeep_msgs(dir, "Not a directory"));

        if (csexists("cscope-indexer") == FALSE)
                return(dobeep_msg("no such file or directory, cscope-indexer"));

        clen = snprintf(cmd, sizeof(cmd), "cscope-indexer -v %s", dir);
        if (clen < 0 || clen >= sizeof(cmd))
                return (FALSE);

        if ((fpipe = popen(cmd, "r")) == NULL)
                return(dobeep_msg("problem opening pipe"));

        bp = bfind("*cscope*", TRUE);
        if (bclear(bp) != TRUE) {
                pclose(fpipe);
                return (FALSE);
        }
        bp->b_flag |= BFREADONLY;

        clen = snprintf(title, sizeof(title), "%s%s",
            "Creating cscope file list 'cscope.files' in: ", dir);
        if (clen < 0 || clen >= sizeof(title)) {
                pclose(fpipe);
                return (FALSE);
        }
        addline(bp, title);
        addline(bp, "");
        while ((len = getline(&line, &sz, fpipe)) != -1) {
                if (line[len - 1] == *bp->b_nlchr)
                        line[len - 1] = '\0';
                addline(bp, line);
        }
        free(line);
        if (ferror(fpipe))
                ewprintf("Problem reading pipe");
        pclose(fpipe);
        return (popbuftop(bp, WNONE));
}

/*
 * Next Symbol. Bound to C-c s n
 */
int
csnextmatch(int f, int n)
{
        struct csrecord *r;
        struct csmatch *m;

        if (curmatch == NULL) {
                if ((r = TAILQ_FIRST(&csrecords)) == NULL)
                        return(dobeep_msg("The *cscope* buffer does "
                            "not exist yet"));

                currecord = r;
                curmatch = TAILQ_FIRST(&r->matches);
        } else {
                m = TAILQ_NEXT(curmatch, entry);
                if (m == NULL) {
                        r = TAILQ_NEXT(currecord, entry);
                        if (r == NULL) {
                                return(dobeep_msg("The end of *cscope* buffer "
                                    "has been reached"));
                        } else {
                                currecord = r;
                                curmatch = TAILQ_FIRST(&currecord->matches);
                        }
                } else
                        curmatch = m;
        }
        return (jumptomatch());
}

/*
 * Previous Symbol. Bound to C-c s p
 */
int
csprevmatch(int f, int n)
{
        struct csmatch *m;
        struct csrecord *r;

        if (curmatch == NULL)
                return (FALSE);
        else {
                m  = TAILQ_PREV(curmatch, matches, entry);
                if (m)
                        curmatch = m;
                else {
                        r = TAILQ_PREV(currecord, csrecords, entry);
                        if (r == NULL) {
                                return(dobeep_msg("The beginning of *cscope* "
                                    "buffer has been reached"));
                        } else {
                                currecord = r;
                                curmatch = TAILQ_LAST(&currecord->matches,
                                    matches);
                        }
                }
        }
        return (jumptomatch());
}

/*
 * Next file.
 */
int
csnextfile(int f, int n)
{
        struct csrecord *r;

        if (curmatch == NULL) {
                if ((r = TAILQ_FIRST(&csrecords)) == NULL)
                        return(dobeep_msg("The *cscope* buffer does not "
                            "exist yet"));
        } else {
                if ((r = TAILQ_NEXT(currecord, entry)) == NULL)
                        return(dobeep_msg("The end of *cscope* buffer has "
                            "been reached"));
        }
        currecord = r;
        curmatch = TAILQ_FIRST(&currecord->matches);
        return (jumptomatch());
}

/*
 * Previous file.
 */
int
csprevfile(int f, int n)
{
        struct csrecord *r;

        if (curmatch == NULL) {
                if ((r = TAILQ_FIRST(&csrecords)) == NULL)
                        return(dobeep_msg("The *cscope* buffer does not"
                            "exist yet"));
        } else {
                if ((r = TAILQ_PREV(currecord, csrecords, entry)) == NULL)
                        return(dobeep_msg("The beginning of *cscope* buffer "
                            "has been reached"));
        }
        currecord = r;
        curmatch = TAILQ_FIRST(&currecord->matches);
        return (jumptomatch());
}

/*
 * The current symbol location is extracted from currecord->filename and
 * curmatch->lineno. Load the file similar to filevisit and goto the
 * lineno recorded.
 */
int
jumptomatch(void)
{
        struct buffer *bp;
        char *adjf;

        if (curmatch == NULL || currecord == NULL)
                return (FALSE);
        adjf = adjustname(currecord->filename, TRUE);
        if (adjf == NULL)
                return (FALSE);
        if ((bp = findbuffer(adjf)) == NULL)
                return (FALSE);
        curbp = bp;
        if (showbuffer(bp, curwp, WFFULL) != TRUE)
                return (FALSE);
        if (bp->b_fname[0] == '\0') {
                if (readin(adjf) != TRUE)
                        killbuffer(bp);
        }
        gotoline(FFARG, curmatch->lineno);
        return (TRUE);
}

/*
 * Ask for the symbol, construct cscope commandline with the symbol
 * and passed in index. Popen cscope, read the output into *cscope*
 * buffer and pop it.
 */
int
do_cscope(int i)
{
        struct buffer *bp;
        FILE *fpipe;
        char pattern[MAX_TOKEN], cmd[BUFSIZ], title[BUFSIZ];
        char *p, *buf;
        int clen, nores = 0;
        size_t sz;
        ssize_t len;

        buf = NULL;
        sz = 0;

        /* If current buffer isn't a source file just return */
        if (fnmatch("*.[chy]", curbp->b_fname, 0) != 0)
                return(dobeep_msg("C-c s not defined"));

        if (curtoken(0, 1, pattern) == FALSE)
                return (FALSE);
        p = eread("%s", pattern, MAX_TOKEN, EFNEW | EFCR | EFDEF, csprompt[i]);
        if (p == NULL)
                return (ABORT);
        else if (p[0] == '\0')
                return (FALSE);

        if (csexists("cscope") == FALSE)
                return(dobeep_msg("no such file or directory, cscope"));

        csflush();
        clen = snprintf(cmd, sizeof(cmd), "cscope -L -%d %s 2>/dev/null",
            i, pattern);
        if (clen < 0 || clen >= sizeof(cmd))
                return (FALSE);

        if ((fpipe = popen(cmd, "r")) == NULL)
                return(dobeep_msg("problem opening pipe"));

        bp = bfind("*cscope*", TRUE);
        if (bclear(bp) != TRUE) {
                pclose(fpipe);
                return (FALSE);
        }
        bp->b_flag |= BFREADONLY;

        clen = snprintf(title, sizeof(title), "%s%s", csprompt[i], pattern);
        if (clen < 0 || clen >= sizeof(title)) {
                pclose(fpipe);
                return (FALSE);
        }
        addline(bp, title);
        addline(bp, "");
        addline(bp, "-------------------------------------------------------------------------------");
        while ((len = getline(&buf, &sz, fpipe)) != -1) {
                if (buf[len - 1] == *bp->b_nlchr)
                        buf[len - 1] = '\0';
                if (addentry(bp, buf) != TRUE) {
                        free(buf);
                        return (FALSE);
                }
                nores = 1;
        }
        free(buf);
        if (ferror(fpipe))
                ewprintf("Problem reading pipe");
        pclose(fpipe);
        addline(bp, "-------------------------------------------------------------------------------");
        if (nores == 0)
                ewprintf("No matches were found.");
        return (popbuftop(bp, WNONE));
}

/*
 * For each line read from cscope output, extract the tokens,
 * add them to list and pretty print a line in *cscope* buffer.
 */
int
addentry(struct buffer *bp, char *csline)
{
        struct csrecord *r;
        struct csmatch *m;
        struct cstokens t;
        int lineno;
        char buf[BUFSIZ];
        const char *errstr;

        r = NULL;
        if (getattr(csline, &t) == FALSE)
                return (FALSE);

        lineno = strtonum(t.lineno, INT_MIN, INT_MAX, &errstr);
        if (errstr)
                return (FALSE);

        if (addentryfn == NULL || strcmp(addentryfn, t.fname) != 0) {
                if ((r = malloc(sizeof(struct csrecord))) == NULL)
                        return (FALSE);
                addentryr = r;
                if ((r->filename = strndup(t.fname, NFILEN)) == NULL)
                        goto cleanup;
                addentryfn = r->filename;
                TAILQ_INIT(&r->matches);
                if ((m = malloc(sizeof(struct csmatch))) == NULL)
                        goto cleanup;
                m->lineno = lineno;
                TAILQ_INSERT_TAIL(&r->matches, m, entry);
                TAILQ_INSERT_TAIL(&csrecords, r, entry);
                addline(bp, "");
                if (snprintf(buf, sizeof(buf), "*** %s", t.fname) < 0)
                        goto cleanup;
                addline(bp, buf);
        } else {
                if ((m = malloc(sizeof(struct csmatch))) == NULL)
                        goto cleanup;
                m->lineno = lineno;
                TAILQ_INSERT_TAIL(&addentryr->matches, m, entry);
        }
        prettyprint(bp, &t);
        return (TRUE);
cleanup:
        free(r);
        return (FALSE);
}

/*
 * Cscope line: <filename> <function> <lineno> <pattern>
 */
int
getattr(char *line, struct cstokens *t)
{
        char *p;

        if ((p = strchr(line, ' ')) == NULL)
                return (FALSE);
        *p++ = '\0';
        t->fname = line;
        line = p;

        if ((p = strchr(line, ' ')) == NULL)
                return (FALSE);
        *p++ = '\0';
        t->function = line;
        line = p;

        if ((p = strchr(line, ' ')) == NULL)
                return (FALSE);
        *p++ = '\0';
        t->lineno = line;

        if (*p == '\0')
                return (FALSE);
        t->pattern = p;

        return (TRUE);
}

void
prettyprint(struct buffer *bp, struct cstokens *t)
{
        char buf[BUFSIZ];

        if (snprintf(buf, sizeof(buf), "%s[%s]\t\t%s",
            t->function, t->lineno, ltrim(t->pattern)) < 0)
                return;
        addline(bp, buf);
}

const char *
ltrim(const char *s)
{
        while (isblank((unsigned char)*s))
                s++;
        return s;
}

void
csflush(void)
{
        struct csrecord *r;
        struct csmatch *m;

        while ((r = TAILQ_FIRST(&csrecords)) != NULL) {
                free(r->filename);
                while ((m = TAILQ_FIRST(&r->matches)) != NULL) {
                        TAILQ_REMOVE(&r->matches, m, entry);
                        free(m);
                }
                TAILQ_REMOVE(&csrecords, r, entry);
                free(r);
        }
        addentryr = NULL;
        addentryfn = NULL;
        currecord = NULL;
        curmatch = NULL;
}

/*
 * Check if the cmd exists in $PATH. Split on ":" and iterate through
 * all paths in $PATH.
 */
int
csexists(const char *cmd)
{
        char fname[NFILEN], *dir, *path, *pathc, *tmp;
        int  len, dlen;

        /* Special case if prog contains '/' */
        if (strchr(cmd, '/')) {
                if (access(cmd, F_OK) == -1)
                        return (FALSE);
                else
                        return (TRUE);
        }
        if ((tmp = getenv("PATH")) == NULL)
                return (FALSE);
        if ((pathc = path = strndup(tmp, NFILEN)) == NULL)
                return(dobeep_msg("out of memory"));

        while ((dir = strsep(&path, ":")) != NULL) {
                if (*dir == '\0')
                        continue;

                dlen = strlen(dir);
                while (dlen > 0 && dir[dlen-1] == '/')
                        dir[--dlen] = '\0';     /* strip trailing '/' */

                len = snprintf(fname, sizeof(fname), "%s/%s", dir, cmd);
                if (len < 0 || len >= sizeof(fname)) {
                        (void)dobeep_msg("path too long");
                        goto cleanup;
                }
                if(access(fname, F_OK) == 0) {
                        free(pathc);
                        return (TRUE);
                }
        }
cleanup:
        free(pathc);
        return (FALSE);
}