X-Git-Url: https://git.cameronkatri.com/cgit.git/blobdiff_plain/d1f3bbe9d22029f45a77bb938c176ccc0c827d46..4046e8ef66225928a4f0d2cd71479e401faf7c3b:/shared.c

diff --git a/shared.c b/shared.c
index 808e674..8ed14c0 100644
--- a/shared.c
+++ b/shared.c
@@ -1,78 +1,40 @@
 /* shared.c: global vars + some callback functions
  *
- * Copyright (C) 2006 Lars Hjemli
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
  *
  * Licensed under GNU General Public License v2
  *   (see COPYING for full license text)
  */
 
 #include "cgit.h"
+#include <stdio.h>
 
 struct cgit_repolist cgit_repolist;
 struct cgit_context ctx;
-int cgit_cmd;
-
-const char *cgit_version = CGIT_VERSION;
-
-int htmlfd = 0;
-
-void cgit_prepare_context(struct cgit_context *ctx)
-{
-	memset(ctx, 0, sizeof(ctx));
-	ctx->cfg.agefile = "info/web/last-modified";
-	ctx->cfg.cache_dynamic_ttl = 5;
-	ctx->cfg.cache_max_create_time = 5;
-	ctx->cfg.cache_repo_ttl = 5;
-	ctx->cfg.cache_root = CGIT_CACHE_ROOT;
-	ctx->cfg.cache_root_ttl = 5;
-	ctx->cfg.cache_static_ttl = -1;
-	ctx->cfg.css = "/cgit.css";
-	ctx->cfg.logo = "/git-logo.png";
-	ctx->cfg.max_commit_count = 50;
-	ctx->cfg.max_lock_attempts = 5;
-	ctx->cfg.max_msg_len = 60;
-	ctx->cfg.max_repodesc_len = 60;
-	ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
-	ctx->cfg.renamelimit = -1;
-	ctx->cfg.robots = "index, nofollow";
-	ctx->cfg.root_title = "Git repository browser";
-	ctx->cfg.script_name = CGIT_SCRIPT_NAME;
-}
-
-int cgit_get_cmd_index(const char *cmd)
-{
-	static char *cmds[] = {"log", "commit", "diff", "tree", "blob",
-			       "snapshot", "tag", "refs", "patch", NULL};
-	int i;
-
-	for(i = 0; cmds[i]; i++)
-		if (!strcmp(cmd, cmds[i]))
-			return i + 1;
-	return 0;
-}
 
 int chk_zero(int result, char *msg)
 {
 	if (result != 0)
-		die("%s: %s", msg, strerror(errno));
+		die_errno("%s", msg);
 	return result;
 }
 
 int chk_positive(int result, char *msg)
 {
 	if (result <= 0)
-		die("%s: %s", msg, strerror(errno));
+		die_errno("%s", msg);
 	return result;
 }
 
 int chk_non_negative(int result, char *msg)
 {
-    	if (result < 0)
-	    	die("%s: %s",msg, strerror(errno));
+	if (result < 0)
+		die_errno("%s", msg);
 	return result;
 }
 
-struct cgit_repo *add_repo(const char *url)
+char *cgit_default_repo_desc = "[no description]";
+struct cgit_repo *cgit_add_repo(const char *url)
 {
 	struct cgit_repo *ret;
 
@@ -87,18 +49,31 @@ struct cgit_repo *add_repo(const char *url)
 	}
 
 	ret = &cgit_repolist.repos[cgit_repolist.count-1];
+	memset(ret, 0, sizeof(struct cgit_repo));
 	ret->url = trim_end(url, '/');
 	ret->name = ret->url;
 	ret->path = NULL;
-	ret->desc = "[no description]";
+	ret->desc = cgit_default_repo_desc;
 	ret->owner = NULL;
-	ret->group = ctx.cfg.repo_group;
-	ret->defbranch = "master";
+	ret->section = ctx.cfg.section;
 	ret->snapshots = ctx.cfg.snapshots;
+	ret->enable_commit_graph = ctx.cfg.enable_commit_graph;
 	ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
 	ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
+	ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
+	ret->enable_subject_links = ctx.cfg.enable_subject_links;
+	ret->max_stats = ctx.cfg.max_stats;
+	ret->branch_sort = ctx.cfg.branch_sort;
+	ret->commit_sort = ctx.cfg.commit_sort;
 	ret->module_link = ctx.cfg.module_link;
-	ret->readme = NULL;
+	ret->readme = ctx.cfg.readme;
+	ret->mtime = -1;
+	ret->about_filter = ctx.cfg.about_filter;
+	ret->commit_filter = ctx.cfg.commit_filter;
+	ret->source_filter = ctx.cfg.source_filter;
+	ret->email_filter = ctx.cfg.email_filter;
+	ret->clone_url = ctx.cfg.clone_url;
+	ret->submodules.strdup_strings = 1;
 	return ret;
 }
 
@@ -107,7 +82,7 @@ struct cgit_repo *cgit_get_repoinfo(const char *url)
 	int i;
 	struct cgit_repo *repo;
 
-	for (i=0; i<cgit_repolist.count; i++) {
+	for (i = 0; i < cgit_repolist.count; i++) {
 		repo = &cgit_repolist.repos[i];
 		if (!strcmp(repo->url, url))
 			return repo;
@@ -115,131 +90,6 @@ struct cgit_repo *cgit_get_repoinfo(const char *url)
 	return NULL;
 }
 
-void cgit_global_config_cb(const char *name, const char *value)
-{
-	if (!strcmp(name, "root-title"))
-		ctx.cfg.root_title = xstrdup(value);
-	else if (!strcmp(name, "css"))
-		ctx.cfg.css = xstrdup(value);
-	else if (!strcmp(name, "logo"))
-		ctx.cfg.logo = xstrdup(value);
-	else if (!strcmp(name, "index-header"))
-		ctx.cfg.index_header = xstrdup(value);
-	else if (!strcmp(name, "index-info"))
-		ctx.cfg.index_info = xstrdup(value);
-	else if (!strcmp(name, "logo-link"))
-		ctx.cfg.logo_link = xstrdup(value);
-	else if (!strcmp(name, "module-link"))
-		ctx.cfg.module_link = xstrdup(value);
-	else if (!strcmp(name, "virtual-root")) {
-		ctx.cfg.virtual_root = trim_end(value, '/');
-		if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
-			ctx.cfg.virtual_root = "";
-	} else if (!strcmp(name, "nocache"))
-		ctx.cfg.nocache = atoi(value);
-	else if (!strcmp(name, "snapshots"))
-		ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
-	else if (!strcmp(name, "enable-index-links"))
-		ctx.cfg.enable_index_links = atoi(value);
-	else if (!strcmp(name, "enable-log-filecount"))
-		ctx.cfg.enable_log_filecount = atoi(value);
-	else if (!strcmp(name, "enable-log-linecount"))
-		ctx.cfg.enable_log_linecount = atoi(value);
-	else if (!strcmp(name, "cache-root"))
-		ctx.cfg.cache_root = xstrdup(value);
-	else if (!strcmp(name, "cache-root-ttl"))
-		ctx.cfg.cache_root_ttl = atoi(value);
-	else if (!strcmp(name, "cache-repo-ttl"))
-		ctx.cfg.cache_repo_ttl = atoi(value);
-	else if (!strcmp(name, "cache-static-ttl"))
-		ctx.cfg.cache_static_ttl = atoi(value);
-	else if (!strcmp(name, "cache-dynamic-ttl"))
-		ctx.cfg.cache_dynamic_ttl = atoi(value);
-	else if (!strcmp(name, "max-message-length"))
-		ctx.cfg.max_msg_len = atoi(value);
-	else if (!strcmp(name, "max-repodesc-length"))
-		ctx.cfg.max_repodesc_len = atoi(value);
-	else if (!strcmp(name, "max-commit-count"))
-		ctx.cfg.max_commit_count = atoi(value);
-	else if (!strcmp(name, "summary-log"))
-		ctx.cfg.summary_log = atoi(value);
-	else if (!strcmp(name, "summary-branches"))
-		ctx.cfg.summary_branches = atoi(value);
-	else if (!strcmp(name, "summary-tags"))
-		ctx.cfg.summary_tags = atoi(value);
-	else if (!strcmp(name, "agefile"))
-		ctx.cfg.agefile = xstrdup(value);
-	else if (!strcmp(name, "renamelimit"))
-		ctx.cfg.renamelimit = atoi(value);
-	else if (!strcmp(name, "robots"))
-		ctx.cfg.robots = xstrdup(value);
-	else if (!strcmp(name, "clone-prefix"))
-		ctx.cfg.clone_prefix = xstrdup(value);
-	else if (!strcmp(name, "repo.group"))
-		ctx.cfg.repo_group = xstrdup(value);
-	else if (!strcmp(name, "repo.url"))
-		ctx.repo = add_repo(value);
-	else if (!strcmp(name, "repo.name"))
-		ctx.repo->name = xstrdup(value);
-	else if (ctx.repo && !strcmp(name, "repo.path"))
-		ctx.repo->path = trim_end(value, '/');
-	else if (ctx.repo && !strcmp(name, "repo.clone-url"))
-		ctx.repo->clone_url = xstrdup(value);
-	else if (ctx.repo && !strcmp(name, "repo.desc"))
-		ctx.repo->desc = xstrdup(value);
-	else if (ctx.repo && !strcmp(name, "repo.owner"))
-		ctx.repo->owner = xstrdup(value);
-	else if (ctx.repo && !strcmp(name, "repo.defbranch"))
-		ctx.repo->defbranch = xstrdup(value);
-	else if (ctx.repo && !strcmp(name, "repo.snapshots"))
-		ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
-	else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
-		ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
-	else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
-		ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
-	else if (ctx.repo && !strcmp(name, "repo.module-link"))
-		ctx.repo->module_link= xstrdup(value);
-	else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
-		if (*value == '/')
-			ctx.repo->readme = xstrdup(value);
-		else
-			ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
-	} else if (!strcmp(name, "include"))
-		cgit_read_config(value, cgit_global_config_cb);
-}
-
-void cgit_querystring_cb(const char *name, const char *value)
-{
-	if (!strcmp(name,"r")) {
-		ctx.qry.repo = xstrdup(value);
-		ctx.repo = cgit_get_repoinfo(value);
-	} else if (!strcmp(name, "p")) {
-		ctx.qry.page = xstrdup(value);
-		cgit_cmd = cgit_get_cmd_index(value);
-	} else if (!strcmp(name, "url")) {
-		cgit_parse_url(value);
-	} else if (!strcmp(name, "qt")) {
-		ctx.qry.grep = xstrdup(value);
-	} else if (!strcmp(name, "q")) {
-		ctx.qry.search = xstrdup(value);
-	} else if (!strcmp(name, "h")) {
-		ctx.qry.head = xstrdup(value);
-		ctx.qry.has_symref = 1;
-	} else if (!strcmp(name, "id")) {
-		ctx.qry.sha1 = xstrdup(value);
-		ctx.qry.has_sha1 = 1;
-	} else if (!strcmp(name, "id2")) {
-		ctx.qry.sha2 = xstrdup(value);
-		ctx.qry.has_sha1 = 1;
-	} else if (!strcmp(name, "ofs")) {
-		ctx.qry.ofs = atoi(value);
-	} else if (!strcmp(name, "path")) {
-		ctx.qry.path = trim_end(value, '/');
-	} else if (!strcmp(name, "name")) {
-		ctx.qry.name = xstrdup(value);
-	}
-}
-
 void *cgit_free_commitinfo(struct commitinfo *info)
 {
 	free(info->author);
@@ -253,38 +103,39 @@ void *cgit_free_commitinfo(struct commitinfo *info)
 	return NULL;
 }
 
-int hextoint(char c)
-{
-	if (c >= 'a' && c <= 'f')
-		return 10 + c - 'a';
-	else if (c >= 'A' && c <= 'F')
-		return 10 + c - 'A';
-	else if (c >= '0' && c <= '9')
-		return c - '0';
-	else
-		return -1;
-}
-
 char *trim_end(const char *str, char c)
 {
 	int len;
-	char *s, *t;
 
 	if (str == NULL)
 		return NULL;
-	t = (char *)str;
-	len = strlen(t);
-	while(len > 0 && t[len - 1] == c)
+	len = strlen(str);
+	while (len > 0 && str[len - 1] == c)
 		len--;
-
 	if (len == 0)
 		return NULL;
+	return xstrndup(str, len);
+}
 
-	c = t[len];
-	t[len] = '\0';
-	s = xstrdup(t);
-	t[len] = c;
-	return s;
+char *ensure_end(const char *str, char c)
+{
+	size_t len = strlen(str);
+	char *result;
+
+	if (len && str[len - 1] == c)
+		return xstrndup(str, len);
+
+	result = xmalloc(len + 2);
+	memcpy(result, str, len);
+	result[len] = '/';
+	result[len + 1] = '\0';
+	return result;
+}
+
+void strbuf_ensure_end(struct strbuf *sb, char c)
+{
+	if (!sb->len || sb->buf[sb->len - 1] != c)
+		strbuf_addch(sb, c);
 }
 
 char *strlpart(char *txt, int maxlen)
@@ -330,7 +181,7 @@ void cgit_add_ref(struct reflist *list, struct refinfo *ref)
 	list->refs[list->count++] = ref;
 }
 
-struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1)
+static struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1)
 {
 	struct refinfo *ref;
 
@@ -348,6 +199,42 @@ struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1)
 	return ref;
 }
 
+static void cgit_free_taginfo(struct taginfo *tag)
+{
+	if (tag->tagger)
+		free(tag->tagger);
+	if (tag->tagger_email)
+		free(tag->tagger_email);
+	if (tag->msg)
+		free(tag->msg);
+	free(tag);
+}
+
+static void cgit_free_refinfo(struct refinfo *ref)
+{
+	if (ref->refname)
+		free((char *)ref->refname);
+	switch (ref->object->type) {
+	case OBJ_TAG:
+		cgit_free_taginfo(ref->tag);
+		break;
+	case OBJ_COMMIT:
+		cgit_free_commitinfo(ref->commit);
+		break;
+	}
+	free(ref);
+}
+
+void cgit_free_reflist_inner(struct reflist *list)
+{
+	int i;
+
+	for (i = 0; i < list->count; i++) {
+		cgit_free_refinfo(list->refs[i]);
+	}
+	free(list->refs);
+}
+
 int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags,
 		  void *cb_data)
 {
@@ -359,8 +246,8 @@ int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags,
 	return 0;
 }
 
-void cgit_diff_tree_cb(struct diff_queue_struct *q,
-		       struct diff_options *options, void *data)
+static void cgit_diff_tree_cb(struct diff_queue_struct *q,
+			      struct diff_options *options, void *data)
 {
 	int i;
 
@@ -379,7 +266,7 @@ static int load_mmfile(mmfile_t *file, const unsigned char *sha1)
 		file->ptr = (char *)"";
 		file->size = 0;
 	} else {
-		file->ptr = read_sha1_file(sha1, &type, 
+		file->ptr = read_sha1_file(sha1, &type,
 		                           (unsigned long *)&file->size);
 	}
 	return 1;
@@ -396,7 +283,7 @@ static int load_mmfile(mmfile_t *file, const unsigned char *sha1)
 char *diffbuf = NULL;
 int buflen = 0;
 
-int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
+static int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
 {
 	int i;
 
@@ -431,8 +318,9 @@ int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
 }
 
 int cgit_diff_files(const unsigned char *old_sha1,
-		     const unsigned char *new_sha1,
-		     linediff_fn fn)
+		    const unsigned char *new_sha1, unsigned long *old_size,
+		    unsigned long *new_size, int *binary, int context,
+		    int ignorews, linediff_fn fn)
 {
 	mmfile_t file1, file2;
 	xpparam_t diff_params;
@@ -442,52 +330,230 @@ int cgit_diff_files(const unsigned char *old_sha1,
 	if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
 		return 1;
 
+	*old_size = file1.size;
+	*new_size = file2.size;
+
+	if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
+	    (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
+		*binary = 1;
+		if (file1.size)
+			free(file1.ptr);
+		if (file2.size)
+			free(file2.ptr);
+		return 0;
+	}
+
+	memset(&diff_params, 0, sizeof(diff_params));
+	memset(&emit_params, 0, sizeof(emit_params));
+	memset(&emit_cb, 0, sizeof(emit_cb));
 	diff_params.flags = XDF_NEED_MINIMAL;
-	emit_params.ctxlen = 3;
+	if (ignorews)
+		diff_params.flags |= XDF_IGNORE_WHITESPACE;
+	emit_params.ctxlen = context > 0 ? context : 3;
 	emit_params.flags = XDL_EMIT_FUNCNAMES;
-	emit_params.find_func = NULL;
 	emit_cb.outf = filediff_cb;
 	emit_cb.priv = fn;
 	xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
+	if (file1.size)
+		free(file1.ptr);
+	if (file2.size)
+		free(file2.ptr);
 	return 0;
 }
 
 void cgit_diff_tree(const unsigned char *old_sha1,
 		    const unsigned char *new_sha1,
-		    filepair_fn fn, const char *prefix)
+		    filepair_fn fn, const char *prefix, int ignorews)
 {
 	struct diff_options opt;
-	int ret;
-	int prefixlen;
+	struct pathspec_item item;
 
+	memset(&item, 0, sizeof(item));
 	diff_setup(&opt);
 	opt.output_format = DIFF_FORMAT_CALLBACK;
 	opt.detect_rename = 1;
 	opt.rename_limit = ctx.cfg.renamelimit;
 	DIFF_OPT_SET(&opt, RECURSIVE);
+	if (ignorews)
+		DIFF_XDL_SET(&opt, IGNORE_WHITESPACE);
 	opt.format_callback = cgit_diff_tree_cb;
 	opt.format_callback_data = fn;
 	if (prefix) {
-		opt.nr_paths = 1;
-		opt.paths = &prefix;
-		prefixlen = strlen(prefix);
-		opt.pathlens = &prefixlen;
+		item.match = prefix;
+		item.len = strlen(prefix);
+		opt.pathspec.nr = 1;
+		opt.pathspec.items = &item;
 	}
 	diff_setup_done(&opt);
 
 	if (old_sha1 && !is_null_sha1(old_sha1))
-		ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt);
+		diff_tree_sha1(old_sha1, new_sha1, "", &opt);
 	else
-		ret = diff_root_tree_sha1(new_sha1, "", &opt);
+		diff_root_tree_sha1(new_sha1, "", &opt);
 	diffcore_std(&opt);
 	diff_flush(&opt);
 }
 
-void cgit_diff_commit(struct commit *commit, filepair_fn fn)
+void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix)
 {
 	unsigned char *old_sha1 = NULL;
 
 	if (commit->parents)
 		old_sha1 = commit->parents->item->object.sha1;
-	cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL);
+	cgit_diff_tree(old_sha1, commit->object.sha1, fn, prefix,
+		       ctx.qry.ignorews);
+}
+
+int cgit_parse_snapshots_mask(const char *str)
+{
+	struct string_list tokens = STRING_LIST_INIT_DUP;
+	struct string_list_item *item;
+	const struct cgit_snapshot_format *f;
+	int rv = 0;
+
+	/* favor legacy setting */
+	if (atoi(str))
+		return 1;
+
+	string_list_split(&tokens, str, ' ', -1);
+	string_list_remove_empty_items(&tokens, 0);
+
+	for_each_string_list_item(item, &tokens) {
+		for (f = cgit_snapshot_formats; f->suffix; f++) {
+			if (!strcmp(item->string, f->suffix) ||
+			    !strcmp(item->string, f->suffix + 1)) {
+				rv |= f->bit;
+				break;
+			}
+		}
+	}
+
+	string_list_clear(&tokens, 0);
+	return rv;
+}
+
+typedef struct {
+	char * name;
+	char * value;
+} cgit_env_var;
+
+void cgit_prepare_repo_env(struct cgit_repo * repo)
+{
+	cgit_env_var env_vars[] = {
+		{ .name = "CGIT_REPO_URL", .value = repo->url },
+		{ .name = "CGIT_REPO_NAME", .value = repo->name },
+		{ .name = "CGIT_REPO_PATH", .value = repo->path },
+		{ .name = "CGIT_REPO_OWNER", .value = repo->owner },
+		{ .name = "CGIT_REPO_DEFBRANCH", .value = repo->defbranch },
+		{ .name = "CGIT_REPO_SECTION", .value = repo->section },
+		{ .name = "CGIT_REPO_CLONE_URL", .value = repo->clone_url }
+	};
+	int env_var_count = ARRAY_SIZE(env_vars);
+	cgit_env_var *p, *q;
+	static char *warn = "cgit warning: failed to set env: %s=%s\n";
+
+	p = env_vars;
+	q = p + env_var_count;
+	for (; p < q; p++)
+		if (p->value && setenv(p->name, p->value, 1))
+			fprintf(stderr, warn, p->name, p->value);
+}
+
+/* Read the content of the specified file into a newly allocated buffer,
+ * zeroterminate the buffer and return 0 on success, errno otherwise.
+ */
+int readfile(const char *path, char **buf, size_t *size)
+{
+	int fd, e;
+	struct stat st;
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1)
+		return errno;
+	if (fstat(fd, &st)) {
+		e = errno;
+		close(fd);
+		return e;
+	}
+	if (!S_ISREG(st.st_mode)) {
+		close(fd);
+		return EISDIR;
+	}
+	*buf = xmalloc(st.st_size + 1);
+	*size = read_in_full(fd, *buf, st.st_size);
+	e = errno;
+	(*buf)[*size] = '\0';
+	close(fd);
+	return (*size == st.st_size ? 0 : e);
+}
+
+static int is_token_char(char c)
+{
+	return isalnum(c) || c == '_';
+}
+
+/* Replace name with getenv(name), return pointer to zero-terminating char
+ */
+static char *expand_macro(char *name, int maxlength)
+{
+	char *value;
+	int len;
+
+	len = 0;
+	value = getenv(name);
+	if (value) {
+		len = strlen(value);
+		if (len > maxlength)
+			len = maxlength;
+		strncpy(name, value, len);
+	}
+	return name + len;
+}
+
+#define EXPBUFSIZE (1024 * 8)
+
+/* Replace all tokens prefixed by '$' in the specified text with the
+ * value of the named environment variable.
+ * NB: the return value is a static buffer, i.e. it must be strdup'd
+ * by the caller.
+ */
+char *expand_macros(const char *txt)
+{
+	static char result[EXPBUFSIZE];
+	char *p, *start;
+	int len;
+
+	p = result;
+	start = NULL;
+	while (p < result + EXPBUFSIZE - 1 && txt && *txt) {
+		*p = *txt;
+		if (start) {
+			if (!is_token_char(*txt)) {
+				if (p - start > 0) {
+					*p = '\0';
+					len = result + EXPBUFSIZE - start - 1;
+					p = expand_macro(start, len) - 1;
+				}
+				start = NULL;
+				txt--;
+			}
+			p++;
+			txt++;
+			continue;
+		}
+		if (*txt == '$') {
+			start = p;
+			txt++;
+			continue;
+		}
+		p++;
+		txt++;
+	}
+	*p = '\0';
+	if (start && p - start > 0) {
+		len = result + EXPBUFSIZE - start - 1;
+		p = expand_macro(start, len);
+		*p = '\0';
+	}
+	return result;
 }