/* expands the prompt if necessary * ----------------------------------------------------------------------- */ void prompt_expand(void) { /* expand PS1 only */ if(prompt_number != 1) return; /* expand prompt tree if present */ if(prompt_node) { stralloc sa; /* escape prompt */ stralloc_init(&sa); #ifdef DEBUG /* debug_list(prompt_node, 0); */ #endif expand_catsa(prompt_node, &sa, 0); stralloc_nul(&sa); stralloc_zero(&prompt_expansion); prompt_escape(sa.s, &prompt_expansion); stralloc_nul(&prompt_expansion); #ifdef DEBUG /* debug_stralloc("prompt", &sa, 0);*/ #endif } }
int sln(const char* path) { stralloc s, d; char* to; ssize_t i; stralloc_init(&s); stralloc_copys(&s, path); stralloc_init(&d); stralloc_copy(&d, &s); while(reduce(&d)) { buffer_puts(buffer_2, "'"); buffer_putsa(buffer_2, &d); buffer_puts(buffer_2, "' -> '"); buffer_putsa(buffer_2, &s); buffer_puts(buffer_2, "'\n"); buffer_flush(buffer_2); stralloc_nul(&s); stralloc_nul(&d); if(mklink_sa(&s, &d) == -1) { errmsg_warnsys("symlink failed", NULL); exit(2); } stralloc_copy(&s, &d); } return 0; }
/* evaluate case conditional construct (3.9.4.3) * ----------------------------------------------------------------------- */ int eval_case(struct eval *e, struct ncase *ncase) { union node *node; union node *pat; int ret = 0; stralloc word; stralloc pattern; stralloc_init(&word); stralloc_init(&pattern); if(ncase->word) expand_catsa(ncase->word, &word, X_NOSPLIT); stralloc_nul(&word); for(node = ncase->list; node; node = node->list.next) { for(pat = node->ncasenode.pats; pat; pat = pat->list.next) { expand_catsa(pat, &pattern, X_NOSPLIT); stralloc_nul(&pattern); if(shell_fnmatch(pattern.s, pattern.len, word.s, word.len, SH_FNM_PERIOD) == 0) { ret = eval_tree(e, node->ncasenode.cmds, E_LIST); goto end; } stralloc_zero(&pattern); } } end: stralloc_free(&pattern); stralloc_free(&word); return ret; }
int stralloc_expand(stralloc* sa) { #if WINDOWS char* s; size_t n; stralloc_nul(sa); s = sa->s; n = sa->len * 2 + 4; sa->s = 0; sa->len = sa->a = 0; do { /* reserve some space */ stralloc_ready(sa, n); /* repeat until we have reserved enough space */ } while((n = ExpandEnvironmentStrings(s, sa->s, sa->a)) > sa->a); /* now truncate to effective length */ if(n > 0) stralloc_trunc(sa, n - 1); free(s); return n; #endif }
int mklink_sa(stralloc* target, stralloc* link) { size_t i; stralloc_nul(target); stralloc_nul(link); if(stralloc_rchr(target, '/') == target->len && (i = stralloc_rchr(link, '/')) != link->len) { size_t len = i + 1; stralloc_insertb(target, link->s, 0, len); } /*if(verbose) { buffer_putsa(buffer_2, link); buffer_puts(buffer_2, " -> "); buffer_putsa(buffer_2, target); buffer_putnlflush(buffer_2); }*/ stralloc_nul(target); return mklink(target->s, link->s); }
/* set a variable value * ----------------------------------------------------------------------- */ const char *var_setvint(const char *v, int i, int flags) { struct var *var; var = var_create(v, flags); var->flags |= flags; if(var->sa.a == 0) var->sa.s = NULL; stralloc_copyb(&var->sa, v, var->len); stralloc_catc(&var->sa, '='); stralloc_catlong(&var->sa, i); stralloc_nul(&var->sa); var->offset = var->len + 1; return var->sa.s; }
int list_dir_internal(stralloc* dir, char type) { size_t l; struct dir_s d; stralloc pre; int dtype; int is_dir, is_symlink; size_t len; #if !WINDOWS_NATIVE struct stat st; static dev_t root_dev; #endif char *name, *s; (void)type; while(dir->len > 1 && IS_DIRSEP(dir->s[dir->len - 1])) dir->len--; stralloc_nul(dir); #if !WINDOWS_NATIVE if(root_dev == 0) { if(stat(dir->s, &st) != -1) { root_dev = st.st_dev; } } #endif if(dir_open(&d, dir->s) != 0) { buffer_puts(buffer_2, "ERROR: Opening directory "); buffer_putsa(buffer_2, dir); buffer_puts(buffer_2, " failed!\n"); buffer_flush(buffer_2); goto end; } if(dir->s[dir->len - 1] != DIRSEP_C) stralloc_cats(dir, DIRSEP_S); l = dir->len; while((name = dir_read(&d))) { unsigned int mode = 0, nlink = 0, uid = 0, gid = 0; uint64 size = 0, mtime = 0; dtype = dir_type(&d); dir->len = l; if(str_equal(name, "") || str_equal(name, ".") || str_equal(name, "..")) { continue; } stralloc_readyplus(dir, str_len(name) + 1); str_copy(dir->s + dir->len, name); dir->len += str_len(name); is_symlink = !!(dtype & D_SYMLINK); #if !WINDOWS_NATIVE if(!opt_deref && lstat(dir->s, &st) != -1) { if(root_dev && st.st_dev) { if(st.st_dev != root_dev) { continue; } } } #endif #if !WINDOWS_NATIVE if(S_ISLNK(st.st_mode)) { stat(dir->s, &st); } mode = st.st_mode; #endif if(dtype) { is_dir = !!(dtype & D_DIRECTORY); } else { #if WINDOWS_NATIVE is_dir = 0; #else is_dir = !!S_ISDIR(mode); #endif } if(dtype & D_SYMLINK) is_symlink = 1; #if !WINDOWS_NATIVE nlink = st.st_nlink; uid = st.st_uid; gid = st.st_gid; size = st.st_size; mtime = st.st_mtime; #else mode = (is_dir ? 0040000 : 0100000) | (is_symlink ? 0120000 : 0); #if USE_READDIR if(!is_dir) { size = dir_size(&d); /* dir_INTERNAL(&d)->dir_entry->d_name); */ mtime = dir_time(&d); } else { mtime = 0; size = 0; } #else size = dir_size(&d); mtime = dir_time(&d, D_TIME_MODIFICATION); #endif #endif if(opt_list && size >= opt_minsize) { stralloc_init(&pre); /* Mode string */ mode_str(&pre, mode); stralloc_catb(&pre, " ", 1); /* num links */ make_num(&pre, nlink, 3); stralloc_catb(&pre, " ", 1); /* uid */ make_num(&pre, uid, 0); stralloc_catb(&pre, " ", 1); /* gid */ make_num(&pre, gid, 0); stralloc_catb(&pre, " ", 1); /* size */ make_num(&pre, size, 6); stralloc_catb(&pre, " ", 1); /* time */ make_num(&pre, mtime, 0); /* make_time(&pre, mtime, 10); */ stralloc_catb(&pre, " ", 1); } /* fprintf(stderr, "%d %08x\n", is_dir, dir_ATTRS(&d)); */ if(is_dir) stralloc_catc(dir, opt_separator); if(dir->len > MAX_PATH) { buffer_puts(buffer_2, "ERROR: Directory "); buffer_putsa(buffer_2, dir); buffer_puts(buffer_2, " longer than MAX_PATH (" STRINGIFY(MAX_PATH) ")!\n"); /*buffer_putulong(buffer_2, MAX_PATH); buffer_puts(buffer_2, ")!\n");*/ buffer_flush(buffer_2); goto end; } s = dir->s; len = dir->len; if(len >= 2 && s[0] == '.' && IS_DIRSEP(s[1])) { len -= 2; s += 2; } if(opt_list && size >= opt_minsize) buffer_putsa(buffer_1, &pre); if(opt_relative_to) { size_t sz = str_len(opt_relative_to); if(str_diffn(s, opt_relative_to, sz) == 0) { s += sz; len -= sz; while(*s == '\\' || *s == '/') { s++; len--; } } } if(size >= opt_minsize) { buffer_put(buffer_1, s, len); buffer_put(buffer_1, "\n", 1); buffer_flush(buffer_1); } if(is_dir && (opt_deref || !is_symlink)) { dir->len--; list_dir_internal(dir, 0); } } end: dir_close(&d); return 0; }
int dir_type(struct dir_s* d) { int r = 0; #if !USE_READDIR && (defined(_WIN32) || defined(_WIN32) || defined(__MSYS__)) if(dir_INTERNAL(d)->dir_finddata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) r |= D_SYMLINK; else if(dir_INTERNAL(d)->dir_finddata.dwFileAttributes & 0x10) r |= D_DIRECTORY; else if(dir_INTERNAL(d)->dir_finddata.dwFileAttributes & 0x20) r |= D_FILE; #else #ifndef DT_DIR #define DT_DIR 4 #endif #ifndef DT_REG #define DT_REG 8 #endif #ifndef DT_LNK #define DT_LNK 10 #endif #if defined(_DIRENT_HAVE_D_TYPE) || (!defined(__MSYS__) && !defined(__CYGWIN__)) switch((dir_TYPE(d))) { case DT_DIR: { r |= D_DIRECTORY; break; } case DT_REG: { r |= D_FILE; break; } case DT_LNK: { r |= D_SYMLINK; break; } case 0: default: { break; } } #else { stralloc sa; struct stat st; DIR* dh = dir_INTERNAL(d)->dir_handle; stralloc_init(&sa); dir_path(d, &sa); stralloc_nul(&sa); if(lstat(sa.s, &st) != -1) { if(S_ISLNK(st.st_mode)) r |= D_SYMLINK; else if(S_ISDIR(st.st_mode)) r |= D_DIRECTORY; else if(S_ISREG(st.st_mode)) r |= D_FILE; } #ifdef DEBUG_OUTPUT buffer_puts(buffer_2, "dir_type path: "); buffer_putsa(buffer_2, &sa); buffer_putnlflush(buffer_2); #endif // printf("dh: %p __d_dirname: %s\n", dh, dh->__d_dirname); stralloc_free(&sa); } //#error No dirent type method #endif #endif return r; }
union node* expand_param(struct nargparam* param, union node** nptr, struct vartab* varstack, char* argv[], int exitcode, int flags) { union node* n = *nptr; stralloc value; char* str = NULL; const char *v = NULL; unsigned long argc, vlen = 0; for(argc = 0; argv[argc]; ++argc) ; stralloc_init(&value); /* treat special arguments */ if(param->flag & S_SPECIAL) { switch(param->flag & S_SPECIAL) { /* $# substitution */ case S_ARGC: { stralloc_catulong0(&value, argc, 0); break; } /* $* substitution */ case S_ARGV: { char** s; for(s = argv; *s;) { stralloc_cats(&n->narg.stra, *s); if(*++s) stralloc_catc(&n->narg.stra, ' '); } break; } /* $@ substitution */ case S_ARGVS: { unsigned int i = 0; while(i < argc) { param->flag &= ~S_SPECIAL; param->flag |= S_ARG; param->numb = 1 + i; n = expand_param(param, nptr, varstack, argv, exitcode,flags); if(++i < argc) nptr = &n->list.next; } return n; } /* $? substitution */ case S_EXITCODE: { stralloc_catulong0(&value, exitcode, 0); break; } /* $- substitution */ case S_FLAGS: break; /* $! substitution */ case S_BGEXCODE: break; /* $[0-9] arg subst */ case S_ARG: { if(param->numb == 0) { /* stralloc_cats(&value, sh_argv0); */ } else if(param->numb - 1 < argc) { stralloc_cats(&value, argv[param->numb - 1]); } break; } /* $$ arg subst */ case S_PID: { stralloc_catulong0(&value, getpid(), 0); break; } } /* special parameters are always set */ if(value.len) { stralloc_nul(&value); v = value.s; } vlen = value.len; } /* ..and variable substitutions */ else { size_t offset; /* look for the variable. if the S_NULL flag is set and we have a var which is null set v to NULL */ if((v = var_get(varstack, param->name, &offset))) { if(v[offset] == '\0' && (param->flag & S_NULL)) { v = NULL; vlen = 0; } else { v = &v[offset]; vlen = str_len(v); } } } /* check for S_STRLEN substitution */ if(param->flag & S_STRLEN) { char lstr[FMT_ULONG]; n = expand_cat(lstr, fmt_ulong(lstr, vlen), nptr, varstack, flags); stralloc_free(&value); return n; } str = str_ndup(v, vlen); /* otherwise expand the apropriate variable/word subst */ switch(param->flag & S_VAR) { /* return word if parameter unset (or null) */ case S_DEFAULT: { if(v) n = expand_cat(v, vlen, nptr, varstack, flags); /* unset, substitute */ else n = expand_arg(¶m->word->narg, nptr, varstack, argv, exitcode, flags); break; } /* if parameter unset (or null) then expand word to it and substitute paramter */ case S_ASGNDEF: { if(v) n = expand_cat(v, vlen, nptr, varstack, flags); else { n = expand_arg(¶m->word->narg, nptr, varstack, argv, exitcode, flags | X_NOSPLIT); var_setvsa(param->name, /* BUG */ &n->narg.stra, V_DEFAULT); } break; } /* indicate error if null or unset */ case S_ERRNULL: { if(v) n = expand_cat(v, vlen, nptr, varstack, flags); else { union node* tmpnode = NULL; n = expand_arg(¶m->word->narg, &tmpnode, varstack, argv, exitcode, flags); errmsg_warn((n && n->narg.stra.s) ? n->narg.stra.s : "parameter null or not set", 0); if(tmpnode) tree_free(tmpnode); } break; } /* if parameter unset (or null) then substitute null, otherwise substitute word */ case S_ALTERNAT: { if(v) n = expand_arg(¶m->word->narg, nptr, varstack, argv, exitcode, flags); break; /* remove smallest matching suffix */ case S_RSSFX: { int i; stralloc sa; if(v && vlen) { expand_copysa(param->word, &sa, varstack, argv, exitcode, 0); stralloc_nul(&sa); for(i = vlen - 1; i >= 0; i--) if(fnmatch(sa.s, str + i, FNM_PERIOD) == 0) break; n = expand_cat(v, (i < 0 ? vlen : i), nptr, varstack, flags); } break; } } /* remove largest matching suffix */ case S_RLSFX: { unsigned int i; stralloc sa; if(v && vlen) { expand_copysa(param->word, &sa, varstack, argv, exitcode, 0); stralloc_nul(&sa); for(i = 0; i <= vlen; i++) if(fnmatch(sa.s, str + i, FNM_PERIOD) == 0) break; n = expand_cat(v, (i > vlen ? vlen : i), nptr, varstack, flags); } break; } /* remove smallest matching prefix */ case S_RSPFX: { unsigned int i; stralloc sa; if(v && vlen) { expand_copysa(param->word, &sa, varstack, argv, exitcode, 0); stralloc_nul(&sa); for(i = 1; i <= vlen; i++) { str_copyn(str, v, i); if(fnmatch(sa.s, (char*)v, FNM_PERIOD) == 0) break; } if(i > vlen) i = 0; n = expand_cat(v + i, vlen - i, nptr, varstack, flags); str_copy(str, v); } break; } /* remove largest matching prefix */ case S_RLPFX: { unsigned int i; stralloc sa; if(v && vlen) { expand_copysa(param->word, &sa, varstack, argv, exitcode, 0); stralloc_nul(&sa); for(i = vlen; i > 0; i--) { str_copyn(str, v, i); if(fnmatch(sa.s, (char*)v, FNM_PERIOD) == 0) break; } if(i == 0) i = vlen; n = expand_cat(v + i, vlen - i, nptr, varstack, flags); str_copy(str, v); } break; } } free(str); stralloc_free(&value); return n; }
/* main loop, parse lines into trees and execute them * ----------------------------------------------------------------------- */ void sh_loop(void) { struct parser p; union node *list; stralloc cmd; /* if we're in interactive mode some additional stuff is to be initialized */ if(source->mode & SOURCE_IACTIVE) history_load(); stralloc_init(&cmd); parse_init(&p, P_DEFAULT); while(!(parse_gettok(&p, P_DEFAULT) & T_EOF)) { p.pushback++; parse_lineno = source->line; var_setvint("LINENO", parse_lineno, V_DEFAULT); /* launch the parser to get a complete command */ if((list = parse_list(&p))) { struct eval e; if(source->mode & SOURCE_IACTIVE) { tree_printlist(list, &cmd, NULL); stralloc_catc(&cmd, '\n'); stralloc_nul(&cmd); history_set(cmd.s); cmd.s = NULL; history_advance(); } #ifdef DEBUG /* debug_list(list, 0); buffer_putnlflush(fd_err->w);*/ #endif /* DEBUG */ eval_push(&e, E_JCTL); eval_tree(&e, list, E_ROOT|E_LIST); sh->exitcode = eval_pop(&e); stralloc_zero(&cmd); tree_free(list); } else if(!(p.tok & (T_NL | T_SEMI | T_BGND))) { /* we have a parse error */ if(p.tok != T_EOF) parse_error(&p, 0); /* exit if not interactive */ if(!(source->mode & SOURCE_IACTIVE)) sh_exit(1); /* ..otherwise discard the input buffer */ source_flush(); p.pushback = 0; } if(p.tok & (T_NL|T_SEMI|T_BGND)) p.pushback = 0; /* reset prompt */ prompt_number = 0; } }
/* change working directory * ----------------------------------------------------------------------- */ int builtin_cd(int argc, char **argv) { int c; int ok = 0; int symbolic = 1; const char *arg; unsigned long len; unsigned long n; stralloc newcwd; /* check options, -L for symlink, -P for physical path */ while((c = shell_getopt(argc, argv, "LP")) > 0) { switch(c) { case 'L': symbolic = 1; break; case 'P': symbolic = 0; break; default: builtin_invopt(argv); return 1; } } arg = argv[shell_optind]; stralloc_init(&newcwd); /* empty argument means chdir(HOME) */ if(arg == NULL) { arg = var_value("HOME", &len); if(arg[0] == '\0') { sh_msg("HOME variable not set!"); return 1; } } len = str_len(arg); /* when it isn't an absolute path we have to check CDPATH */ if(arg[0] != '/') { char path[PATH_MAX + 1]; const char *cdpath; /* loop through colon-separated CDPATH variable */ cdpath = var_value("CDPATH", NULL); do { /* too much, too much :) */ if((n = str_chr(cdpath, ':')) + len + 1 > PATH_MAX) { /* set error code and print the longer string in the error msg */ errno = ENAMETOOLONG; return builtin_errmsgn(argv, (n > len ? cdpath : arg), (n > len ? n : len), strerror(errno)); } /* copy path prefix from cdpath if present */ if(n) { byte_copy(path, n, cdpath); cdpath += n; path[n++] = '/'; } /* copy the argument and canonicalize */ str_copy(&path[n], arg); ok = shell_realpath(path, &newcwd, symbolic, &sh->cwd); /* skip the colon */ if(*cdpath == ':') cdpath++; } while(*cdpath && !ok); } /* absolute path */ else { /* last cdpath length set to 0, because we're not using cdpath here */ n = 0; ok = shell_canonicalize(arg, &newcwd, symbolic); } stralloc_nul(&newcwd); /* try to chdir() if everything's ok */ if(ok && chdir(newcwd.s) == 0) { /* print path if prefix was taken from cdpath */ if(n) { buffer_putsa(fd_out->w, &newcwd); buffer_putnlflush(fd_out->w); } /* set the path */ stralloc_move(&sh->cwd, &newcwd); /* if the path has symlinks then set sh->cwdsym */ sh->cwdsym = (ok > 1); return 0; } /* we failed */ builtin_error(argv, newcwd.s); stralloc_free(&newcwd); return 1; }
/* evaluate a simple command (3.9.1) * * this function doesn't put stuff in background, it always wait()s, so * it only needs to fork() real programs * ----------------------------------------------------------------------- */ int eval_simple_command(struct eval *e, struct ncmd *ncmd) { union node *nptr; int argc; char **argv; int status; union node *args = NULL; union node *assigns = NULL; union command cmd = { NULL }; enum hash_id id = H_BUILTIN; struct vartab vars; /* struct fdstack io;*/ union node *r; union node *redir = ncmd->rdir; /* expand arguments, if there are arguments we start a hashed search for the command */ if(expand_args(ncmd->args, &args, 0)) { stralloc_nul(&args->narg.stra); cmd = exec_hash(args->narg.stra.s, &id); } /* expand and set the variables, mark them for export if we're gonna execute a command */ if(expand_vars(ncmd->vars, &assigns)) { /* if we don't exit after the command, have a command and not a special builtin the variable changes should be temporary */ if(!(e->flags & E_EXIT) && cmd.ptr && id != H_SBUILTIN) vartab_push(&vars); for(nptr = assigns; nptr; nptr = nptr->list.next) var_setsa(&nptr->narg.stra, (cmd.ptr ? V_EXPORT : V_DEFAULT)); tree_free(assigns); } /* do redirections if present */ /* if(redir && id != H_SBUILTIN && id != H_EXEC) fdstack_push(&io);*/ if(redir/* && id != H_PROGRAM*/) { for(r = redir; r; r = r->list.next) { struct fd *fd = NULL; /* if its the exec special builtin the new fd needs to be persistent */ if(id != H_EXEC) fd_alloca(fd); /* return if a redirection failed */ if(redir_eval(&r->nredir, fd, (id == H_EXEC ? R_NOW : 0))) { status = 1; goto end; } /* check if we need to initialize fd buffers for the new redirection */ if(fd_needbuf(r->nredir.fd)) { /* if its not exec then set up buffers for temporary redirections on the stack */ if(id != H_EXEC) fd_setbuf(r->nredir.fd, alloca(FD_BUFSIZE), FD_BUFSIZE); else fd_allocbuf(r->nredir.fd, FD_BUFSIZE); } } } /* if there is no command we can return after setting the vars and doing the redirections */ if(args == NULL) { status = 0; goto end; } /* when the command wasn't found we abort */ if(cmd.ptr == NULL) { sh_error(args->narg.stra.s); status = exec_error(); goto end; } /* assemble argument list */ argc = tree_count(args); argv = alloca((argc + 1) * sizeof(char *)); expand_argv(args, argv); /* execute the command, this may or may not return, depending on E_EXIT */ status = exec_command(id, cmd, argc, argv, (e->flags & E_EXIT), redir); end: /* restore variable stack */ if(varstack == &vars) vartab_pop(&vars); if(args) tree_free(args); /* undo redirections */ if(id != H_EXEC) { for(r = redir; r; r = r->list.next) fd_pop(r->nredir.fd); } /* if(fdstack == &io) fdstack_pop(&io);*/ return status; }
void xml_read_callback(xmlreader* r, xml_read_callback_fn* fn) { ssize_t n; buffer* b = r->b; stralloc tag, attr, val; stralloc_init(&tag); stralloc_init(&attr); stralloc_init(&val); hmap_init(XML_HMAP_BUCKETS, &r->attrmap); while((n = buffer_skip_until(b, "<", 1)) > 0) { const char* s; stralloc_zero(&tag); r->self_closing = r->closing = 0; s = buffer_peek(b); if(*s == '/') { r->closing = 1; buffer_skipc(b); } else if(*s == '?') { r->self_closing = 1; } else if(*s == '!') { if(buffer_skip_until(b, ">", 1) <= 0) return; continue; } if((n = buffer_gettok_sa(b, &tag, " \n\t\r\v/>", 7)) < 0) return; stralloc_nul(&tag); buffer_skipspace(b); while((s = buffer_peek(b)) && isalpha(*s)) { char ch; int quoted = 0; const char* charset; stralloc_zero(&attr); stralloc_zero(&val); if((n = buffer_gettok_sa(b, &attr, "=", 1)) < 0) break; if(buffer_skipc(b) < 0) return; if(*buffer_peek(b) == '"') { if(buffer_skipc(b) < 0) return; quoted = 1; } charset = quoted ? "\"" : "/> \t\r\n\v"; if((n = buffer_gettok_sa(b, &val, charset, str_len(charset))) < 0) break; if(quoted && buffer_skipc(b) < 0) return; stralloc_nul(&attr); stralloc_nul(&val); hmap_set(&r->attrmap, attr.s, attr.len, val.s, val.len + 1); if(!fn(r, XML_ATTRIBUTE, &attr, &val, NULL)) return; buffer_skipspace(b); } buffer_skipspace(b); if((s = buffer_peek(b)) && str_chr("/?", *s) < 2) { r->self_closing = 1; r->closing = 0; buffer_skipc(b); } buffer_skipspace(b); if((s = buffer_peek(b)) && *s == '>') buffer_skipc(b); if(!fn(r, XML_ELEMENT, &tag, NULL, &r->attrmap)) return; if(r->attrmap) { hmap_destroy(&r->attrmap); r->attrmap = NULL; } hmap_init(XML_HMAP_BUCKETS, &r->attrmap); stralloc_zero(&tag); if((n = buffer_gettok_sa(b, &tag, "<", 1)) < 0) return; s = buffer_peek(b); if(!is_whitespace(tag.s, tag.len)) { if(!fn(r, XML_TEXT, NULL, &tag, NULL)) return; } } }