/* This file is in the public domain. */ /* * While this file is not entirely concerned with pathname completion, * that's easily the bulk of it, because that's the most complicated * part. * * Braces and tildes complicate things here. * * If no braces, no brace problem. * * Expansion operates in two ways. If the path being expanded matches * at least one name, then it takes the part after the specified * string, for each name matched, and inserts the longest initial * substring all those additional parts have in common. If the path * being expanded does not match at least one name, then, effectively, * it strips things from the end until it does, but never stripping * back beyond a slash or a piece of brace syntax (a brace or an * alternative-separating comma). After this is done, if it stripped * back far enough that there's a match, completion is performed * again to expand. So, for example, if there is a file "eleven" but * nothing else matching "el*", completing "eltrit" will erase the * "trit" and replace it with "even". (Whenever this stripping * backwards is done, a beep is emitted, whether or not a completion * results from the second expansion attempt.) * * For each unclosed open-brace, components other than the current * component are ignored for purposes of expansion, but are preserved; * the effect is to expand just the currently-being-typed alternative. * However, as mentioned above, stripping backwards never strips back * beyond the current alternative's beginning; for example, completing * "foo{bar,baz" when there is a file "forth" but nothing else * matching "fo*" results in "foo{bar," rather than deleting back * beyond the {. * * In general, globbing syntax (balanced braces, *, ?, []) is left * alone. However, if there is at least one match, some replacements * may be done. If a *, ?, or [] always matches the same thing, it is * replaced by what it matches, and brace alternatives that result in * no matches are removed. A brace that has only one alternative * (after removal of any non-matching alternatives) has the braces * removed as well. * * After a tilde, expansion actually expands usernames, not filenames * (unless there's a slash, of course). This interacts properly with * braces, so that completing, for example, {foo,~bar}x will look for * files beginning "foox" and usernames beginning "barx"; in each * case, the part after the x is used as described above to generate * the string to expand to. * * There is some ambiguity as to what wildcards match. For example, * consider "foo*[ab]*[xy]*bar" matching against "foo1a2x3b4y5bar". * There are two possible matches: * foo * [ab] * [xy] * bar * --- ----- ---- - ---- ----- --- * foo 1 a 2 x 3b4y5 bar * foo 1a2x3 b 4 y 5 bar * While this doesn't always matter from the point of view of *what* * to replace patterns with (both the matches above match the same * string), it does affect *whether* to replace wildcards. For * example, consider completing "/mnt/foo*[ab]*[xy]*bar/0" when * /mnt/foo1a2x3b4y5bar/01 and /mnt/foo1a2x3b4y5bar/02 exist. If the * matching uses one of the matches above for the 01 file and the * other for the 02 file, then none of the wildcards always matches * the same thing and thus none of them will be replaced, though they * really ought to be. To resolve this, matching is always done * "grudgingly" - a wildcard matches as few characters as possible, * consistent with an overall match, with earlier wildcards getting * minimzation priority over later ones (thus, in the above example, * the first of the two matches is the one that will be taken). * * This code supports ~, { }, [ ], *, and ?, but not ` ` - a ` is taken * as an ordinary character. Globbing metacharacters can be quoted * with ' ', " ", or \, though the code to actually do so is in ecl.c, * not here (our input is a string of QCHs). * * Completion proceeds in multiple phases, fairly well separated. * First, the input string is parsed into literal text portions, * character classes, wildcards (* and ?), and braced alternatives, * possibly including one or more nested unclosed braces. A BTEXT is * such a string: a sequence of PCs (pieces), each of which can be one * of the above (there are some additional types of PC which occur * later; they cannot be generated at this stage). Then, if the input * string does not end with a *, one is appended, but it is marked as * such so it can be removed later. (This * simplifies the pathname * walks by providing a wildcard to match pathname portions which are * the string we're expanding into.) The strip-to point is then * located; this is the place referred to above, back before which * failures will not delete - immediately after the last slash, brace, * or brace-alternative-separating comma, or, if the string begins * with an unquoted tilde and doesn't have any of the others, that * tilde. A distinctive `marker' PC is inserted at this point * (splitting a plain-text PC if necessary). Then a recursive walk * enumerates the alternatives for braces (closed braces, that is; * unclosed braces always use their last alternative). Each of the * resulting strings is then broken into slash-separated components; * these are walked from left to right, matching wildcard syntax ([], * *, ?) in the process. If the entire string is matched (including * the trailing * which was added if none was already present), then * the strings matched by the wildcards are recorded for possible * wildcard replacement, and which brace alternatives led to a * successful match are also recorded; if the match failed at some * point, then the point of failure (in terms of PCs) is recorded, * provided it's after the marker PC. When these recursive walks are * done, then either of two things happens, depending on whether there * were any successful matches. If there were, then the longest * common substring of the strings that matched the trailing * is * marked as being that *'s match string. If not, everything after * the earliest last-match point - or everything past the marker, if * nothing matched past the marker - is discarded, and the whole * process (except for the initial parsing) is repeated - but only * once. Then, the resulting BTEXT is re-processed back into input * string syntax; this is the point at which wildcards are replaced if * they matched only one thing and braced alternatives simplified. * (However, a brace none of whose alternatives matched is left * untouched; this prevents a brace following something that can never * match from being destroyed; an example might be * /hoem/user/{dir1,dir2}/foo where "hoem" is a typo for "home".) * This is also when a distinction is drawn between an ordinary * and * the synthetic * appended if the string does not end with one, * though this matters only for complete-to-braces operations which * match nothing. * * ecl_path_follow_symlink and ecl_path_harden have readlink(2) buffer * size issues. The issue is that readlink(2) does not give us the * desired buffer size if the link-to string is too long (an annoying * interface design botch), so we have to just keep growing the buffer * until we succeed. * * [}}} here to pacfy editor brace matching.] */ #include #include #include #include #include #include #include #include #include "ecl-path.h" #include "ecl-glue.h" #include "ecl-malloc.h" #define MAX_REPORTED_ERRS 10 /* * ecl_path_buf and ecl_path_len are exported interfaces; they are how * paths are passed down into and up out of this file. pbspc is * private to this file; it is the amount of space ecl_path_buf * actually points to (ecl_path_len is how much is currently used). */ QCH *ecl_path_buf; int ecl_path_len; static int pbspc; /* * Set nonzero to generate debugging output. */ static int debug; /* * Called from the outside. Ensures ecl_path_buf points to at least n * QCHs of space. */ void ecl_path_space(int n) { if (pbspc < n) ecl_path_buf = realloc(ecl_path_buf,(pbspc=n)*sizeof(*ecl_path_buf)); } /* * Returns true iff the QCH is an unquoted instance of the character. */ static int qchis(QCH q, unsigned char c) { return((q.c == c) && !(q.f & QCHF_QUOTE)); } /* * Generate debugging output, if debug is turned on. */ static void dbg(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void dbg(const char *fmt, ...) { va_list ap; if (! debug) return; va_start(ap,fmt); vfprintf(eclerr(),fmt,ap); va_end(ap); } /* * Metasyntax characters. These can be changed without breaking the * code per se, but user expectations dictate that they must match * what other globbing code does. (SLASH also must match the pathname * separator character used by the filesystem API, or some truly * bizarre things will happen.) */ #define OPENBRACE '{' #define CLOSEBRACE '}' #define COMMA ',' #define OPENCLASS '[' #define CLOSECLASS ']' #define NEGCLASS '!' #define CCRANGE '-' #define STAR '*' #define QUES '?' #define TILDE '~' #define SLASH '/' /* * Typedefs for struct types. The structs themselves carry the * documentation. */ typedef struct btext BTEXT; typedef struct loclen LOCLEN; typedef struct pc PC; typedef struct brace BRACE; typedef struct cclass CCLASS; typedef struct wild WILD; typedef struct sla SLA; typedef struct matchres MATCHRES; typedef struct strlist STRLIST; typedef struct onepathstate ONEPATHSTATE; /* * A list of strings, to be sorted and then processed. Since this is * used for user homedirs as well as pathname components (where we * want to sort on username but do something with the homedir), it * actually contains two strings. Sorting sorts on s1. */ struct strlist { STRLIST *link; char *s1; char *s2; } ; /* * Used to work out the earliest match failure point, for failure * cases. pcx indexes into the PC vector, with pcx==1 corresponding * to the PC immediately after the mark, or pcx set to -1 indicates * that no failued matches after the mark have occurred yet. pcl * indicates how much of the pcx-indicated PC is to be preserved. For * [], this is not used (it's always set to 0); for plain text and * strings of ?s, it's the number of characters to preserve. (A * can * never fail to match, so this never points to a * PC.) */ struct matchres { int pcx; int pcl; } ; /* * SLA = String/Length/Alloc. Used for expandable strings in various * places. (ecl_path_buf, above, and some other things elsewhere are * conceptually the same thing, but use types other than unsigned char * as the member type, so they can't use this struct; C isn't * polymorphic enough to handle that.) */ struct sla { unsigned char *b; int l; int a; } ; /* * Represents a [] character class. closed says whether its ] has been * seen (this currently is not used; it's there against future plans * to handle expanding unclosed character classes, akin to how * unclosed braces are handled.) neg says whether a ! prefix was * present. map[] holds 256 bits, one for each character possibly * included; CCSET, CCCLR, and CCTST access the map. */ struct cclass { unsigned int closed : 1; unsigned int neg : 1; unsigned char map[32]; #define CCSET(cc,bit) ((cc)->map[(bit)/8] |= 1 << ((bit)&7)) #define CCCLR(cc,bit) ((cc)->map[(bit)/8] &=~ (1 << ((bit)&7))) #define CCTST(cc,bit) ((cc)->map[(bit)/8] & (1 << ((bit)&7))) } ; /* * A location-and-length. The only places these are used are in PCs, * below, where they describe fragments of the string in ecl_path_buf. * Of course, at is the location and len the length. */ struct loclen { int at; int len; } ; /* * A set of braced alternative. closed says whether the closing brace * has been seen. nalts is the number of alternatives; alts is a * vector of pointers to BTEXTs for the altneratives. altused is a * vector of booleans saying which alternatives have been used. * curalt is used during the recursive walks; it records which * alternative is current, so that when a match is found, alternatives * which got used can be flagged in altused. * * alts and altused always point to nalts elements. */ struct brace { unsigned int closed : 1; unsigned int alter : 1; int nalts; BTEXT **alts; char *altused; int curalt; } ; /* * A piece of a pattern. * * A PCTYPE is the type of a piece; * PCT_UNSET * Invalid in most contexts; this occurs only briefly * during creation. * PCT_PLAIN * A string of non-special characters from the input * string. * PCT_BRACE * A brace-bracketed set of alternatives. * PCT_CCLASS * A [] character class. * PCT_QUES * One or more consecutive ? wildcards. * PCT_STAR * A * wildcard. * PCT_MARK * The marker used to record the strip-to point. * PCT_TEXT * A string of non-special characters not coming from the * input string. (This is used when expanding to a braced * list of possibilities.) * * A PCMATCH records what a wildcard has matched; * PCM_NONE * Hasn't matched anything yet. * PCM_ONE * It's matched only one distinct string. * PCM_MULT * It's matched at least two different strings. * * In the PC itself, type is of course the type. src describes the * characters in the input string that gave rise to this PC. The * transparent union is a discriminated union based on the type: * PCT_UNSET * None. * PCT_PLAIN * plain, describing the characters. * PCT_BRACE * brace, holding the data on the alternatives. * PCT_CCLASS * cclass, describing the class. * PCT_QUES * ques, recording how many ?s occurred. * PCT_STAR * star, 0 for an ordinary * and 1 for a synthetic *. * PCT_MARK * None. * PCT_TEXT * text, holding the text. * match records what match success this PC has had (not used except * for CCLASS, QUES, and STAR). cur is what this PC is currently * matching; saved is what it matched in the successful match that * moved this PC from PCM_NONE to PCM_ONE; it's usd only if PCM_ONE. */ typedef enum { PCT_UNSET = 1, PCT_PLAIN, PCT_BRACE, PCT_CCLASS, PCT_QUES, PCT_STAR, PCT_MARK, PCT_TEXT } PCTYPE; typedef enum { PCM_NONE = 1, PCM_ONE, PCM_MULT } PCMATCH; struct pc { PCTYPE type; LOCLEN src; union { CCLASS cclass; LOCLEN plain; BRACE brace; int ques; SLA text; int star; } ; PCMATCH match; SLA cur; SLA saved; } ; /* * A string of PCs. allplain is an optimization; it allows identifying * BTEXTs that include no globbing characters, which allows skipping * directory reads for fixed pathname components. npcs and pcs hold * the array of PC pointers and its size. * * BTEXTs are, in a few places, (ab)used as simple lists of PCs rather * than the input wildcard strings they conceptually are. They are * called "container"s in such uses. */ struct btext { unsigned int allplain : 1; int npcs; PC **pcs; } ; struct onepathstate { SLA p; char *tildex; int txl; char *tildeu; int tul; } ; /* * File-private variables. */ /* The parsed input string. */ static BTEXT *mainbt; /* Index into ecl_path_buf during parsing. */ static int x; /* Vector of components for path walks. This is conceptually a SLA of BTEXT pointers. */ static BTEXT **comps; static int ncomps; static int acomps; /* True iff the comps vector is /-prefixed. */ static int rooted; /* The accumulated OS-API path when doing path walks. */ static SLA path; /* True if path begins with the result of a ~ expansion. */ static int path_tilde; /* The username and homedir of the ~ expansion path begins with, if path_tilde is true. (If !path_tilde, these are meaningless.) */ static SLA path_user; static SLA path_home; /* Container holding PCs we want to free when done path-walking. */ static BTEXT *tofree; /* True iff we've had any successful matches. (Not used for expand-to-braces; for which bracebt is used.) */ static int anymatches; /* The only MATCHRES allocated. Records failure status. */ static MATCHRES failres; /* Stores the longest common substring of successful matche suffixes. */ static SLA succres; /* The PC representing the (possibly synthetic) trailing *. */ static PC *finalstar; /* Count of errors printed. */ static int errors; /* Number of completions found. Used for list-completions. */ static int n_found; /* Container holding PCT_TEXT PCs in expand-to-braces. */ static BTEXT *bracebt; /* Function to call when walk_path finds a match. */ void (*path_walk_success)(void); /* * Initialize a newly-allocated SLA. */ static void sla_init(SLA *s) { s->b = 0; s->a = 0; } /* * Free what an SLA points to. The SLA itself is never freed. */ static void sla_free(SLA *s) { free(s->b); } /* * Enlarge an SLA if necessary, so that it has room for at least nb * bytes. */ static void sla_room(SLA *s, int nb) { if (nb > s->a) s->b = realloc(s->b,s->a=nb); } /* * Called from outside, once, during startup, to initialize stuff. */ void ecl_path_init(void) { ecl_path_buf = 0; pbspc = 0; comps = 0; acomps = 0; sla_init(&path); sla_init(&path_user); sla_init(&path_home); } /* * Create and return a new, empty BTEXT. */ static BTEXT *new_btext(void) { BTEXT *bt; bt = malloc(sizeof(BTEXT)); bt->allplain = 1; bt->npcs = 0; bt->pcs = 0; return(bt); } /* * Create and return a new PC. It's created of type PCT_UNSET; the * caller is expected to set the type and, for types that have them, * the discriminated union data. */ static PC *new_pc(void) { PC *pc; pc = malloc(sizeof(PC)); pc->type = PCT_UNSET; pc->src.at = 0; pc->src.len = 0; sla_init(&pc->cur); sla_init(&pc->saved); pc->match = PCM_NONE; return(pc); } /* * Terminator detection function for parsing the main BTEXT. This * parse wants to swallow everything available, so we never indicate * termination. */ static int btext_term_main(void) { return(0); } /* * Terminator detection function for parsing a braced alternative's * BTEXT. Indicate termination on an unquoted comma or close brace. */ static int btext_term_inner(void) { return( qchis(ecl_path_buf[x],COMMA) || qchis(ecl_path_buf[x],CLOSEBRACE) ); } /* * Add a PC to a BTEXT. at says where to add it; if at is >=0, then * it's added such that bt->pcs[at] is pc, with everything later * shifted over to make room. If at is <0, pc is added at the end. * This maintains the BTEXT's allplain flag. */ static void add_pc_to_btext(PC *pc, BTEXT *bt, int at) { if (at < 0) at = bt->npcs; bt->pcs = realloc(bt->pcs,(bt->npcs+1)*sizeof(PC *)); if (at < bt->npcs) bcopy(bt->pcs+at,bt->pcs+at+1,(bt->npcs-at)*sizeof(PC *)); bt->pcs[at] = pc; bt->npcs ++; switch (pc->type) { case PCT_PLAIN: case PCT_MARK: break; default: bt->allplain = 0; break; } } /* * Parse and return a BTEXT. (*term) is a termination indicator test, * used to decide when to stop consuming input and return. * * The only thing of much note here is that {} is considered two plain * characters rather than a braced alternative with one alternative, * that one having zero length. This is mostly for find(1) and is how * the rest of csh globbing works. */ static BTEXT *parse_btext(int (*term)(void)) { BTEXT *bt; PC *pc; QCH ch; bt = new_btext(); while (1) { if (x >= ecl_path_len) break; if ((*term)()) break; pc = new_pc(); pc->src.at = x; if (qchis(ecl_path_buf[x],OPENBRACE)) { BTEXT *sub; x ++; pc->type = PCT_BRACE; pc->brace.alter = 0; pc->brace.nalts = 0; pc->brace.alts = 0; pc->brace.altused = 0; while (1) { sub = parse_btext(&btext_term_inner); pc->brace.alts = realloc( pc->brace.alts, (pc->brace.nalts+1)*sizeof(BTEXT *) ); pc->brace.alts[pc->brace.nalts++] = sub; if (x >= ecl_path_len) { pc->brace.closed = 0; break; } if (qchis(ecl_path_buf[x],CLOSEBRACE)) { pc->brace.closed = 1; x ++; break; } if (qchis(ecl_path_buf[x],COMMA)) { x ++; continue; } /* recursive call returned, but why?! */ abort(); } pc->brace.altused = malloc(pc->brace.nalts); bzero(pc->brace.altused,pc->brace.nalts); } else if (qchis(ecl_path_buf[x],OPENCLASS)) { pc->type = PCT_CCLASS; x ++; if ((x < ecl_path_len) && qchis(ecl_path_buf[x],NEGCLASS)) { pc->cclass.neg = 1; x ++; } else { pc->cclass.neg = 0; } bzero(&pc->cclass.map,sizeof(pc->cclass.map)); if ((x < ecl_path_len) && qchis(ecl_path_buf[x],CLOSECLASS)) { ecl_path_buf[x].f |= QCHF_QUOTE; } while (1) { if (x >= ecl_path_len) { pc->cclass.closed = 0; break; } if (qchis(ecl_path_buf[x],CLOSECLASS)) { pc->cclass.closed = 1; x ++; break; } if ( (x+2 < ecl_path_len) && qchis(ecl_path_buf[x+1],CCRANGE) && !qchis(ecl_path_buf[x+1],CLOSECLASS) && (ecl_path_buf[x].c <= ecl_path_buf[x+2].c) ) { int i; int j; j = ecl_path_buf[x+2].c; for (i=ecl_path_buf[x].c;i<=j;i++) CCSET(&pc->cclass,i); x += 3; } else { CCSET(&pc->cclass,ecl_path_buf[x].c); x ++; } } } else if (qchis(ecl_path_buf[x],STAR)) { pc->type = PCT_STAR; pc->star = 0; x ++; } else if (qchis(ecl_path_buf[x],QUES)) { pc->type = PCT_QUES; pc->ques = 0; do { x ++; pc->ques ++; } while ( (x < ecl_path_len) && qchis(ecl_path_buf[x],QUES) ); } else { while (1) { if (x >= ecl_path_len) break; if ((*term)()) break; ch = ecl_path_buf[x]; if (! (ch.f & QCHF_QUOTE)) { switch (ch.c) { case OPENBRACE: if ((x+1 < ecl_path_len) && qchis(ecl_path_buf[x+1],CLOSEBRACE)) { x ++; } else { goto break_plain; } break; case OPENCLASS: case STAR: case QUES: goto break_plain; } } x ++; } break_plain:; pc->type = PCT_PLAIN; pc->plain.at = pc->src.at; pc->plain.len = x - pc->src.at; } pc->src.len = x - pc->src.at; add_pc_to_btext(pc,bt,-1); } return(bt); } /* * Dump out a BTEXT for debugging. Very straightforward, possibly * except for the way indentation is handled recursively. */ #define INC 2 static void dump_btext(BTEXT *b, int indent) { int i; int j; PC *pc; if (! debug) return; dbg("%*sbtext %p: npcs %d\n",indent,"",(void *)b,b->npcs); for (i=0;inpcs;i++) { pc = b->pcs[i]; dbg("%*spc %p = ",indent+INC,"",(void *)pc); switch (pc->type) { case PCT_PLAIN: dbg("PLAIN at %d len %d: ",pc->plain.at,pc->plain.len); for (j=0;jplain.len;j++) dbg("%c",ecl_path_buf[pc->plain.at+j].c); dbg("\n"); break; case PCT_BRACE: dbg("BRACE closed %d alter %d nalts %d\n",pc->brace.closed,pc->brace.alter,pc->brace.nalts); for (j=0;jbrace.nalts;j++) { dump_btext(pc->brace.alts[j],indent+INC+INC); } break; case PCT_CCLASS: dbg("CCLASS closed %d neg %d\n",pc->cclass.closed,pc->cclass.neg); dbg("%*smap = ",indent+INC+INC,""); for (j=0;j<256;j++) if (CCTST(&pc->cclass,j)) { if ((j <= 32) || ((j >= 127) && (j <= 160))) { dbg("\\%03o",j); } else if (j == '\\') { dbg("\\\\"); } else { putc(j,eclerr()); } } dbg("\n"); break; case PCT_STAR: dbg("STAR (%d)\n",pc->star); break; case PCT_QUES: dbg("QUES %d\n",pc->ques); break; case PCT_MARK: dbg("MARK\n"); break; case PCT_TEXT: dbg("TEXT a=%d l=%d s=%.*s\n",pc->text.a,pc->text.l,pc->text.l,pc->text.b); break; default: dbg("?%d\n",(int)pc->type); break; } } } #undef INC /* * free_btext and free_pc call one another recursively. It doesn't * much matter which one we declare forward; free_btext was marginally * easier because I happened to write free_pc earlier in the file than * I did free_btext. */ static void free_btext(BTEXT *); /* forward */ /* * Free a PC and what it points to. */ static void free_pc(PC *pc) { int i; switch (pc->type) { case PCT_PLAIN: case PCT_CCLASS: case PCT_STAR: case PCT_QUES: case PCT_MARK: break; case PCT_BRACE: for (i=0;ibrace.nalts;i++) { free_btext(pc->brace.alts[i]); } free(pc->brace.alts); free(pc->brace.altused); break; case PCT_TEXT: sla_free(&pc->text); break; default: abort(); break; } sla_free(&pc->cur); sla_free(&pc->saved); free(pc); } /* * Free a BTEXT and what it points to. */ static void free_btext(BTEXT *bt) { int i; for (i=0;inpcs;i++) free_pc(bt->pcs[i]); free(bt->pcs); free(bt); } /* * Free a BTEXT but do *not* free what it points to. This is used when * the BTEXT holds PCs that are already held elsewhere (in which case * freeing them here would free them before we're done with them, * leading to use-after-free bugs, and when the other holder is freed * would produce double-free bugs). */ static void free_just_btext(BTEXT *bt) { free(bt->pcs); free(bt); } /* * Walk down trailing unclosed braces, finding the last alternative * until we find a BTEXT that doesn't end with an unclosed brace. If * beginp is non-nil, we store a boolean through it saying whether the * resulting BTEXT's first PC starts at the beginning of the argument * BTEXT - that is, whether all the unclosed braces are all first (as * well as last) in their BTEXTs. */ static BTEXT *walk_unclosed_braces(BTEXT *b, int *beginp) { PC *p; int begin; begin = 1; while (1) { if (b->npcs < 1) break; p = b->pcs[b->npcs-1]; if (p->type != PCT_BRACE) break; if (p->brace.closed) break; if (p->brace.nalts < 1) abort(); if (b->npcs > 1) begin = 0; b = p->brace.alts[p->brace.nalts-1]; } if (beginp) *beginp = begin; return(b); } /* * Find the strip-to point and insert a PCT_MARK PC there. This may * involve splitting an existing PCT_PLAIN PC. */ static void mark_stripto(void) { int i; int j; int k; PC *pc; BTEXT *bt; int begin; PC *markpc; bt = walk_unclosed_braces(mainbt,&begin); dbg("find_stripto: bt=%p begin=%d\n",(void *)bt,begin); for (i=bt->npcs-1;i>=0;i--) { pc = bt->pcs[i]; if (pc->type == PCT_BRACE) { if (pc->brace.closed) { dbg("find_stripto: closed brace at %d\n",i); break; } abort(); } } dbg("find_stripto: after loop, bt=%p i=%d\n",(void *)bt,i); markpc = new_pc(); markpc->type = PCT_MARK; i ++; if (i >= bt->npcs) { dbg("find_stripto: nothing there, adding %p\n",(void *)markpc); add_pc_to_btext(markpc,bt,-1); } else { for (k=bt->npcs-1;k>=i;k--) { pc = bt->pcs[k]; if (pc->type != PCT_PLAIN) continue; dbg("find_stripto: plain %p at %d\n",(void *)pc,k); for (j=pc->plain.len-1;j>=0;j--) { if ( (ecl_path_buf[pc->plain.at+j].c == SLASH) || ( begin && (j == 0) && (k == 0) && qchis(ecl_path_buf[pc->plain.at+j],TILDE) ) ) { dbg("find_stripto: %c at %d\n",ecl_path_buf[pc->plain.at+j].c,j); if (j == pc->plain.len-1) { dbg("find_stripto: adding %p at %d\n",(void *)markpc,k+1); add_pc_to_btext(markpc,bt,k+1); } else { PC *new; dbg("find_stripto: splitting\n"); new = new_pc(); new->type = PCT_PLAIN; new->plain.at = pc->plain.at; new->plain.len = j + 1; pc->plain.len -= j + 1; pc->plain.at += j + 1; add_pc_to_btext(markpc,bt,k); add_pc_to_btext(new,bt,k); } return; } } } dbg("find_stripto: no slash found, adding %p at %d\n",(void *)markpc,i); add_pc_to_btext(markpc,bt,i); } dbg("find_stripto: done\n"); } /* * Set a character in path, enlarging it if necessary. */ static void path_setc(int inx, unsigned char ch) { sla_room(&path,inx+1); path.b[inx] = ch; } /* * Set the currently-matching string for a wildcard PC. */ static void pc_set_match(PC *p, const unsigned char *txt, int len) { sla_room(&p->cur,len); p->cur.l = len; if (len) bcopy(txt,p->cur.b,len); } /* * Match a component BTEXT (c) against a component string (str). Set * wildcard currently-matching strings as we go in case we succeed; if * we fail and mr is non-nil, record our failure point there. The * only thing of note here is the way recursion is used to handle * * matches - that's why the nested function. */ static int component_match(BTEXT *c, const unsigned char *str, MATCHRES *mr) { int match(int cx, int so, int mx) { PC *p; int i; void setmr(int pcx, int pcl) { if (! mr) return; if (mx < 0) return; pcx -= mx; if (pcx < 0) abort(); if ((pcx > mr->pcx) || ((pcx == mr->pcx) && (pcl > mr->pcl))) { mr->pcx = pcx; mr->pcl = pcl; } } while (1) { if (cx >= c->npcs) { if (! str[so]) return(1); setmr(cx,0); return(0); } p = c->pcs[cx]; switch (p->type) { default: abort(); break; case PCT_PLAIN: for (i=0;iplain.len;i++) { if (str[so+i] != ecl_path_buf[p->plain.at+i].c) { setmr(cx,i); return(0); } } cx ++; so += i; break; case PCT_CCLASS: if (CCTST(&p->cclass,str[so]) ? p->cclass.neg : !p->cclass.neg) { setmr(cx,0); return(0); } pc_set_match(p,str+so,1); cx ++; so ++; break; case PCT_STAR: for (i=0;;i++) { if (match(cx+1,so+i,mx)) { pc_set_match(p,str+so,i); return(1); } if (! str[so+i]) return(0); } break; case PCT_QUES: for (i=0;iques;i++) { if (! str[so+i]) { setmr(cx,i); return(0); } } pc_set_match(p,str+so,i); cx ++; so += i; break; case PCT_MARK: mx = cx; cx ++; break; } } } return(match(0,0,-1)); } /* * Append a string of characters (C char * style) to the path. Do the * stores backwards so we realloc at most once. */ static void append_string_to_path(const char *s) { int l; int i; l = strlen(s); for (i=l-1;i>=0;i--) path_setc(path.l+i,s[i]); path.l += l; } /* * Report an error. */ static void expand_error(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void expand_error(const char *fmt, ...) { va_list ap; if (errors < MAX_REPORTED_ERRS) { if (errors == 0) fprintf(eclerr(),"\n"); va_start(ap,fmt); fprintf(eclerr(),"Error: "); vfprintf(eclerr(),fmt,ap); fprintf(eclerr(),"\n"); va_end(ap); } errors ++; } /* * Push a string pair onto a STRLIST. Either or both of s1 and s2 may * be nil; if they're not, they're copied (since this is called with * non-persistent strings). */ static STRLIST *strlist_push(STRLIST *list, const char *s1, const char *s2) { STRLIST *s; s = malloc(sizeof(STRLIST)); s->s1 = s1 ? strdup(s1) : 0; s->s2 = s2 ? strdup(s2) : 0; s->link = list; return(s); } /* * Sort a STRLIST by increasing s1. A straightforward linked-lsit * mergesort: after checking for recursion grounding, unzip the list * into two, sort the sublists, then merge tconcly and return. */ static STRLIST *strlist_sort(STRLIST *list) { STRLIST *a; STRLIST *b; STRLIST *t; STRLIST **lp; if (!list || !list->link) return(list); a = 0; b = 0; while (list) { t = list; list = t->link; t->link = a; a = b; b = t; } a = strlist_sort(a); b = strlist_sort(b); lp = &list; while (a || b) { if (a && (!b || (strcmp(a->s1,b->s1) < 0))) { t = a; a = a->link; } else { t = b; b = b->link; } *lp = t; lp = &t->link; } *lp = 0; return(list); } /* * Sort a STRLIST, then walk it, calling (*each)() for each string pair * in the list. The strings, and the list, are freed as we go. */ static void strlist_sort_walk(STRLIST *list, void (*each)(const char *, const char *)) { STRLIST *s; list = strlist_sort(list); while (list) { s = list; list = list->link; (*each)(s->s1,s->s2); free(s->s1); free(s->s2); free(s); } } /* * Append a BTEXT full of PCT_PLAIN components to the path. Wish we * could do this backwards the way append_string_to_path does, but * that's complicated enough I decided it's not worth it. */ static void append_btext_to_path(BTEXT *b) { int i; PC *p; int j; for (i=0;inpcs;i++) { p = b->pcs[i]; switch (p->type) { case PCT_PLAIN: for (j=0;jplain.len;j++) { path_setc(path.l++,ecl_path_buf[p->plain.at+j].c); } break; case PCT_MARK: break; default: abort(); break; } } } /* * Walk the filesystem path corresponding to comps/ncomps, globbing as * we go. For each complete match we get, call through the * path_walk_success global. * * recurse_if_dir, matchit, and append_find are just factorings-out of * otherwise-common code. */ static void walk_path(void) { int dotglob; int dotdotdotglob; void more(int compx) { BTEXT *c; DIR *d; struct dirent *e; struct stat stb; int oldl; STRLIST *list; void recurse_if_dir(void) { path_setc(path.l,'\0'); if ( (stat(path.b,&stb) >= 0) && ((stb.st_mode & S_IFMT) == S_IFDIR) ) { more(compx+1); } } int matchit(const char *ms, BTEXT *mc) { return(component_match(mc,ms,(compxnpcs > 0) && (c->pcs[0]->type == PCT_PLAIN) && qchis(ecl_path_buf[c->pcs[0]->plain.at],TILDE) ) { if (c->allplain) { /* No metasyntax; optimize. */ char *user; const char *home; int i; int j; int ulen; int ux; PC *p; ulen = -1; for (i=c->npcs-1;i>=0;i--) ulen += c->pcs[i]->plain.len; user = malloc(ulen+1); ux = 0; for (i=0;inpcs;i++) { p = c->pcs[i]; for (j=(i?0:1);jplain.len;j++) { user[ux++] = ecl_path_buf[p->plain.at+j].c; } } if (ux != ulen) abort(); user[ux] = '\0'; home = ecl_user_homedir(user); if (home) { append_homeof(user,home); } else { if (! user[0]) { expand_error("no homedir"); } else { expand_error("nonexistent user %s",user); } } free(user); } else { /* Have metasyntax; must enumerate usernames. The code is simpler but does much more work. We form a list and then walk it rather than matching as we go along so as to get names sorted. */ BTEXT *b; PC *p; void save_each(const char *user, const char *home) { list = strlist_push(list,user,home); } void use_each(const char *user, const char *home) { if (matchit(user,b)) append_homeof(user,home); } b = new_btext(); b->allplain = c->allplain; b->npcs = c->npcs; b->pcs = malloc(b->npcs*sizeof(PC *)); bcopy(c->pcs,b->pcs,b->npcs*sizeof(PC *)); p = new_pc(); p->type = PCT_PLAIN; p->plain.at = b->pcs[0]->plain.at + 1; p->plain.len = b->pcs[0]->plain.len - 1; b->pcs[0] = p; list = 0; ecl_user_home_scan(&save_each); strlist_sort_walk(list,&use_each); free_pc(p); free_just_btext(b); } } else if (c->allplain) { /* No metacharacters; optimize. */ append_btext_to_path(c); recurse_if_dir(); } else { /* Have metasyntax; must read the directory. We form a list and then walk it rather than matching as we go along so as to get entries in sorted order. Note also the special-case code for leading dots. */ const char *s; int dotbegin; dotbegin = 0; if (c->npcs > 0) { PC *p; p = c->pcs[0]; if ((p->type == PCT_MARK) && (c->npcs > 1)) p = c->pcs[1]; if ( (p->type == PCT_PLAIN) && (p->plain.len > 0) && (ecl_path_buf[p->plain.at].c == '.') ) dotbegin = 1; } if (path.l) { path_setc(path.l,'\0'); s = path.b; } else { s = "."; } d = opendir(s); if (! d) return; list = 0; while ((e = readdir(d))) { if (e->d_fileno == 0) continue; if ( (e->d_name[0] == '.') && ( (!dotglob && !dotbegin) || ( !dotdotdotglob && ( (e->d_name[1] == '\0') || ( (e->d_name[1] == '.') && (e->d_name[2] == '\0') ) ) ) ) ) continue; list = strlist_push(list,&e->d_name[0],0); } closedir(d); strlist_sort_walk(list,({ void use(const char *s, const char *s2 __attribute__((__unused__))) { if (matchit(s,c)) { append_find(s); } } &use; })); } } dotglob = ecl_dotglob(); dotdotdotglob = ecl_dotdotdotglob(); path.l = 0; path_tilde = 0; more(0); } /* * Add a PC to the tofree container, so it'll get freed when we're done * path-walking. */ static void add_to_free(PC *p) { if (! tofree) tofree = new_btext(); add_pc_to_btext(p,tofree,-1); } /* * Convert a list of PCs resulting from brace-walking to a list of * pathname components. This involves splitting PCT_PLAIN PCs at * slashes (usually, at least - no splitting is involved when a slash * occurs as the only character in a PCT_PLAIN PC). * * This function makes sure there is a final zero-length component if * the input ends with a slash. */ static void convert_to_components(int pcl, PC **pcs) { int pcx; BTEXT *curb; PC *pc; int i; int i0; /* * This name is a joke I couldn't quite resist, and liked enough to * leave. "curb" stands for "current BTEXT", but also spells a real * word, which inspired the joke. * * This function is basically just add_pc_to_btext on the curb BTEXT, * except that it skips zero-length PCT_PLAIN PCs, makes sure curb * exists, and logs debugging output. */ void kick_to_curb(PC *p) { if ((p->type == PCT_PLAIN) && (p->plain.len < 1)) return; if (! curb) curb = new_btext(); dbg("kicking %p to the curb\n",(void *)p); add_pc_to_btext(p,curb,-1); } /* * Append a part, but not all, of an existing PCT_PLAIN PC. This is * why tofree exists - so that these transient PCs can be cllected * together and freed when we're done. We just create a new * PCT_PLAIN PC describing the fragment and add it to both curb and * tofree. */ void append_fragment(PC *p, int o0, int o) { PC *p2; if (p->type != PCT_PLAIN) abort(); if (o < o0) abort(); p2 = new_pc(); dbg("append_fragment: creating %p from %p %d-%d\n",(void *)p2,(void *)p,o0,o); p2->type = PCT_PLAIN; p2->plain.at = p->plain.at + o0; p2->plain.len = o - o0; add_to_free(p2); kick_to_curb(p2); } /* * We've found a complete new component; save it. */ void new_component(BTEXT *b) { if (ncomps >= acomps) comps = realloc(comps,(acomps=ncomps+8)*sizeof(BTEXT *)); comps[ncomps++] = b; } ncomps = 0; rooted = 0; curb = 0; tofree = 0; for (pcx=0;pcxtype == PCT_PLAIN) { i0 = 0; for (i=0;iplain.len;i++) { if (ecl_path_buf[pc->plain.at+i].c == SLASH) { if (i > i0) { append_fragment(pc,i0,i); } i0 = i + 1; if (!curb || !curb->npcs) { if (ncomps == 0) rooted = 1; continue; } new_component(curb); curb = 0; } } if (i > i0) { if (i0 > 0) { append_fragment(pc,i0,i); } else { kick_to_curb(pc); } } } else { kick_to_curb(pc); } } if (! curb) curb = new_btext(); new_component(curb); dbg("rooted %d\n",rooted); dbg("component count %d\n",ncomps); for (i=0;i= pca) pcs = realloc(pcs,(pca=pcl+8)*sizeof(PC *)); pcs[pcl++] = pc; } void more(BTEXT *bt, int o, void (*cb)(void)) { PC *pc; int sl; int i; for (;onpcs;o++) { pc = bt->pcs[o]; switch (pc->type) { case PCT_BRACE: sl = pcl; i = pc->brace.closed ? 0 : pc->brace.nalts-1; for (;ibrace.nalts;i++) { void rest(void) { more(bt,o+1,cb); } pc->brace.curalt = i; more(pc->brace.alts[i],0,&rest); pcl = sl; } return; break; default: addpc(pc); break; } } (*cb)(); } void alldone(void) { dbg("Expanded\n"); if (debug) { BTEXT b; b.npcs = pcl; b.pcs = pcs; dump_btext(&b,0); } convert_to_components(pcl,pcs); walk_path(); free_components(); } pcs = 0; pca = 0; pcl = 0; more(bt,0,&alldone); free(pcs); } /* * Check whether we need to append a *, and, if so, do it. We need to * do this whenever there isn't a trailing * already present. */ static void maybe_append_star(void) { BTEXT *b; PC *p; b = walk_unclosed_braces(mainbt,0); if ( (b->npcs < 1) || ((p=b->pcs[b->npcs-1])->type != PCT_STAR) ) { p = new_pc(); p->type = PCT_STAR; p->star = 1; add_pc_to_btext(p,b,-1); } finalstar = p; } /* * Initialize a MATCHRES. */ static void init_matchres(MATCHRES *mr) { mr->pcx = -1; } /* * Record things for a successful match: maintain wildcard saved-match * strings and PCM_* match status, and set the alternative-used bits * for the current status of braced alternatives. */ static void set_pcs(BTEXT *b) { int i; PC *p; for (i=b->npcs-1;i>=0;i--) { p = b->pcs[i]; switch (p->type) { default: abort(); break; case PCT_PLAIN: break; case PCT_BRACE: if ((p->brace.curalt < 0) || (p->brace.curalt >= p->brace.nalts)) abort(); p->brace.altused[p->brace.curalt] = 1; set_pcs(p->brace.alts[p->brace.curalt]); break; case PCT_CCLASS: case PCT_STAR: case PCT_QUES: switch (p->match) { case PCM_NONE: sla_room(&p->saved,p->cur.l); bcopy(p->cur.b,p->saved.b,p->cur.l); p->saved.l = p->cur.l; p->match = PCM_ONE; break; case PCM_ONE: if ( (p->cur.l != p->saved.l) || bcmp(p->cur.b,p->saved.b,p->cur.l) ) { p->match = PCM_MULT; } break; case PCM_MULT: break; default: abort(); break; } break; case PCT_MARK: break; } } } /* * Match-found function for completion. Record success, then maintain * the successful-match common string, extracting the string from the * match for the trailing *. */ static void found_match_complete(void) { BTEXT *lc; PC *p; path_setc(path.l,'\0'); dbg("walked to %s\n",path.b); set_pcs(mainbt); if (ncomps < 1) abort(); lc = comps[ncomps-1]; if (lc->npcs < 1) abort(); p = lc->pcs[lc->npcs-1]; if (p->type != PCT_STAR) abort(); if (! anymatches) { sla_room(&succres,p->cur.l); bcopy(p->cur.b,succres.b,p->cur.l); succres.l = p->cur.l; anymatches = 1; dbg("first match, succres = %.*s\n",succres.l,succres.b); } else { int i; for (i=0;(icur.l)&&(succres.b[i]==p->cur.b[i]);i++) ; succres.l = i; dbg("not first match, succres = %.*s\n",succres.l,succres.b); } } /* * Match-found function for expand-to-braces completion. Just like * found_match_complete except that instead of maintaining the * longest-common-prefix string, we just save the last-* match string * in a PCT_TEXT PC in bracebt for later use. */ static void found_match_brace_complete(void) { BTEXT *lc; PC *p; PC *t; path_setc(path.l,'\0'); dbg("walked to %s\n",path.b); set_pcs(mainbt); if (ncomps < 1) abort(); lc = comps[ncomps-1]; if (lc->npcs < 1) abort(); p = lc->pcs[lc->npcs-1]; if (p->type != PCT_STAR) abort(); t = new_pc(); t->type = PCT_TEXT; sla_init(&t->text); sla_room(&t->text,p->cur.l); bcopy(p->cur.b,t->text.b,p->cur.l); t->text.l = p->cur.l; add_pc_to_btext(t,bracebt,-1); } /* * Match-found function for match listing. Not much to do here but * print the path. n_found is used to provide an appropriate header * (and used elsewhere to notice if this function never gets called). */ static void found_match_show(void) { if (n_found == 0) fprintf(eclout(),"\nCompletions:\n"); path_setc(path.l,'\0'); if (path_tilde) { if ((path.l < path_home.l) || bcmp(path.b,path_home.b,path_home.l)) abort(); fprintf(eclout(),"\t~%.*s%s\n",path_user.l,path_user.b,path.b+path_home.l); } else { fprintf(eclout(),"\t%s\n",path.b); } n_found ++; } /* * Successful match. Expand the trailing star to the longest common * substring of its matches. */ static void expand_success(void) { finalstar->match = PCM_ONE; sla_room(&finalstar->saved,succres.l); bcopy(succres.b,finalstar->saved.b,succres.l); finalstar->saved.l = succres.l; } /* * Failed match. Strip back to the latest failure point - or to the * mark, if all failure points are before that. Find the mark and * then jump forward by pcx PCs. This depends on all matches having * the same sequence of PCs after the mark, which results from the * definition of the strip-to point. */ static void strip_failure(void) { BTEXT *b; int i; PC *p; b = walk_unclosed_braces(mainbt,0); for (i=b->npcs-1;i>=0;i--) { p = b->pcs[i]; if (p->type == PCT_MARK) { if (failres.pcx < 0) { failres.pcx = 0; failres.pcl = 0; } i += failres.pcx; if (i >= b->npcs) abort(); while (b->npcs > i+1) free_pc(b->pcs[--b->npcs]); p = b->pcs[i]; switch (p->type) { default: abort(); break; case PCT_PLAIN: if ((failres.pcl < 0) || (failres.pcl > p->plain.len)) abort(); p->plain.len = failres.pcl; break; case PCT_CCLASS: case PCT_MARK: if (failres.pcl != 0) abort(); free_pc(p); b->npcs --; break; case PCT_QUES: if ((failres.pcl < 0) || (failres.pcl > p->ques)) abort(); p->ques = failres.pcl; break; } return; } } abort(); } /* * Convert the BTEXT back into an input-style QCH string. This is * where wildcards in state PCM_ONE get replaced with their match * strings, where unused brace alternatives disappear, and where * synthetic trailing *s get removed on match failure. * * THe code to generate CCLASS syntax is annoyingly complicated by the * contortions necessary to handle all the special cases. */ static void reinsert_btext(int alter) { QCH *rb; int ra; int rl; void add(QCH c) { if (rl >= ra) rb = realloc(rb,(ra=rl+8)*sizeof(QCH)); rb[rl++] = c; } void reinsert(BTEXT *b) { int bx; PC *p; int i; for (bx=0;bxnpcs;bx++) { p = b->pcs[bx]; switch (p->type) { default: abort(); break; case PCT_PLAIN: for (i=0;iplain.len;i++) { if (qchis(ecl_path_buf[p->plain.at+i],TILDE)) { add((QCH){.f=0,.c=TILDE}); } else { add((QCH){.f=QCHF_MAYQUOTE,.c=ecl_path_buf[p->plain.at+i].c}); } } break; case PCT_BRACE: { int n; unsigned char pre; n = 0; for (i=0;ibrace.nalts;i++) if (p->brace.altused[i]) n ++; pre = OPENBRACE; for (i=0;ibrace.nalts;i++) { if ((n == 0) || !(alter || p->brace.alter) || p->brace.altused[i] || !p->brace.closed) { if ((n > 1) || !(alter || p->brace.alter) || !p->brace.closed) { add((QCH){.f=0,.c=pre}); pre = COMMA; } reinsert(p->brace.alts[i]); } } if (p->brace.closed && (n > 1)) add((QCH){.f=0,.c=CLOSEBRACE}); } break; case PCT_CCLASS: case PCT_QUES: case PCT_STAR: if (alter && (p->match == PCM_ONE)) { for (i=0;isaved.l;i++) { add((QCH){.f=QCHF_MAYQUOTE,.c=p->saved.b[i]}); } } else { switch (p->type) { default: abort(); break; case PCT_CCLASS: { CCLASS t; int traildash; int x0; int x; add((QCH){.f=0,.c=OPENCLASS}); t = p->cclass; if (t.neg) add((QCH){.f=0,.c=NEGCLASS}); if ( CCTST(&t,CLOSECLASS) && ( !CCTST(&t,CLOSECLASS-1) || !CCTST(&t,CLOSECLASS+1) ) ) { add((QCH){.f=0,.c=CLOSECLASS}); CCCLR(&t,CLOSECLASS); } if ( CCTST(&t,CCRANGE) && ( !CCTST(&t,CCRANGE-1) || !CCTST(&t,CCRANGE+1) ) ) { traildash = 1; CCCLR(&t,CCRANGE); } else { traildash = 0; } x0 = -1; for (x=0;x<=256;x++) { if ((x < 256) && CCSET(&t,x)) { if (x0 < 0) x0 = x; } else { if (x0 >= 0) { switch (x-x0) { case 1: add((QCH){.f=QCHF_MAYQUOTE,.c=x0}); break; case 2: add((QCH){.f=QCHF_MAYQUOTE,.c=x0}); add((QCH){.f=QCHF_MAYQUOTE,.c=x0+1}); break; default: add((QCH){.f=QCHF_MAYQUOTE,.c=x0}); add((QCH){.f=0,.c=CCRANGE}); add((QCH){.f=QCHF_MAYQUOTE,.c=x-1}); break; } x0 = -1; } } } if (traildash) add((QCH){.f=QCHF_MAYQUOTE,.c=CCRANGE}); add((QCH){.f=0,.c=CLOSECLASS}); } break; case PCT_QUES: for (i=p->ques;i>0;i--) add((QCH){.f=0,.c=QUES}); break; case PCT_STAR: if (! p->star) add((QCH){.f=0,.c=STAR}); break; } } break; case PCT_MARK: break; case PCT_TEXT: for (i=0;itext.l;i++) { add((QCH){.f=QCHF_MAYQUOTE,.c=p->text.b[i]}); } break; } } } rb = 0; ra = 0; rl = 0; reinsert(mainbt); free(ecl_path_buf); ecl_path_buf = rb; pbspc = ra; ecl_path_len = rl; } /* * Externally-called function to do completion. Fairly * straightforward. */ void ecl_path_complete(int alter) { errors = 0; debug = ecl_debug_completion(); dbg("\n"); x = 0; mainbt = parse_btext(&btext_term_main); maybe_append_star(); mark_stripto(); dump_btext(mainbt,0); init_matchres(&failres); sla_init(&succres); anymatches = 0; path_walk_success = &found_match_complete; expand_braces(mainbt); if (anymatches) { dbg("have matches, succres %.*s\n",succres.l,succres.b); expand_success(); } else { dbg("no matches, failres pcx=%d pcl=%d\n",failres.pcx,failres.pcl); strip_failure(); ecl_beep(); maybe_append_star(); mark_stripto(); dump_btext(mainbt,0); init_matchres(&failres); anymatches = 0; expand_braces(mainbt); if (anymatches) { dbg("now have matches, succres %.*s\n",succres.l,succres.b); expand_success(); } else { dbg("still no matches, failres pcx=%d pcl=%d\n",failres.pcx,failres.pcl); } } reinsert_btext(alter); if (errors > MAX_REPORTED_ERRS) fprintf(eclerr(),"(total of %d errors)\n",errors); if (errors) ecl_full_redraw(); free_btext(mainbt); } /* * Externally-called function to show completions. Fairly * straightforward. */ void ecl_path_show_completions(void) { errors = 0; debug = ecl_debug_completion(); dbg("\n"); x = 0; mainbt = parse_btext(&btext_term_main); maybe_append_star(); mark_stripto(); dump_btext(mainbt,0); init_matchres(&failres); sla_init(&succres); anymatches = 0; n_found = 0; path_walk_success = &found_match_show; expand_braces(mainbt); if (errors > MAX_REPORTED_ERRS) fprintf(eclerr(),"(total of %d errors)\n",errors); if (n_found == 0) fprintf(eclout(),"\nNo completions\n"); ecl_full_redraw(); free_btext(mainbt); } /* * Convert the collected strings to a braced alternative. We go to the * trouble to factor out a common initial substring, if any. This * operates by converting the trailing PCT_STAR PC to a PCT_BRACE PC. * If we have an initial portion to factor out, we can't insert a new * PC, because we don't have the containing BTEXT. So we convert the * PCT_STAR PC to a PCT_BRACE PC with only one alternative, that * alternative containing the two PCs we want, and depend on * single-alternative brace removal to cover our tracks. */ static void convert_to_braces(void) { BTEXT *b; PC *p; int i; int j; int cl; unsigned char *cb; if (bracebt->npcs < 1) abort(); p = bracebt->pcs[bracebt->npcs-1]; if (p->type != PCT_TEXT) abort(); cl = p->text.l; cb = p->text.b; for (i=bracebt->npcs-2;i>=0;i--) { p = bracebt->pcs[i]; if (p->type != PCT_TEXT) abort(); for (j=0;(jtext.l)&&(cb[j]==p->text.b[j]);j++) ; cl = j; } if (cl > 0) { finalstar->type = PCT_BRACE; finalstar->brace.closed = 1; finalstar->brace.alter = 1; finalstar->brace.nalts = 1; finalstar->brace.alts = malloc(sizeof(BTEXT *)); finalstar->brace.altused = malloc(1); b = new_btext(); finalstar->brace.alts[0] = b; finalstar->brace.altused[0] = 1; p = new_pc(); p->type = PCT_TEXT; sla_init(&p->text); sla_room(&p->text,cl); bcopy(cb,p->text.b,cl); p->text.l = cl; add_pc_to_btext(p,b,-1); p = new_pc(); p->type = PCT_STAR; add_pc_to_btext(p,b,-1); finalstar = p; for (i=bracebt->npcs-1;i>=0;i--) { p = bracebt->pcs[i]; if (p->type != PCT_TEXT) abort(); p->text.l -= cl; if (p->text.l) bcopy(p->text.b+cl,p->text.b,p->text.l); } } finalstar->type = PCT_BRACE; finalstar->brace.closed = 1; finalstar->brace.alter = 0; finalstar->brace.nalts = bracebt->npcs; finalstar->brace.alts = malloc(bracebt->npcs*sizeof(BTEXT *)); finalstar->brace.altused = malloc(bracebt->npcs); for (i=0;inpcs;i++) { b = new_btext(); add_pc_to_btext(bracebt->pcs[i],b,-1); finalstar->brace.alts[i] = b; finalstar->brace.altused[i] = 1; } free_just_btext(bracebt); } /* * Externally-called function to expand-to-braces. Fairly * straightforward. */ void ecl_path_brace_complete(void) { errors = 0; debug = ecl_debug_completion(); dbg("\n"); x = 0; mainbt = parse_btext(&btext_term_main); maybe_append_star(); mark_stripto(); dump_btext(mainbt,0); init_matchres(&failres); bracebt = new_btext(); path_walk_success = &found_match_brace_complete; expand_braces(mainbt); if (bracebt->npcs > 0) { dbg("have matches\n"); convert_to_braces(); } else { dbg("no matches\n"); ecl_beep(); free_btext(bracebt); } reinsert_btext(0); if (errors > MAX_REPORTED_ERRS) fprintf(eclerr(),"(total of %d errors)\n",errors); if (errors) ecl_full_redraw(); free_btext(mainbt); } /* * Do common stuff for calls that fiddle with single paths - that is, * that don't do anything with ?, [], *, or {} - but which handle ~s. * This means converting from QCHs in ecl_path_buf to chars in the * ONEPATHSTATE. It also means checking for a leading ~, and, if * present, expanding it and saving enough info for * one_path_tilde_wrapup to put it back if the expansion is still * there. * * Return value is boolean - true means an error has occurred and been * printed, and return should be immediate, without performing the * operation. In this case, any necessary freeing has already been * done. */ static int one_path_tilde_setup(ONEPATHSTATE *s) { int i; s->p.l = ecl_path_len; s->p.a = ecl_path_len + 1; s->p.b = malloc(s->p.a); s->p.b[s->p.l] = '\0'; for (i=s->p.l-1;i>=0;i--) s->p.b[i] = ecl_path_buf[i].c; s->txl = -1; s->tul = -1; if ((ecl_path_len > 0) && qchis(ecl_path_buf[0],TILDE)) { unsigned char *slash; const char *home; slash = index(s->p.b,'/'); if (! slash) slash = s->p.b + s->p.l; s->tul = (slash - s->p.b) - 1; if (s->tul < 0) abort(); s->tildeu = malloc(s->tul+1); bcopy(s->p.b+1,s->tildeu,s->tul); s->tildeu[s->tul] = '\0'; home = ecl_user_homedir(s->tildeu); if (! home) { if (! s->tildeu[0]) { fprintf(eclerr(),"No home\n"); } else { fprintf(eclerr(),"No homedir for %s\n",s->tildeu); } ecl_full_redraw(); free(s->tildeu); s->tul = -1; free(s->p.b); return(1); } s->txl = strlen(home); s->tildex = strdup(home); sla_room(&s->p,s->p.l-(s->tul+1)+s->txl+1); if (s->tul+1 != s->txl) { bcopy(s->p.b+s->tul+1,s->p.b+s->txl,s->p.l+1-(s->tul+1)); } bcopy(s->tildex,s->p.b,s->txl); s->p.l += s->txl - (s->tul+1); } return(0); } /* * Wrap up after a call that used one_path_tilde_setup(). This means * checking to see if a ~ expansion was done, and, if so, whether the * path still begins with the expansion; if so, the ~ syntax is put * back. Then the string is converted back into ecl_path_buf, with * care taken to handle ~ quoting properly. */ static void one_path_tilde_wrapup(ONEPATHSTATE *s) { int realtilde; int i; realtilde = 0; if ( (s->txl >= 0) && (s->p.l >= s->txl) && !bcmp(s->tildex,s->p.b,s->txl) && (!s->p.b[s->txl] || (s->p.b[s->txl] == '/')) ) { sla_room(&s->p,1+s->tul+(s->p.l-s->txl)+1); if (1+s->tul != s->txl) bcopy(s->p.b+s->txl,s->p.b+1+s->tul,s->p.l+1-s->txl); bcopy(s->tildeu,s->p.b+1,s->tul); s->p.b[0] = '~'; s->p.l += 1 + s->tul - s->txl; realtilde = 1; } ecl_path_space(s->p.l); for (i=s->p.l-1;i>=0;i--) ecl_path_buf[i] = (QCH){.f=QCHF_MAYQUOTE,.c=s->p.b[i]}; if (realtilde && (s->p.b[0] == '~')) ecl_path_buf[0].f = 0; ecl_path_len = s->p.l; if (s->txl >= 0) free(s->tildex); if (s->tul >= 0) free(s->tildeu); sla_free(&s->p); } /* * Abort a call that used one_path_tilde_setup(). This means just * freeing anything the ONEPATHSTATE points to. */ static void one_path_tilde_abort(ONEPATHSTATE *s) { if (s->txl >= 0) free(s->tildex); if (s->tul >= 0) free(s->tildeu); sla_free(&s->p); } /* * Implementation of "follow this symlink, if any". Does something * only when the last component is a symlink. */ void ecl_path_follow_symlink(void) { ONEPATHSTATE ps; char *linkbuf; int linkspc; int linklen; if (one_path_tilde_setup(&ps)) return; do { linkspc = 64; while (1) { linkbuf = malloc(linkspc); linklen = readlink(ps.p.b,linkbuf,linkspc); if (linklen < 0) { free(linkbuf); goto break_ret; } if (linklen < linkspc) break; linkspc <<= 1; } if (linkbuf[0] == '/') { sla_free(&ps.p); linkbuf[linklen] = '\0'; ps.p.b = linkbuf; ps.p.a = linkspc; ps.p.l = linklen; } else { int i; for (i=ps.p.l-1;(i>=0)&&(ps.p.b[i]!='/');i--) ; i ++; sla_room(&ps.p,i+linklen+1); bcopy(linkbuf,ps.p.b+i,linklen); ps.p.l = i + linklen; ps.p.b[ps.p.l] = '\0'; free(linkbuf); } } while (0); break_ret:; one_path_tilde_wrapup(&ps); } /* * Implementation of "convert this path to a path not involving * symlinks". This also removes . and .. components. */ void ecl_path_harden(void) { ONEPATHSTATE ps; char *linkbuf; int linkspc; int linklen; int cx; int i; if (one_path_tilde_setup(&ps)) return; linkspc = 64; linkbuf = malloc(linkspc); cx = 0; if (ps.p.b[0] == '/') cx = 1; while (1) { char savec; if ((cx > ps.p.l) || (cx && (ps.p.b[cx-1] != '/'))) abort(); if (cx >= ps.p.l) break; if (ps.p.b[cx] == '/') { for (i=cx+1;(i .. * (b) /@.. -> / * (c) /@../A -> /@A * (d) @../A -> ../@A * (e) ../@../A -> ../../@A * (f) A/../@.. -> A/../.. * (g) A/../@../B -> A/../../@B * (h) C/@.. -> . * (i) C/@../A -> @A * (j) A/C/@.. -> A * (k) A/C/@../B -> A/@B */ if (cx == 0) { /* Cases (a) and (d) */ if (ps.p.l == 2) { /* Case (a) */ goto break_dotdot; } /* Case (d) */ cx += 3; goto continue_dotdot; } if (ps.p.l == 3) { /* Case (b) */ ps.p.l = 1; goto break_dotdot; } if (cx == 1) { /* Case (c) */ bcopy(ps.p.b+4,ps.p.b+1,ps.p.l+1-4); ps.p.l -= 3; goto continue_dotdot; } if ( (cx >= 3) && (ps.p.b[cx-2] == '.') && (ps.p.b[cx-3] == '.') && ((cx == 3) || (ps.p.b[cx-4] == '/')) ) { /* Cases (e), (f), and (g) */ if (i == ps.p.l) { /* Case (f) */ goto break_dotdot; } /* Cases (e) and (g) */ cx += 3; goto continue_dotdot; } if (cx < 2) abort(); for (cx-=2;;cx--) { if (cx < 0) { /* Cases (h) and (i) */ if (i == ps.p.l) { /* Case (h) */ ps.p.b[0] = '.'; ps.p.l = 1; goto break_dotdot; } /* Case (i) */ bcopy(ps.p.b+i+1,ps.p.b,ps.p.l+1-(i+1)); ps.p.l -= i + 1; cx = 0; goto continue_dotdot; } if (ps.p.b[cx] == '/') { /* Cases (j) and (k) */ if (i == ps.p.l) { /* Case (j) */ ps.p.l = cx; ps.p.b[cx] = '\0'; goto break_dotdot; } /* Case (k) */ bcopy(ps.p.b+i,ps.p.b+cx,ps.p.l+1-i); ps.p.l -= i - cx; cx ++; goto continue_dotdot; } } } savec = ps.p.b[i]; ps.p.b[i] = '\0'; while (1) { linklen = readlink(ps.p.b,linkbuf,linkspc); if (linklen < 0) { ps.p.b[i] = savec; if (errno == EINVAL) /* not a symlink */ { if (i == ps.p.l) goto break_dotdot; cx = i + 1; goto continue_dotdot; } goto break_dotdot; } if (linklen < linkspc) break; linkspc <<= 1; free(linkbuf); linkbuf = malloc(linkspc); } ps.p.b[i] = savec; if ((linklen > 0) && (linkbuf[0] == '/')) { sla_room(&ps.p,linklen+(ps.p.l+1-i)); if (linklen != i) bcopy(ps.p.b+i,ps.p.b+linklen,ps.p.l+1-i); bcopy(linkbuf,ps.p.b,linklen); ps.p.l += linklen - i; cx = 1; goto continue_dotdot; } sla_room(&ps.p,cx+linklen+(ps.p.l+1-i)); if (linklen != i-cx) bcopy(ps.p.b+i,ps.p.b+cx+linklen,ps.p.l+1-i); bcopy(linkbuf,ps.p.b+cx,linklen); ps.p.l += linklen - (i-cx); goto continue_dotdot; continue_dotdot:; } break_dotdot:; one_path_tilde_wrapup(&ps); } /* * Find the next or previous path that exists. (*cmp) says whether we * look for the next path or the previous path - it takes a strcmp * result and returns true if it's >0, for next, or <0, for previous. */ static void adjacent_path(int (*cmp)(int)) { ONEPATHSTATE ps; int sx; DIR *d; struct dirent *e; const char *dp; char *ename; int el; int existing; int errs; if (one_path_tilde_setup(&ps)) return; errs = 0; if (ps.p.l < 1) { sx = 0; existing = 0; } else if (ps.p.b[ps.p.l-1] == '/') { sx = ps.p.l; existing = 0; } else { for (sx=ps.p.l-1;(sx>=0)&&(ps.p.b[sx]!='/');sx--) ; sx ++; existing = 1; } while (1) { if (sx < 1) { dp = "."; } else { if (ps.p.b[sx-1] != '/') abort(); if (sx == 1) { dp = "/"; } else { ps.p.b[sx-1] = '\0'; dp = ps.p.b; } } d = opendir(dp); if (! d) { if (!existing && (errno == ENOTDIR)) break; if (! errs) fprintf(eclerr(),"\n"); fprintf(eclerr(),"%s: %s\n",dp,strerror(errno)); errs = 1; if (sx < 0) one_path_tilde_abort(&ps); /* pacify -Wunused */ break; } if (sx > 1) ps.p.b[sx-1] = '/'; ename = 0; while ((e = readdir(d))) { if (e->d_fileno == 0) continue; if ( (e->d_name[0] == '.') && ( (e->d_name[1] == '\0') || ( (e->d_name[1] == '.') && (e->d_name[2] == '\0') ) ) ) continue; if (!existing || (*cmp)(strcmp(&e->d_name[0],ps.p.b+sx))) { if (!ename || (*cmp)(strcmp(ename,&e->d_name[0]))) { free(ename); ename = strdup(&e->d_name[0]); } } } closedir(d); if (ename) { el = strlen(ename); sla_room(&ps.p,sx+el+2); bcopy(ename,ps.p.b+sx,el); ps.p.b[sx+el] = '/'; ps.p.b[sx+el+1] = '\0'; ps.p.l = sx + el + 1; sx += el + 1; existing = 0; } else { if (sx < 2) break; ps.p.b[--sx] = '\0'; for (sx--;(sx>=0)&&(ps.p.b[sx]!='/');sx--) ; sx ++; existing = 1; } } if (sx > 2) { ps.p.l = sx - 1; } else { ps.p.l = sx; } one_path_tilde_wrapup(&ps); if (errs) ecl_full_redraw(); } /* * Find the next path that exists. Just use adjacent_path with a * suitable comparison function. */ void ecl_path_next(void) { int cmp(int scrv) { return(scrv>0); } adjacent_path(&cmp); } /* * Find the previous path that exists. Just use adjacent_path with a * suitable comparison function. */ void ecl_path_prev(void) { int cmp(int scrv) { return(scrv<0); } adjacent_path(&cmp); }