From f9e6664929164e11f5fa2fdce778d652e234c6ec Mon Sep 17 00:00:00 2001 From: Jim Monte Date: Sat, 14 Dec 2019 01:13:13 -0500 Subject: [PATCH] Fixed resolution of ~ to home directory. (Bug #405) Also fixed potential buffer overruns in glob expansion --- src/frontend/parser/glob.c | 842 +++++++++++++++++++++++++++++------- src/frontend/streams.c | 2 + src/include/ngspice/cpstd.h | 2 +- src/misc/tilde.c | 267 ++++++++---- src/misc/tilde.h | 8 +- 5 files changed, 888 insertions(+), 233 deletions(-) diff --git a/src/frontend/parser/glob.c b/src/frontend/parser/glob.c index ac32b642a..4cdd8c8f0 100644 --- a/src/frontend/parser/glob.c +++ b/src/frontend/parser/glob.c @@ -6,9 +6,12 @@ Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group /* * Expand global characters. */ +#include #include "ngspice/ngspice.h" #include "ngspice/cpdefs.h" +#include "ngspice/wordlist.h" +#include "../misc/tilde.h" #include "glob.h" #ifdef HAVE_SYS_DIR_H @@ -30,198 +33,725 @@ Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group #include #endif +#define OPT_WLL_COPY_ALL 1 +/* This structure is a "long-form" of the wordlist structure. The inital + * wordlist structure fields have the same meanings as in a standalone + * wordlist structure, except that the allocations for p_escape and + * p_after are separate from the one for wl_word. This structure is useful + * when a wordlist must undergo many modifications to its words, as when + * globbing is being expanded */ +typedef struct wordlist_l { + struct wordlist wl; + size_t n_char_word; /* length of word excluding null */ + size_t n_elem_word_alloc; /* Allocated size of word array */ +} wordlist_l; + +static void wl_modify_word(wordlist *wl_node, unsigned int n_input, + const size_t *p_n_char_word, char **pp_worde); +static wordlist *wll_to_wl(const wordlist_l *wll); +static wordlist_l *wll_append(wordlist_l *wl_dst, wordlist_l *wl_to_append); +static void wll_append_to_node(wordlist_l *dst, const wordlist_l *to_append); +static wordlist_l *wll_cons( + size_t n_elem_word_alloc, size_t n_char_word, const char *p_word, + unsigned int opt, wordlist_l *tail); +static void wll_free(wordlist_l *wll); +static wordlist *wll_node_to_wl_node(const wordlist_l *wll); char cp_comma = ','; char cp_ocurl = '{'; char cp_ccurl = '}'; char cp_til = '~'; -static wordlist *bracexpand(char *string); -static wordlist *brac1(char *string); -static wordlist *brac2(char *string); +static wordlist_l *brac1(size_t offset_ocurl1, const char *p_str_cur); +static wordlist_l *brac2(const char *string, + size_t *p_n_char_processed); +static wordlist *bracexpand(const wordlist *w_exp); +static void merge_home_with_rest(wordlist *wl_node, + size_t n_char_home, const char *sz_home, size_t n_char_skip); +static inline void strip_1st_char(wordlist *wl_node); +static void tilde_expand_word(wordlist *wl_node); /* For each word, go through two steps: expand the {}'s, and then do ?*[] * globbing in them. Sort after the second phase but not the first... - */ - -/* MW. Now only tilde is supported, {}*? don't work */ - -wordlist * -cp_doglob(wordlist *wlist) + * + * Globbing of arbitrary levels of brace nesting and tilde expansion to the + * name of a "HOME" directory are supported. ?*[] are not */ +wordlist *cp_doglob(wordlist *wlist) { - wordlist *wl; - char *s; - /* Expand {a,b,c} */ - - for (wl = wlist; wl; wl = wl->wl_next) { - wordlist *nwl, *w = bracexpand(wl->wl_word); - if (!w) { - wlist->wl_word = NULL; /* XXX */ - return (wlist); - } - nwl = wl_splice(wl, w); - if (wlist == wl) - wlist = w; - wl = nwl; - } - - /* Do tilde expansion. */ - - for (wl = wlist; wl; wl = wl->wl_next) - if (*wl->wl_word == cp_til) { - s = cp_tildexpand(wl->wl_word); - txfree(wl->wl_word); /* sjb - fix memory leak */ - if (!s) - wl->wl_word = copy(""); /* MW. We Con't touch tmalloc addres */ - else - wl->wl_word = s; - } - - return (wlist); -} - - -static wordlist * -bracexpand(char *string) -{ - wordlist *wl, *w; - char *s; - - if (!string) - return (NULL); - wl = brac1(string); - if (!wl) - return (NULL); - for (w = wl; w; w = w->wl_next) { - s = w->wl_word; - w->wl_word = copy(s); - tfree(s); - } - return (wl); -} - -/* Given a string, returns a wordlist of all the {} expansions. This is - * called recursively by cp_brac2(). All the words here will be of size - * BSIZE_SP, so it is a good idea to copy() and free() the old words. - */ - -static wordlist * -brac1(char *string) -{ - wordlist *words, *wl, *w, *nw, *nwl, *newwl; - char *s; - int nb; - - words = wl_cons(TMALLOC(char, BSIZE_SP), NULL); - words->wl_word[0] = '\0'; - for (s = string; *s; s++) { - if (*s == cp_ocurl) { - nwl = brac2(s); - nb = 0; - for (;;) { - if (*s == cp_ocurl) - nb++; - if (*s == cp_ccurl) - nb--; - if (*s == '\0') { - fprintf(cp_err, "Error: missing }.\n"); - return (NULL); - } - if (nb == 0) - break; - s++; + { + wordlist *wl = wlist; + while (wl != (wordlist *) NULL) { + wordlist *w = bracexpand(wl); + if (!w) { + wl_free(wlist); + return (wordlist *) NULL; } - /* Add nwl to the rest of the strings in words. */ - newwl = NULL; - for (wl = words; wl; wl = wl->wl_next) - for (w = nwl; w; w = w->wl_next) { - nw = wl_cons(TMALLOC(char, BSIZE_SP), NULL); - (void) strcpy(nw->wl_word, wl->wl_word); - (void) strcat(nw->wl_word, w->wl_word); - newwl = wl_append(newwl, nw); + + /* Replace the node that was just expanded, wl, with the + * expansion w (if different) and continue after that */ + if (wl != w) { + wordlist *wl_next = wl->wl_next; + (void) wl_splice(wl, w); + + /* Update head of list if the replacement + * changed it */ + if (wlist == wl) { + wlist = w; } - wl_free(words); - wl_free(nwl); - words = newwl; - } else { - for (wl = words; wl; wl = wl->wl_next) - appendc(wl->wl_word, *s); + + /* Continue after the spliced nodes since + * they are already fully expanded */ + wl = wl_next; + } + else { /* same node, so just step to the next node */ + wl = wl->wl_next; + } + } /* end of loop over words in wordlist */ + } /* end of block expanding braces */ + + /* Do tilde expansion on each word. */ + { + wordlist *wl; + for (wl = wlist; wl; wl = wl->wl_next) { + if (*wl->wl_word == cp_til) { + tilde_expand_word(wl); + } + } /* end of loop over words in wordlist */ + } /* end of block expanding braces */ + + return wlist; +} /* end of function cp_doglob */ + + + +static wordlist *bracexpand(const wordlist *w_exp) +{ + const char * const wl_word = w_exp->wl_word; + + /* If no string, nothing to expand */ + if (wl_word == (char *) NULL) { + return (wordlist *) NULL; + } + + + /* Find first opening brace. If none, the string expands to itself as + * a wordlist */ + size_t offset_ocurl = ~(size_t) 0; /* flag for not found */ + + /* Loop until find opening brace or end of string */ + { + const char *p_cur = wl_word; + char ch_cur; + for ( ; (ch_cur = *p_cur) != '\0'; p_cur++) { + if (ch_cur == cp_ocurl) { + offset_ocurl = p_cur - wl_word; + break; + } } } - return (words); -} + + + /* Test for '{' and glob if there is one */ + if (offset_ocurl != ~(size_t) 0) { + /* Found a brace, so glob */ + wordlist_l *wll_glob = brac1(offset_ocurl, wl_word); + + wordlist *wl_glob = wll_to_wl(wll_glob); + wll_free(wll_glob); + return wl_glob; + } + + /* Unescaped '{' not found, so return the input node */ + return (wordlist *) w_exp; +} /* end of function bracexpand */ + + + +/* Given a string, returns a wordlist of all the {} expansions. This function + * calls cp_brac2() with braced expressions and is called recursively by + * cp_brac2(). + * + * Parameters + * offset_ocurl1: Offset from p_str where the first opening brace occurs + * or the offset to the terminating null of p_str (length of the + * string) if it contains no opening brace. + */ +static wordlist_l *brac1(size_t offset_ocurl1, const char *p_str) +{ + wordlist_l *words; + const char *s; + + /* Create the inital entry in the list using all of the characters + * before the first '{' */ + { + const size_t n_byte_alloc = BSIZE_SP + 1; + + words = wll_cons(n_byte_alloc, offset_ocurl1, p_str, + OPT_WLL_COPY_ALL, (wordlist_l *) NULL); + } + + /* Step through string. In each iteration one {} group and the ungrouped + * characters following that group, if any are processed */ + for (s = p_str + offset_ocurl1; *s != '\0'; ) { + { /* Process braced expression */ + size_t n_char_processed; + + /* Process braced list using brac2() */ + wordlist_l *nwl = brac2(s, + &n_char_processed); + if (nwl == (wordlist_l *) NULL) { + /* brac2() already printed an error message */ + wll_free(words); + return (wordlist_l *) NULL; + } + + /* New wordlist to replace existing words. Note + * the number of nodes is + * #(existing list) X #(brac2() list). Each of + * the brac2() words is appended to each of the + * existing words to form the new wordlist */ + wordlist_l *newwl = (wordlist_l *) NULL; + + /* For each word in the existing word list (words) */ + wordlist_l *wl; /* loop iterator */ + for (wl = words; wl; wl = (wordlist_l *) wl->wl.wl_next) { + + /* For each word in the word list from brac2() */ + wordlist_l *w; /* loop iterator */ + for (w = nwl; w; w = (wordlist_l *) w->wl.wl_next) { + wordlist_l *nw = wll_cons( + BSIZE_SP + 1, 0, (char *) NULL, + OPT_WLL_COPY_ALL, (wordlist_l *) NULL); + wll_append_to_node(nw, wl); + wll_append_to_node(nw, w); + newwl = wll_append(newwl, nw); + } /* end of loop over words from brac2() */ + } /* end of loop over words */ + wll_free(words); + wll_free(nwl); + words = newwl; + s += n_char_processed; /* skip braced list */ + } /* end of processing of braced expression */ + + { + /* Apend all chars after {} expression until the next + * '{' or the end of the word to each word in the wordlist */ + const char * const p_start = s; + char ch_cur; + for ( ; (ch_cur = *s) != cp_ocurl; s++) { + if (ch_cur == '\0') { + break; + } + } + + const size_t n_char_append = s - p_start; + + if (n_char_append > 0) { + wordlist_l *wl; + for (wl = words; wl; wl = (wordlist_l *) wl->wl.wl_next) { + const size_t n_char_total = wl->n_char_word + + n_char_append; + const size_t n_elem_needed = n_char_total + 1; + if (wl->n_elem_word_alloc < n_elem_needed) { + const size_t n_elem_alloc = 2 * n_elem_needed; + wl->wl.wl_word = TREALLOC(char, wl->wl.wl_word, + n_elem_alloc); + wl->n_elem_word_alloc = n_elem_alloc; + } + char *p_dst = wl->wl.wl_word + wl->n_char_word; + (void) memcpy(p_dst, p_start, n_char_append); + p_dst += n_char_append; + *p_dst = '\0'; + wl->n_char_word = n_char_total; + } + } + } /* end of characters after braced expression */ + } /* end of loop over braced expressions + following chars in string */ + + return words; +} /* end of function brac1 */ + + /* Given a string starting with a {, return a wordlist of the expansions - * for the text until the matching }. + * for the text until the matching }. A vaild input string must have both + * an opening brace and a closing brace. If an error occurs, NULL is + * returned. On a successful return, *p_n_char_processed will contain the + * number of characters processed by brac2 up to and including the closing + * outermost brace */ - -static wordlist * -brac2(char *string) +static wordlist_l *brac2(const char *string, + size_t *p_n_char_processed) { - wordlist *wlist = NULL, *nwl; - char buf[BSIZE_SP], *s; - int nb; - bool eflag = FALSE; + wordlist_l *wlist = (wordlist_l *) NULL; + char buf_fixed[BSIZE_SP]; /* default work buffer */ + char *buf = buf_fixed; /* actual work buffer */ + bool eflag = FALSE; /* end-of-processing flag */ + + /* Required buffer size. Note that 1st char of string is not copied, + * so strlen(string) includes the length of the null at the end */ + const size_t n_elem_needed = strlen(string); + + /* Allocate and use a larger buffer if required */ + if (n_elem_needed > BSIZE_SP) { /* will not fit in stack buffer */ + buf = TMALLOC(char, n_elem_needed); + } string++; /* Get past the first open brace... */ - for (;;) { - (void) strcpy(buf, string); - nb = 0; - s = buf; - for (;;) { - if ((*s == cp_ccurl) && (nb == 0)) { - eflag = TRUE; - break; - } - if ((*s == cp_comma) && (nb == 0)) - break; - if (*s == cp_ocurl) - nb++; - if (*s == cp_ccurl) + (void) strcpy(buf, string); /* make a copy of string */ + char *buf_cur = buf; /* current position in buffer */ + + /* Each iteration of the outer loop processes one comma-separated + * expression of the top-level brace-enclosed list */ + for ( ; ; ) { + int nb = 0; /* number of braces **inside 1st brace** */ + size_t offset_ocurl1 = SIZE_MAX; /* Offset to 1st '{' */ + + /* Start processing at start of next top-level term */ + char *s = buf_cur; + + /* Scan the string until the next comma at the top level is found, + * the closing brace at the top level is found or the string ends + * with a missing brace. If another term is found, it is processed + * as a null-terminated string by calling brac1() */ + for ( ; ; ) { + const char ch_cur = *s; + if (ch_cur == cp_ccurl) { /* closing brace found */ + if (nb == 0) { + /* A closing brace found without any internal opening + * braces, so this one is the outermost brace */ + eflag = TRUE; /* done -- set end flag */ + break; + } + /* Else closing brace of internal level */ nb--; - if (*s == '\0') { - fprintf(cp_err, "Error: missing }.\n"); - return (NULL); } - s++; - } + else if (ch_cur == cp_ocurl) { /* another brace level started */ + if (nb++ == 0) { /* Inc count. If 1st '{', save offset */ + offset_ocurl1 = s - buf_cur; + } + } + else if ((ch_cur == cp_comma) && (nb == 0)) { + /* Comma found outside of any internal braced + * expression */ + break; + } + + /* Check if reached end of string. If so, the closing + * brace was not present. */ + if (ch_cur == '\0') { + fprintf(cp_err, "Error: missing }.\n"); + if (buf != buf_fixed) { /* free allocation if made */ + txfree(buf); + } + if (wlist != (wordlist_l *) NULL) { + wll_free(wlist); + } + + return (wordlist_l *) NULL; + } + s++; /* process next char */ + } /* end of loop finding end of braces */ + + /* The above loop exits without returning from the function if either + * a comma at the top level or the closing top level brace is reached. + * Variable s points to this location. By setting it to null, a string + * is created for one of the comma-separated expressions at the top + * level */ *s = '\0'; - nwl = brac1(buf); - wlist = wl_append(wlist, nwl); - string += s - buf + 1; - if (eflag) - return (wlist); - } -} + + /* Process the top-level expression found and append to the + * wordlist being built */ + { + wordlist_l *nwl = brac1( + offset_ocurl1 == SIZE_MAX ? s - buf_cur : offset_ocurl1, + buf_cur); + wlist = wll_append(wlist, nwl); + } + + /* Check for competion of processing */ + if (eflag) { /* done -- normal function exit */ + if (buf != buf_fixed) { /* free allocation if made */ + txfree(buf); + } + + /* When the loop is exited, s is at a brace or comma, which + * is also considered to be processed. Hence +2 not +1. */ + *p_n_char_processed = s - buf + 2; + return wlist; + } + + buf_cur = s + 1; /* go to next term after comma */ + } /* end of loop over top-level comma-separated expressions */ +} /* end of function brac2 */ + + /* Expand tildes. */ - -char * -cp_tildexpand(char *string) +char *cp_tildexpand(char *string) { - char *result; + /* Attempt to do the tilde expansion */ + char * const result = tildexpand(string); - result = tildexpand(string); - - if (!result) { - if (cp_nonomatch) + if (!result) { /* expansion failed */ + if (cp_nonomatch) { /* If set, should return the original string */ return copy(string); - else - return NULL; + } + /* Else should return NULL to indiciate failure */ + return (char *) NULL; } - return result; -} + return result; /* successful expansion returned */ +} /* end of function cp_tildexpand */ + /* Say whether the pattern p can match the string s. */ /* MW. Now simply compare strings */ -bool -cp_globmatch(char *p, char *s) +bool cp_globmatch(char *p, char *s) { - return (!(strcmp(p, s))); -} + return !(strcmp(p, s)); +} /* end of function cp_globmatch */ + + + +/* This function expands the leading ~ of wl_node. */ +static void tilde_expand_word(wordlist *wl_node) +{ + char *word = wl_node->wl_word; + char *p_char_cur = ++word; + char ch = *p_char_cur; + if (ch == '\0' || ch == DIR_TERM) { + char *sz_home; + const int n_char_home = get_local_home(0, &sz_home); + if (n_char_home < 0) { /* expansion failed */ + /* Strip the ~ and return the rest */ + strip_1st_char(wl_node); + return; + } + merge_home_with_rest(wl_node, (size_t) n_char_home, sz_home, 1); + return; + } + +#ifdef HAVE_PWD_H + /* ~bob -- Get name of user and find home for that user */ + { + char * const usr_start = wl_node->wl_word + 1; + char *usr_end = usr_start; + char c; + while ((c = *usr_end) != '\0' && c != DIR_TERM) { + ++usr_end; + } + const size_t n_char_usr = usr_end - usr_start; + const size_t n_byte_usr = n_char_usr + 1; + const char c_orig = c; /* save char to be overwritten by '\0' */ + *usr_end = '\0'; + + char *sz_home; + const int n_char_home = get_usr_home(usr_start, 0, &sz_home); + *usr_end = c_orig; /* restore char overwritten by '\0' */ + if (n_char_home < 0) { + strip_1st_char(wl_node); + return; /* Strip the ~ and return the rest */ + } + merge_home_with_rest(wl_node, (size_t) n_char_home, sz_home, + n_char_usr + 1); + return; + } + +#else + /* ~bob is meaningless. Strip the ~ and return the rest */ + strip_1st_char(wl_node); + return; +#endif +} /* end of function tilde_expand_word */ + + + +/* Strip the 1st char. Equivalent to merging an empty HOME string with the + * chars after the 1st char. Assumes string is at least 1 char long + * excluding trailing NULL */ +static inline void strip_1st_char(wordlist *wl_node) +{ + merge_home_with_rest(wl_node, 0, (char *) NULL, 1); + return; +} /* end of function strip_1st_char */ + + + +/* This function modifies the wordlist node to consist of + + * rest of string after n_char_skip. It is assumed that the string + * is at least n_char_skip characters long excluding the trailing null */ +static inline void merge_home_with_rest(wordlist *wl_node, + size_t n_char_home, const char *sz_home, size_t n_char_skip) +{ + size_t p_n_char_word[2]; + p_n_char_word[0] = n_char_home; + p_n_char_word[1] = strlen(wl_node->wl_word) - n_char_skip; + + char *pp_word[2]; + pp_word[0] = (char *) sz_home; + pp_word[1] = wl_node->wl_word + n_char_skip; + + wl_modify_word(wl_node, 2u, p_n_char_word, pp_word); + return; +} /* end of function merge_home_with_rest */ + + + + +/*** Long-form wordlist functions ***/ + +/* This function converts long-form wordlist wll to a standard wordlist. + * The input long-form list is not modified. */ +static wordlist *wll_to_wl(const wordlist_l *wll) +{ + /* Handle degnerate case of NULL input */ + if (wll == (wordlist_l *) NULL) { + return (wordlist *) NULL; + } + + /* There is at least one node in the long-form wordlist. */ + /* Convert it to a standard wordlist node, which is the node to + * return */ + wordlist * const wl_start = wll_node_to_wl_node(wll); + wordlist * wl_dst_prev = wl_start; + wl_start->wl_prev = (wordlist *) NULL; + + /* Continue adding nodes */ + for (wll = (wordlist_l *) wll->wl.wl_next ; wll != (wordlist_l *) NULL; + wll = (wordlist_l *) wll->wl.wl_next) { + /* Convert a single long-form node to a standard for node */ + wordlist *wl_dst_cur = wll_node_to_wl_node(wll); + wl_dst_prev->wl_next = wl_dst_cur; + wl_dst_cur->wl_prev = wl_dst_prev; + wl_dst_prev = wl_dst_cur; + } /* end of loop over nodes in input long-form wordlist */ + + /* Terminate the list of words */ + wl_dst_prev->wl_next = (wordlist *) NULL; + return wl_start; +} /* end of function wll_to_wl */ + + + +/* This function creates word data for a standard list from word data of + * a long-form list. Both structures must be allocated on input, and the + * long-form data is not change */ +static wordlist *wll_node_to_wl_node(const wordlist_l *wll) +{ + /* Allocate node being returned */ + wordlist * const wl_dst = TMALLOC(wordlist, 1); + + /* Find required size of allocation and save lengths of arrays */ + const size_t n_char_word = wll->n_char_word; + const size_t n_byte_alloc = n_char_word + 1; + + /* Allocate buffer */ + char * p_dst_cur = wl_dst->wl_word = TMALLOC(char, n_byte_alloc); + + /* Word data */ + wl_dst->wl_word = p_dst_cur; + (void) memcpy(p_dst_cur, wll->wl.wl_word, n_char_word); + p_dst_cur += n_char_word; + *p_dst_cur++ = '\0'; + + return wl_dst; +} /* end of function wll_node_to_wl_node */ + + + +/* Free a long-form wordlist */ +void wll_free(wordlist_l *wll) +{ + while (wll != (wordlist_l *) NULL) { + wordlist_l * const next = (wordlist_l *) wll->wl.wl_next; + void *p; + if ((p = (void *) wll->wl.wl_word) != NULL) { + txfree(p); + } + txfree(wll); + wll = next; + } /* end of loop over wordlist nodes */ +} /* end of function wll_free */ + + + +/* This function prepends a wordlist_l node to the existing list. + * + * Parameters + * n_char_word: Length of word, excluding trailing NULL + * p_word: Address of word, or NULL if none. A null terminiation is + * not required if the word will be duplicated. + * opt: OPT_WLL_COPY_ALL -- create copy instead of resuing word allocation + * tail: Address of wordlist having this word prepended. May be null. + * + * Return value + * New wordlist node which is the start of the list + */ +wordlist_l *wll_cons( + size_t n_elem_word_alloc, size_t n_char_word, const char *p_word, + unsigned int opt, wordlist_l *tail) +{ + /* Create a new node and link with the existing wordlist */ + wordlist_l *w = TMALLOC(wordlist_l, 1); + w->wl.wl_next = (wordlist *) tail; + w->wl.wl_prev = (wordlist *) NULL; + + w->n_char_word = n_char_word; + w->n_elem_word_alloc = n_elem_word_alloc; + + if (opt & OPT_WLL_COPY_ALL) { + char *p_dst = w->wl.wl_word = TMALLOC(char, n_elem_word_alloc); + (void) memcpy(p_dst, p_word, n_char_word); + p_dst += n_char_word; + *p_dst = '\0'; + } + else { + w->wl.wl_word = (char *) p_word; + } + + /* Link to front of rest of nodes, if present */ + if (tail) { + /* The new word goes in the front */ + tail->wl.wl_prev = (wordlist *) w; + } + + return w; +} /* end of function wl_cons */ + + + +/* This function appends wl_to_append to the end of wl_dst and returns the + * start of the combined wordlist */ +static wordlist_l *wll_append(wordlist_l *wl_dst, wordlist_l *wl_to_append) +{ + /* Handle degenerate cases where both of the input wordlists + * are not non-NULL */ + if (wl_dst == (wordlist_l *) NULL) { + return wl_to_append; + } + if (wl_to_append == (wordlist_l *) NULL) { + return wl_dst; + } + + /* Locate last node of wl_dst */ + { + wordlist_l *wl; + for (wl = wl_dst; wl->wl.wl_next; + wl = (wordlist_l *) wl->wl.wl_next) { + ; + } + + /* Link nwl to end */ + wl->wl.wl_next = (wordlist *) wl_to_append; + wl_to_append->wl.wl_prev = (wordlist *) wl; + } + + return wl_dst; /* Return combined wordlist */ +} /* end of function wll_append */ + + + +/* This function appends word data, of the wordlist_l node "to_append" to + * the existing values at wordlist_l node "dst". + */ +void wll_append_to_node(wordlist_l *dst, const wordlist_l *to_append) +{ + /* Get sizes */ + const size_t n_old = dst->n_char_word; + const size_t n_new = to_append->n_char_word; + const size_t n_total = n_old + n_new; + const size_t n_elem_needed = n_total + 1; + + /* Resize if needed */ + if (dst->n_elem_word_alloc < n_elem_needed) { + const size_t n_elem_alloc = 2 * n_elem_needed; + dst->wl.wl_word = TREALLOC( + char, dst->wl.wl_word, n_elem_alloc); + dst->n_elem_word_alloc = n_elem_alloc; + } + + /* Do append */ + { + char *p_dst = dst->wl.wl_word + n_old; + char * const p_src = to_append->wl.wl_word; + (void) memcpy(p_dst, p_src, n_new); + p_dst += n_new; + *p_dst = '\0'; + } + dst->n_char_word = n_total; +} /* end of function wll_append_to_node */ + + + +/* This function modifies word data in a word list node by building a new word + * from the arrays supplied. These arrays may contain pieces of the word + * being modified, with overlap and duplication of intervals allowed. Null + * terminiations are not required on the input data. + * + * Parameters + * wl_node: wordlist node being modified. Cannot be NULL. + * n_input: Number of inputs + * p_n_char_word: Array of length n_input of lengths of input strings, + excluding trailing nulls + * pp_word: Array of pointers to input character data. An entry may be NULL + * iff the corrseponding value of p_n_char_word is 0. + */ +static void wl_modify_word(wordlist *wl_node, unsigned int n_input, + const size_t *p_n_char_word, char **pp_word) +{ + /* Find the number of chars of word data */ + size_t n_char_word_new = 0; + { /* have array */ + /* Accumulate count of chars */ + const size_t *p_n_char_word_cur = p_n_char_word; + const size_t * const p_n_char_word_end = p_n_char_word + n_input; + for ( ; p_n_char_word_cur != p_n_char_word_end; + ++p_n_char_word_cur) { + n_char_word_new += *p_n_char_word_cur; + } + } + + /* New allocation */ + char *p_word_new; + + /* Process the segments. */ + { /* no escapes */ + /* + 1 for null after word */ + const size_t n_byte_alloc = n_char_word_new + 1; + + /* New allocation */ + p_word_new = TMALLOC(char, n_byte_alloc); + + /* New word. Build from input pieces */ + { + const size_t *p_n_char_word_cur = p_n_char_word; + const size_t * const p_n_char_word_end = p_n_char_word + n_input; + char **pp_word_cur = pp_word; + char *p_dst = p_word_new; + for ( ; p_n_char_word_cur < p_n_char_word_end; + ++p_n_char_word_cur, ++pp_word_cur) { + const size_t n_char_word_cur = *p_n_char_word_cur; + (void) memcpy(p_dst, *pp_word_cur, n_char_word_cur); + p_dst += n_char_word_cur; + } + *p_dst = '\0'; + } + } + + + /* Free old and assign new */ + txfree(wl_node->wl_word); + wl_node->wl_word = p_word_new; +} /* end of function wl_modify_word */ + + + + + + diff --git a/src/frontend/streams.c b/src/frontend/streams.c index 730d5658b..e49a91a9b 100644 --- a/src/frontend/streams.c +++ b/src/frontend/streams.c @@ -1,6 +1,8 @@ /************* * streams.c ************/ +#include +#include #include "ngspice/ngspice.h" #include "ngspice/wordlist.h" diff --git a/src/include/ngspice/cpstd.h b/src/include/ngspice/cpstd.h index b6498de39..aab9409f9 100644 --- a/src/include/ngspice/cpstd.h +++ b/src/include/ngspice/cpstd.h @@ -26,7 +26,7 @@ Author: 1986 Wayne A. Christopher, U. C. Berkeley CAD Group /* Externs defined in std.c */ -extern char *tildexpand(char *string); +extern char *tildexpand(const char *string); extern void printnum(char *buf, double num); int printnum_ds(DSTRING *p_ds, double num); extern int cp_numdgt; diff --git a/src/misc/tilde.c b/src/misc/tilde.c index db0ef8bd4..017330d84 100644 --- a/src/misc/tilde.c +++ b/src/misc/tilde.c @@ -3,20 +3,24 @@ Copyright 1991 Regents of the University of California. All rights reserved. Modified: 2002 R. Oktas, **********/ +#include "ngspice/defines.h" #include "ngspice/ngspice.h" -#include "tilde.h" #include "ngspice/stringskip.h" +#include "tilde.h" #ifdef HAVE_PWD_H #include #endif -#if defined(__MINGW32__) || defined(_MSC_VER) +#ifdef _WIN32 #undef BOOLEAN #include /* win32 functions */ #include "shlobj.h" /* SHGetFolderPath */ #endif +static inline int copy_home_to_buf(size_t n_byte_dst, char **p_dst, + const char *src); + /* XXX To prevent a name collision with `readline's `tilde_expand', the original name: `tilde_expand' has changed to `tildexpand'. This situation naturally brings to mind that `tilde_expand' could be used @@ -26,83 +30,196 @@ Modified: 2002 R. Oktas, reason why it should be replaced: eg. it returns NULL which should not behave this way, IMHO. Anyway... Don't care for the moment, may be in the future. -- ro */ - - -char * -tildexpand(char *string) +char *tildexpand(const char *string) { -#ifdef HAVE_PWD_H - char buf[BSIZE_SP]; - char *k, c; -#endif -#if defined(__MINGW32__) || defined(_MSC_VER) - char buf2[BSIZE_SP]; -#endif - char *result = NULL; - - if (!string) - return NULL; - - string = skip_ws(string); - - if (*string != '~') - return copy(string); - - string += 1; - - if (!*string || *string == '/') { - /* First try the environment setting. May also make life easier - for non-unix platforms, eg. MS-DOS. -- ro */ - result = getenv("HOME"); -#ifdef HAVE_PWD_H - /* Can't find a result from the environment, let's try - the other stuff. -- ro */ - if (!result) { - struct passwd *pw; - pw = getpwuid(getuid()); - if (pw) - result = pw->pw_dir; - *buf = '\0'; - } - - } else { - struct passwd *pw; - k = buf; - while ((c = *string) && c != '/') - *k++ = c, string++; - *k = '\0'; - pw = getpwnam(buf); - if (pw) - result = pw->pw_dir; -#endif + /* If no string passed, return NULL */ + if (!string) { + return NULL; } - if (result) { -#ifdef HAVE_PWD_H - strcpy(buf, result); - if (*string) - strcat(buf, string); - return copy(buf); - } else - return NULL; + string = skip_ws(string); /* step past leading whitespace */ + + /* If the string does not begin with a tilde, there is no ~ to expand */ + if (*string != '~') { + return copy(string); + } + + ++string; /* step past tilde */ + + /* Test for home of current user */ + if (*string == '\0' || *string == DIR_TERM) { + char *sz_home; + const int n_char_home = get_local_home(0, &sz_home); + if (n_char_home < 0) { + return copy(string); /* Strip the ~ and return the rest */ + } + const size_t n_char_rest = strlen(string); + TREALLOC(char, sz_home, (size_t) n_char_home + n_char_rest + 1); + strcpy(sz_home + n_char_home, string); + return sz_home; + } + +#ifdef HAVE_PWD_H + /* ~bob -- Get name of user and find home for that user */ + { + char buf_fixed[100]; + char *buf = buf_fixed; + const char * const usr_start = string; + char c; + while ((c = *string) && c != '/') { + string++; + } + const char * const usr_end = string; + const size_t n_char_usr = usr_end - usr_start; + const size_t n_byte_usr = n_char_usr + 1; + if (n_byte_usr > sizeof buf_fixed) { + buf = TMALLOC(char, n_byte_usr); + } + (void) memcpy(buf, usr_start, n_char_usr); + buf[n_char_usr] = '\0'; + + + char *sz_home; + const int n_char_home = get_usr_home(buf, 0, &sz_home); + if (buf != buf_fixed) { /* free allocated buffer for user name */ + txfree(buf); + } + if (n_char_home < 0) { + return copy(usr_start); /* Strip the ~ and return the rest */ + } + const size_t n_char_rest = strlen(string); + TREALLOC(char, sz_home, (size_t) n_char_home + n_char_rest + 1); + strcpy(sz_home + n_char_home, string); + return sz_home; + } #else - /* Emulate the old behavior to prevent side effects. -- ro */ - return copy(string); - } -#if defined(__MINGW32__) || defined(_MSC_VER) - else if(SUCCEEDED(SHGetFolderPath(NULL, - CSIDL_PERSONAL, - NULL, - 0, - buf2))) - { - if (*string) - strcat(buf2, string); - return copy(buf2); - } + /* ~abc is meaningless */ + return copy(string); /* Again strip ~ and return rest */ #endif - return NULL; -#endif -} +} /* end of function tildexpand */ + + + + +/* Get value of "HOME" for the current user and copy to *p_buf if + * the value is less than n_byte_buf characters long. Otherwise + * allocate a buffer of the minimum required size. + * + * Return values + * >0: Number of characters copied to *p_buf, excluding the trailing null + * -1: A value for HOME could not be obtained. + * + * Remarks: + * This function does not free any allocation at *p_buf, allowing a + * fixed buffer to be passed to it. + */ +int get_local_home(size_t n_byte_buf, char **p_buf) +{ + char *sz_home = (char *) NULL; +#ifdef _WIN32 + char buf_sh_path[MAX_PATH]; +#endif + + do { + /* First thing to try is an environment variable HOME */ + if ((sz_home = getenv("HOME")) != (char *) NULL) { + break; + } + +#if defined(_WIN32) + /* If Windows, try an env var USERPROFILE next */ + if ((sz_home = getenv("USERPROFILE")) != (char *) NULL) { + break; + } + + /* For Windows, the folder path CSIDL_PERSONAL is tried next */ + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, + buf_sh_path))) { + sz_home = buf_sh_path; + break; + } +#elif defined(HAVE_PWD_H) /* _WIN32 and HAVE_PWD_H are mutually exclusive */ + /* Get home information for the current user */ + { + struct passwd *pw; + pw = getpwuid(getuid()); + if (pw) { + sz_home = pw->pw_dir; + } + } +#endif + } while (0); + + if (sz_home == (char *) NULL) { /* did not find a HOME value */ + return -1; + } + + /* Copy home value to buffer */ + return copy_home_to_buf(n_byte_buf, p_buf, sz_home); +} /* end of function get_local_home */ + + + +#ifdef HAVE_PWD_H +/* Get value of "HOME" for usr and copy to *p_buf if + * the value is less than n_byte_buf characters long. Otherwise + * allocate a buffer of the minimum required size. + * + * Return values + * >0: Number of characters copied to *pp_buf, excluding the trailing null + * -1: A value for HOME could not be obtained. + * + * Remarks: + * This function does not free any allocation at *p_buf, allowing a + * fixed buffer to be passed to it. + */ +int get_usr_home(const char *usr, size_t n_byte_buf, char **p_buf) +{ + struct passwd * const pw = getpwnam(usr); + if (pw) { + /* Copy home value to buffer */ + return copy_home_to_buf(n_byte_buf, p_buf, pw->pw_dir); + } + + return -1; +} /* end of function get_usr_home */ +#endif /* HAVE_PWD_H */ + + + +/* This function copies home value src to the buffer at *p_dst, allocating + * a larger buffer if required. + * + * Parameters + * n_byte_dst: Size of supplied destination buffer + * p_dst: Address containing address of supplied buffer on input. May be + * given the address of a larger buffer allocation if the input buffer + * is too small. + * src: Address of HOME value + * + * Return values + * number of characters copied excluding terminating null + * + * Remarks: + * This function does not free any allocation at *p_dst, allowing a + * fixed buffer to be passed to it. + */ +static inline int copy_home_to_buf(size_t n_byte_dst, char **p_dst, + const char *src) +{ + const size_t n_char_src = strlen(src); /* Size of HOME value */ + const size_t n_byte_src = n_char_src + 1; + + /* Allocate dst if input buffer too small */ + if (n_byte_src > n_byte_dst) { /* too big */ + *p_dst = TMALLOC(char, n_byte_src); + } + + (void) memcpy(*p_dst, src, n_byte_src); + + return (int) n_char_src; +} /* end of function copy_home_to_buf */ + + diff --git a/src/misc/tilde.h b/src/misc/tilde.h index 19dd519af..7aea0ef29 100644 --- a/src/misc/tilde.h +++ b/src/misc/tilde.h @@ -6,7 +6,13 @@ #ifndef ngspice_TILDE_H #define ngspice_TILDE_H -char * tildexpand(char *string); +char * tildexpand(const char *string); + +int get_local_home(size_t n_byte_buf, char **p_buf); +#ifdef HAVE_PWD_H +int get_usr_home(const char *usr, size_t n_byte_buf, char **p_buf); #endif + +#endif /* include guard */