/*
 * input - nested input source file reader
 *
 * Copyright (C) 1999-2007,2014,2018,2021-2023  David I. Bell
 *
 * Calc is open software; you can redistribute it and/or modify it under
 * the terms of the version 2.1 of the GNU Lesser General Public License
 * as published by the Free Software Foundation.
 *
 * Calc is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
 * Public License for more details.
 *
 * A copy of version 2.1 of the GNU Lesser General Public License is
 * distributed with calc under the filename COPYING-LGPL.  You should have
 * received a copy with calc; if not, write to Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Under source code control:   1990/02/15 01:48:16
 * File existed as early as:    before 1990
 *
 * Share and enjoy!  :-)        http://www.isthe.com/chongo/tech/comp/calc/
 */

/*
 * Nested input source file reader.
 * For terminal input, this also provides a simple command stack.
 */


#include <stdio.h>
#include <ctype.h>
#if !defined(_WIN32) && !defined(_WIN64)
# include <pwd.h>
#else
# include <stdlib.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>

#include "have_unistd.h"
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
#endif

#if defined(_WIN32) || defined(_WIN64)
#include <limits.h>
#define _fullpath(f,n,s) (_fixpath(n,f),f)
#endif

#include "calc.h"
#include "conf.h"
#include "hist.h"
#include "strl.h"


#include "errtbl.h"
#include "banned.h"     /* include after system header <> includes */


EXTERN int stdin_tty;           /* TRUE if stdin is a tty */
E_FUNC FILE *f_open(char *name, char *mode);
E_FUNC FILE *curstream(void);


#define TTYSIZE         8191    /* reallocation size for terminal buffers */
#define MAXDEPTH        255     /* maximum depth of input */
#define IS_READ         1       /* reading normally */
#define IS_REREAD       2       /* reread current character */
#define chartoint(ch)   ((ch) & 0xff)   /* make sure char is not negative */
#define READSET_ALLOC   8       /* readset to allocate chunk size */


typedef struct {
        int i_state;            /* state (read, reread) */
        int i_char;             /* currently read char */
        long i_line;            /* line number */
        char *i_cp;             /* pointer to string character to be read */
        char *i_str;            /* start of string copy to be read, or NULL */
        long i_num;             /* number of string characters remaining */
        char *i_ttystr;         /* current character of tty line (or NULL) */
        FILE *i_fp;             /* current file for input (if not NULL) */
        char *i_name;           /* file name if known */
} INPUT;


/* files that calc has read or included */
typedef struct {
        int active;             /* != 0 => active entry, 0 => unused entry */
        char *name;             /* name used to read file */
        char *path;             /* real path used to open file */
        struct stat inode;      /* inode information for file */
} READSET;

STATIC READSET *readset = NULL;         /* array of files read */
STATIC int maxreadset = 0;              /* length of readset */

STATIC int linesize;            /* current max size of input line */
STATIC char *linebuf;           /* current input line buffer */
STATIC char *prompt;            /* current prompt for terminal */
STATIC BOOL noprompt;           /* TRUE if should not print prompt */

STATIC int depth;               /* current input depth */
STATIC INPUT *cip;              /* current input source */
STATIC INPUT inputs[MAXDEPTH];  /* input sources */


S_FUNC int openfile(char *name);
S_FUNC int ttychar(void);
S_FUNC int isinoderead(struct stat *sbuf);
S_FUNC int findfreeread(void);
S_FUNC int addreadset(char *name, char *path, struct stat *sbuf);
S_FUNC char *homeexpand(char *name);


/*
 * Open an input file by possibly searching through a path list
 * and also possibly applying the specified extension.  For example:
 *
 *      opensearchfile("barf", ".:/tmp", ".c", rd_once)
 *
 * searches in order for the files:
 *
 *      "./barf", "./barf.c", "/tmp/barf", and "/tmp/barf.c".
 *
 * Returns -1 if we could not open a file or error.
 * Returns 1 if file was opened and added to/updated in the readset
 * Returns 0 if file was already in the readset and reopen was 0.
 *
 * given:
 *      name            file name to be read
 *      pathlist        list of colon separated paths (or NULL)
 *      extension       extra extension to try (or NULL)
 *      rd_once         TRUE => do not reread a file
 */
int
opensearchfile(char *name, char *pathlist, char *extension, int rd_once)
{
        int i;
        char *cp;
        char *path;             /* name being searched for */
        size_t path_alloc;      /* length of malloced path */
        struct stat statbuf;    /* stat of the path */
        size_t namelen; /* length of name */
        size_t extlen;  /* length of the extension if non-NULL or 0 */
        size_t pathlen; /* length of the pathlist if non-NULL or 0 */

        /* firewall */
        if (name == NULL) {
                math_error("NULL name given to opensearchfile");
                not_reached();
        }

        /*
         * We ignore the pathlist of the file is absolute, dot-relative
         * or tilde-relative or if there is no search path.
         */
        if (name[0] == PATHCHAR ||
            name[0] == HOMECHAR ||
            (name[0] == DOTCHAR && name[1] == '\0') ||
            (name[0] == DOTCHAR && name[1] == DOTCHAR && name[2] == '\0') ||
            (name[0] == DOTCHAR && name[1] == PATHCHAR) ||
            (name[0] == DOTCHAR && name[1] == DOTCHAR && name[2] == PATHCHAR) ||
            pathlist == NULL) {
                pathlist = "";
        }

        /*
         * allocate storage for the longest name being searched for
         *
         * We will allocate more than we could ever want/need.
         * The longest we could ever need would be:
         *
         *      pathlist (as a single long string)
         *      /
         *      name
         *      .
         *      extension
         *      \0
         *      guard byte
         */
        namelen = strlen(name);
        if (extension != NULL) {
            extlen = strlen(extension);
        } else {
            extlen = 0;
        }
        pathlen = strlen(pathlist);
        path_alloc = pathlen+1 + 1 + namelen+1 + extlen+1 + 1 + 1;
        path = malloc(path_alloc+1);
        if (path == NULL) {
                math_error("Cannot allocate filename path buffer");
                not_reached();
        }
        path[0] = '\0'; /* paranoia */
        path[path_alloc] = '\0';        /* paranoia */

        /*
         * Don't try the extension if the filename already contains it.
         */
        if (extension != NULL && namelen >= extlen &&
            strcmp(&name[namelen-extlen], extension) == 0) {
                extension = NULL;
        }

        /*
         * Search through the path list for the file
         */
        pathlist--;
        do {
                pathlist++;
                cp = path;
                while (*pathlist && (*pathlist != LISTCHAR))
                        *cp++ = *pathlist++;
                if (cp != path)
                        *cp++ = PATHCHAR;
                strlcpy(cp, name, namelen+1);
                i = openfile(path);
                if ((i < 0) &&
                    (extension != NULL && extension[0] != '\0')) {
                        strlcat(path, extension, path_alloc+1);
                        i = openfile(path);
                }
        } while ((i < 0) && *pathlist);

        /* examine what our search produced */
        if (i < 0) {
                free(path);
                return i;
        }
        if (cip->i_fp == NULL) {
                /* cannot find a file to open */
                free(path);
                return -3;
        }
        if (fstat(fileno(cip->i_fp), &statbuf) < 0) {
                /* unable to fstat the open file */
                free(path);
                return -4;
        }

        /* note if we will reopen a file and if that is allowed */
        if (rd_once == TRUE && isinoderead(&statbuf) >= 0) {
                /* file is in readset and reopen is false */
                closeinput();
                free(path);
                return 1;
        }

        /* add this name to the readset if allowed */
        if (addreadset(name, path, &statbuf) < 0) {
                /* cannot add to readset */
                closeinput();
                free(path);
                return -1;
        }

        /* file was added to/updated in readset */
        free(path);
        return 0;
}


/*
 * f_pathopen - open an absolute or relative filename along a search path
 *
 * Open a file by possibly searching through a path list.  For example:
 *
 *      f_pathopen("curds", ".:/tmp:~/pub", "r", NULL)
 *
 * searches in order for a file that it can open for reading:
 *
 *      "./curds", "/tmp/curds", "~/pub/curds"
 *
 *      NOTE: ~ is expanded accordingly (see homeexpand() below).
 *
 * However is the file is /absolue/path/name or a ./dot/relative/name, or
 * a ~/home/dir/name, or a ~user/home/name, then the pathlist is ignored
 * we just attempt to open name.
 *
 * and opens the first one that exists and allows the mode.
 *
 *      name            file name to be read
 *      mode            fopen() mode argument
 *                        (one of "r", "w", "a", "r+", "w+", "a+")
 *      pathlist        list of colon separated paths (or NULL)
 *      openpath        if non-NULL, and file was opened, set to malloced
 *                        path used to open
 *
 * returns:
 *      open file stream, NULL ==> file was not found or error
 *      If file was open and openpath was non-NULL, changed to point
 *      to path used to open
 */
FILE *
f_pathopen(char *name, char *mode, char *pathlist, char **openpath)
{
        char *cp;
        char *path;             /* name being searched for */
        size_t namelen;         /* length of name */
        size_t pathlen;         /* length of the pathlist if non-NULL or 0 */
        FILE *ret;              /* return open stream or NULL */

        /* firewall */
        if (name == NULL) {
                math_error("NULL name given to f_pathopen");
                not_reached();
        }
        if (mode == NULL) {
                math_error("NULL mode given to f_pathopen");
                not_reached();
        }

        /*
         * We ignore the pathlist of the file is absolute, dot-relative
         * or tilde-relative or if there is no search path.
         */
        if (name[0] == PATHCHAR ||
            name[0] == HOMECHAR ||
            (name[0] == DOTCHAR && name[1] == '\0') ||
            (name[0] == DOTCHAR && name[1] == DOTCHAR && name[2] == '\0') ||
            (name[0] == DOTCHAR && name[1] == PATHCHAR) ||
            (name[0] == DOTCHAR && name[1] == DOTCHAR && name[2] == PATHCHAR) ||
            pathlist == NULL) {
                pathlist = "";
        }

        /*
         * allocate storage for the longest name being searched for
         *
         * We will allocate more than we could ever want/need.
         * The longest we could ever need would be:
         *
         *      pathlist (as a single long string)
         *      /
         *      name
         *      \0
         *      guard byte
         */
        namelen = strlen(name);
        pathlen = strlen(pathlist);
        path = malloc(pathlen+1 + 1 + namelen+1 + 1 + 1);
        if (path == NULL) {
                math_error("Cannot allocate f_pathopen buffer");
                not_reached();
        }

        /*
         * Search through the path list for the file
         */
        pathlist--;
        do {
                pathlist++;
                cp = path;
                while (*pathlist && (*pathlist != LISTCHAR))
                        *cp++ = *pathlist++;
                if (cp != path)
                        *cp++ = PATHCHAR;
                strlcpy(cp, name, namelen+1);
                ret = f_open(path, mode);
        } while ((ret == NULL) && *pathlist);

        /* if caller wants to know the path, malloc it and return it */
        if (openpath != NULL && ret != NULL) {
                if (path[0] == HOMECHAR) {
                        *openpath = homeexpand(path);
                } else {
                        *openpath = strdup(path);
                }
                if (*openpath == NULL) {
                        free(path);
                        if ((conf->calc_debug & CALCDBG_TTY) && ret == stdin) {
                                printf("f_pathopen: closing stdin "
                                       "on malloc return error\n");
                        }
                        fclose(ret);
                        math_error("cannot malloc return openpath buffer");
                        not_reached();
                }
        }
        free(path);

        /* return open file or NULL */
        return ret;
}


/*
 * Given a filename with a leading ~, expand it into a home directory for
 * that user.  This function will malloc the space for the expanded path.
 *
 * If the path is just ~, or begins with ~/, expand it to the home
 * directory of the current user.  If the environment variable $HOME
 * is known, it will be used, otherwise the password file will be
 * consulted.
 *
 * If the path is just ~username, or ~username/, expand it to the home
 * directory of that user by looking it up in the password file.
 *
 * If the password file must be consulted and the username is not found
 * a NULL pointer is returned.
 *
 * given:
 *      name            a filename with a leading ~
 */
S_FUNC char *
homeexpand(char *name)
{
#if defined(_WIN32) || defined(_WIN64)

        return NULL;

#else /* Windows free systems */

        struct passwd *ent;     /* password entry */
        char *home2;            /* fullpath of the home directory */
        char *fullpath;         /* the malloced expanded path */
        char *after;            /* after the ~user or ~ */
        char *username;         /* extracted username */
        size_t fullpath_len;    /* length of fullpath */
        size_t snprintf_len;    /* malloced snprintf buffer length */

        /* firewall */
        if (name[0] != HOMECHAR)
                return NULL;

        /*
         * obtain the home directory component
         */
        switch (name[1]) {
        case PATHCHAR:          /* ~/... */
        case '\0':              /* ~ */
                home2 = home;
                after = name+1;
                break;
        default:                /* ~username or ~username/... */

                /* extract the username after the ~ */
                after = (char *)strchr(name+2, PATHCHAR);
                if (after == NULL) {
                        /* path is just ~username */
                        ent = (struct passwd *)getpwnam(name+1);
                        if (ent == NULL) {
                                /* unknown user */
                                return NULL;
                        }
                        /* just malloc the home directory and return it */
                        fullpath_len = strlen(ent->pw_dir);
                        fullpath = (char *)malloc(fullpath_len+1);
                        if (fullpath == NULL) {
                                return NULL;
                        }
                        strlcpy(fullpath, ent->pw_dir, fullpath_len+1);
                        return fullpath;
                }
                username = (char *) malloc(after-name + 1 + 1);
                if (username == NULL) {
                        /* failed to malloc username */
                        return NULL;
                }
                strlcpy(username, name+1, after-name+1+1);

                /* get that user's home directory */
                ent = (struct passwd *)getpwnam(username);
                free(username);
                if (ent == NULL) {
                        /* unknown user */
                        return NULL;
                }
                home2 = ent->pw_dir;
                break;
        }

        /*
         * build the fullpath given the home directory
         */
        snprintf_len = strlen(home2)+strlen(after) + 1;
        fullpath = (char *)malloc(snprintf_len+1);
        if (fullpath == NULL) {
                return NULL;
        }
        snprintf(fullpath, snprintf_len, "%s%s", home2, after);
        fullpath[snprintf_len] = '\0';  /* paranoia */
        return fullpath;
#endif /* Windows free systems */
}


/*
 * f_open - ~-expand a filename and fopen() it
 *
 * given:
 *      name            the filename to open
 *      mode            fopen() mode argument
 *                          (one of "r", "w", "a", "r+", "w+", "a+")
 */
FILE *
f_open(char *name, char *mode)
{
        FILE *fp;               /* open file descriptor */
        char *fullname;         /* file name with HOMECHAR expansion */

        /*
         * be sore we are allowed to open a file in this mode
         */
        if (!allow_read && !allow_write) {
                /* no reads and no writes means no opens! */
                if (run_state > RUN_BEGIN) {
                        fprintf(stderr,
                                "open of %s mode %s - %s\n", name, mode,
                                "open for read or write disallowed by -m\n");
                }
                return NULL;
        } else if (!allow_read && strchr(mode, 'r') != NULL) {
                /* reading new files disallowed */
                if (run_state > RUN_BEGIN) {
                        fprintf(stderr,
                                "open of %s mode %s - %s\n", name, mode,
                                "open for read disallowed by -m\n");
                }
                return NULL;
        } else if (!allow_write &&
                   (strchr(mode, 'w') != NULL ||
                    strchr(mode, 'a') != NULL ||
                    strchr(mode, '+') != NULL)) {
                /* writing new files disallowed */
                if (run_state > RUN_BEGIN) {
                        fprintf(stderr,
                                "open of %s mode %s - %s\n", name, mode,
                                "open for write disallowed by -m\n");
                }
                return NULL;
        }

        /*
         * expand ~ if needed
         */
        if (name[0] == HOMECHAR) {
                fullname = homeexpand(name);
                if (fullname == NULL)
                        return NULL;
                fp = fopen(fullname, mode);
                free(fullname);
        } else {
                fp = fopen(name, mode);
        }
        return fp;
}


/*
 * Setup for reading from a input file.
 * Returns -1 if file could not be opened.
 *
 * given:
 *      name            file name to be read
 */
S_FUNC int
openfile(char *name)
{
        FILE *fp;               /* open file descriptor */
        size_t namelen;

        if (depth >= MAXDEPTH)
                 return -2;
        fp = f_open(name, "r");
        if (fp == NULL)
                 return -1;
        cip = inputs + depth++;
        cip->i_state = IS_READ;
        cip->i_char = '\0';
        cip->i_str = NULL;
        cip->i_ttystr = NULL;
        cip->i_fp = fp;
        cip->i_line = 1;
        namelen = strlen(name);
        cip->i_name = (char *)malloc(namelen+1);
        if (cip->i_name == NULL) {
                 return -1;
        }
        strlcpy(cip->i_name, name, namelen+1);
        return 0;
}


/*
 * Return the current input file stream, or NULL if none.
 */
FILE *
curstream(void)
{
        if (depth <= 0 || depth > MAXDEPTH)
                return NULL;
        return cip->i_fp;
}


/*
 * Open a string for scanning, num characters to be read.
 * String is copied into local memory so it can be trashed afterwards.
 * Returns -1 if cannot open string.
 *
 * given:
 *      str             string to be opened
 *      num             length of string to open
 */
int
openstring(char *str, size_t num)
{
        char *cp;               /* copied string */

        if ((depth >= MAXDEPTH) || (str == NULL))
                 return -2;
        cp = (char *) malloc(num + 1);
        if (cp == NULL)
                 return -1;
        strlcpy(cp, str, num+1);
        cip = inputs + depth++;
        cip->i_state = IS_READ;
        cip->i_char = '\0';
        cip->i_cp = cp;
        cip->i_str = cp;
        cip->i_num = num;
        cip->i_fp = NULL;
        cip->i_name = NULL;
        cip->i_ttystr = NULL;
        cip->i_line = 1;
        return 0;
}


/*
 * Set to read input from the terminal.
 * Returns -1 if there is no more depth for input.
 */
int
openterminal(void)
{
        if (depth >= MAXDEPTH)
                 return -2;
        cip = inputs + depth++;
        cip->i_state = IS_READ;
        cip->i_char = '\0';
        cip->i_str = NULL;
        cip->i_ttystr = NULL;
        cip->i_fp = NULL;
        cip->i_name = NULL;
        cip->i_line = 1;
        return 0;
}


/*
 * Close the current input source.
 */
void
closeinput(void)
{
        if (depth <= 0)
                return;
        if (cip->i_str)
                free(cip->i_str);
        if (cip->i_fp) {
                if ((conf->calc_debug & CALCDBG_TTY) && cip->i_fp == stdin) {
                        printf("closeinput: closing stdin "
                                "at depth: %d\n", depth);
                }
                fclose(cip->i_fp);
        }
        if (cip->i_name)
                free(cip->i_name);
        depth--;
        cip = depth ? &inputs[depth - 1] : NULL;
}


/*
 * Reset the input sources back to the initial state.
 */
void
resetinput(void)
{
        while (depth > 0)
                closeinput();
        noprompt = FALSE;
}


/*
 * Set the prompt for terminal input.
 */
void
setprompt(char *str)
{
        prompt = str;
        noprompt = FALSE;
}


/*
 * Read the next character from the current input source.
 * End of file closes current input source, and returns EOF character.
 */
int
nextchar(void)
{
        int ch;                 /* current input character */

        if (depth == 0)         /* input finished */
                 return EOF;
        if (cip->i_state == IS_REREAD) {        /* rereading current char */
                 ch = cip->i_char;
                 cip->i_state = IS_READ;
                 if (ch == '\n')
                        cip->i_line++;
                 return ch;
        }
        if (cip->i_str) {               /* from string */
                if (cip->i_num) {
                        ch = chartoint(*cip->i_cp++);
                        cip->i_num--;
                } else {
                        ch = EOF;
                }
        } else if (cip->i_fp) {         /* from file */
                ch = fgetc(cip->i_fp);
        } else if (!stdin_tty) {                /* from file */
                ch = fgetc(stdin);
        } else {                        /* from terminal */
                ch = ttychar();
        }
        if (depth > 0)
                cip->i_char = ch;       /* save for rereads */
        if (ch == '\n')
                cip->i_line++;
        return ch;
}


/*
 * Read in the next line of input from the current input source.
 * The line is terminated with a null character, and does not contain
 * the final newline character.  The returned string is only valid
 * until the next such call, and so must be copied if necessary.
 * Returns NULL on end of file.
 */
char *
nextline(void)
{
        char *cp;
        int ch;
        int len;

        cp = linebuf;
        if (linesize == 0) {
                cp = (char *)malloc(TTYSIZE + 1);
                if (cp == NULL) {
                        math_error("Cannot allocate line buffer");
                        not_reached();
                }
                linebuf = cp;
                linesize = TTYSIZE;
        }
        len = 0;
        for (;;) {
                noprompt = TRUE;
                ch = nextchar();
                noprompt = FALSE;
                if (ch == EOF)
                        return NULL;
                if (ch == '\0')
                        continue;
                if (ch == '\n')
                        break;
                if (len >= linesize) {
                        cp = (char *)realloc(cp, linesize + TTYSIZE + 1);
                        if (cp == NULL) {
                                math_error("Cannot realloc line buffer");
                                not_reached();
                        }
                        linebuf = cp;
                        linesize += TTYSIZE;
                }
                cp[len++] = (char)ch;
        }
        cp[len] = '\0';
        return linebuf;
}


/*
 * Read the next character from the terminal.
 * The routines in the history module are called so that the user
 * can use a command history and emacs-like editing of the line.
 */
S_FUNC int
ttychar(void)
{
        int ch;                 /* current char */
        int len;                /* length of current command */
        STATIC char charbuf[256*1024];

        /*
         * If we have more to read from the saved command line, then do that.
         * When we see a newline character, then clear the pointer so we will
         * read a new line on the next call.
         */
        if (cip->i_ttystr) {
                ch = chartoint(*cip->i_ttystr++);
                if (ch == '\n')
                        cip->i_ttystr = NULL;
                return ch;
        }

        /*
         * We need another complete line.
         */
        abortlevel = 0;
        inputwait = TRUE;
        len = hist_getline(noprompt ? "" : prompt, charbuf, sizeof(charbuf));
        if (len == 0) {
                inputwait = FALSE;
                return EOF;
        }
        inputwait = FALSE;

        /*
         * Handle shell escape if present
         */
        if (charbuf[0] == '!') {                /* do a shell command */
                char *cmd;
                int ret;

                cmd = charbuf + 1;
                if (*cmd == '\0' || *cmd == '\n')
                        cmd = shell;
                if (allow_exec) {
                        if (conf->calc_debug & CALCDBG_SYSTEM) {
                                printf("%s\n", cmd);
                        }
                        ret = system(cmd);
                        if (ret < 0) {
                                fprintf(stderr, "error in cmd: %s\n", cmd);
                        }
                } else {
                        fprintf(stderr, "execution disallowed by -m flag\n");
                }
                return '\n';
        }
        hist_saveline(charbuf, len);

        /*
         * Return the first character of the line, and set up to
         * return the rest of it with later calls.
         */
        ch = chartoint(charbuf[0]);
        if (ch != '\n')
                cip->i_ttystr = charbuf + 1;
        return ch;
}


/*
 * Return whether or not the input source is the terminal.
 */
BOOL
inputisterminal(void)
{
        return ((depth <= 0) || ((cip->i_str == NULL) && (cip->i_fp == NULL)));
}


/*
 * Return depth of current input source
 */
int
inputlevel(void)
{
        return depth - 1;
}


/*
 * Return the name of the current input file.
 * Returns NULL for terminal or strings.
 */
char *
inputname(void)
{
        if (depth <= 0)
                return NULL;
        return cip->i_name;
}


/*
 * Return the current line number.
 */
long
linenumber(void)
{
        if (depth > 0)
                return cip->i_line;
        return 1;
}


/*
 * Restore the next character to be read again on the next nextchar call.
 */
void
reread(void)
{
        if ((depth <= 0) || (cip->i_state == IS_REREAD))
                return;
        cip->i_state = IS_REREAD;
        if (cip->i_char == '\n')
                cip->i_line--;
}


/*
 * Process all startup files found in the $CALCRC path.
 */
void
runrcfiles(void)
{
        char path[MAX_CALCRC+1+1];      /* name being searched for */
        char *cp;
        char *p;

        /* execute each file in the list */
        while (calcrc != NULL && *calcrc) {
                cp = calcrc;
                calcrc = (char *) strchr(calcrc + 1, LISTCHAR);

                /* load file name into the path */
                if (calcrc == NULL) {
                        strlcpy(path, cp, MAX_CALCRC+1);
                } else {
                        strlcpy(path, cp, calcrc - cp + 1);
                }

                /* find the start of the path */
                p = (path[0] == ':') ? path + 1 : path;
                if (p[0] == '\0') {
                        continue;
                }

                /* process the current file in the list */
                if (openfile(p) < 0) {
                        /* Unable to open rcfile */
                        if (c_flag && !d_flag)
                                 fprintf(stderr,
                                        "Unable to open rcfile \"%s\"\n", p);
                        continue;
                }
                getcommands(FALSE);
                closeinput();
        }
}


/*
 * isinoderead - determine if we have read a given dev/inode
 *
 * This function returns the index of the readset element that matches
 * a given device/inode, -1 otherwise.
 *
 *
 * _WIN32 and _WIN64 NOTE:
 *
 *      This function likely will not work under _WIN32 or _WIN64.
 *
 *      The sbuf->st_ino is always zero because the FAT and NTFS filesystems
 *      do not support inodes.  Those filesystems don't support links, which
 *      is why you need this function under UNIX. For _WIN32 or _WIN64, use
 *      _fullpath() to determine if you have already opened a file.
 *
 * given:
 *      sbuf            stat of the inode in question
 */

S_FUNC int
isinoderead(struct stat *sbuf)
{
        int i;

        /* deal with the empty case */
        if (readset == NULL || maxreadset <= 0) {
                /* readset is empty */
                return -1;
        }

        /* scan the entire readset */
        for (i=0; i < maxreadset; ++i) {
#if defined(_WIN32) || defined(_WIN64)
                char tmp[MAX_PATH+1];
                tmp[MAX_PATH] = '\0';
                if (_fullpath(tmp, cip->i_name, MAX_PATH) &&
                    readset[i].active &&
                    strcasecmp(readset[i].path, tmp) == 0) {
                        /* found a match */
                        return i;
                }
#else /* Windows free systems */
                if (readset[i].active &&
                    sbuf->st_dev == readset[i].inode.st_dev &&
                    sbuf->st_ino == readset[i].inode.st_ino) {
                        /* found a match */
                        return i;
                }
#endif /* Windows free systems */
        }

        /* no match found */
        return -1;
}


/*
 * findfreeread - find the next free readset element
 *
 * This function will return the index of the next free readset element.
 * If needed, this function will allocate new readset elements.
 *
 * This function returns the index of the next free element, or -1.
 */
S_FUNC int
findfreeread(void)
{
        int i;

        /* deal with an empty readset case */
        if (readset == NULL || maxreadset <= 0) {

                /* malloc a new readset */
                readset = (READSET *)malloc((READSET_ALLOC+1)*sizeof(READSET));
                if (readset == NULL) {
                        return -1;
                }
                maxreadset = READSET_ALLOC;
                for (i=0; i < READSET_ALLOC; ++i) {
                        readset[i].active = 0;
                }

                /* return first entry */
                return 0;
        }

        /* try to find a free readset entry */
        for (i=0; i < maxreadset; ++i) {
                if (readset[i].active == 0) {
                        /* found a free readset entry */
                        return i;
                }
        }

        /* all readset entries are in use, allocate more */
        readset = (READSET *)realloc(readset,
            (maxreadset+READSET_ALLOC) * sizeof(READSET));
        if (readset == NULL) {
                return -1;
        }
        for (i=0; i < READSET_ALLOC; ++i) {
                readset[i+maxreadset].active = 0;
        }
        maxreadset += READSET_ALLOC;

        /* return the first newly allocated free entry */
        return maxreadset-READSET_ALLOC;
}


/*
 * addreadset - add a entry to the readset array if it is not already there
 *
 * This function attempts to add a file into the readset.  If the readset
 * has an entry with a matching dev/inode, then that entry is updated with
 * the new name and path.  If no such readset entry is found, a new entry
 * is added.
 *
 * This function returns the index of the readset entry, or -1 if error.
 *
 * given:
 *      name            name given to read or include
 *      path            full pathname of file
 *      sbuf            stat of the path
 */
S_FUNC int
addreadset(char *name, char *path, struct stat *sbuf)
{
        int ret;                /* index to return */
        size_t name_len;        /* length of read set name */
        size_t path_len;        /* length of path to read set name */

        /* find the inode */
        ret = isinoderead(sbuf);
        if (ret < 0) {
                /* not in readset, find a free node */
                ret = findfreeread();
                if (ret < 0) {
                        /* cannot find/form a free readset entry */
                        return -1;
                }
        } else {
                /* found an readset entry, free old readset data */
                if (readset[ret].name != NULL) {
                        free(readset[ret].name);
                }
                if (readset[ret].path != NULL) {
                        free(readset[ret].path);
                }
        }

        /* load our information into the readset entry */
        name_len = strlen(name);
        readset[ret].name = (char *)malloc(name_len+1);
        if (readset[ret].name == NULL) {
                return -1;
        }
        strlcpy(readset[ret].name, name, name_len+1);
#if defined(_WIN32) || defined(_WIN64)
        /*
         * For _WIN32 or _WIN64, _fullpath expands the path to a fully qualified
         * path name, which under _WIN32 or _WIN64 FAT and NTFS is unique, just
         * like UNIX inodes. _fullpath also allocated the memory for
         * this new longer path name.
         */
         {
                readset[ret].path = _fullpath(NULL, path, MAX_PATH);
                if (readset[ret].path == NULL) {
                        return -1;
                }
         }
#else /* Windows free systems */
        path_len = strlen(path);
        readset[ret].path = (char *)malloc(path_len+1);
        if (readset[ret].path == NULL) {
                return -1;
        }
        strlcpy(readset[ret].path, path, path_len+1);
#endif /* Windows free systems */
        readset[ret].inode = *sbuf;
        readset[ret].active = 1;

        /* return index of the newly added entry */
        return ret;
}
