X-Git-Url: https://gitweb.ps.run/ps-cgit/blobdiff_plain/488a214a81a25c6397c56822ed1713f51dddc520..50d5af3adcdd90424b70e9472af24356ed50aa9b:/cgit.c diff --git a/cgit.c b/cgit.c index 411e421..a792fe4 100644 --- a/cgit.c +++ b/cgit.c @@ -17,6 +17,31 @@ const char *cgit_version = CGIT_VERSION; +void add_mimetype(const char *name, const char *value) +{ + struct string_list_item *item; + + item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); + item->util = xstrdup(value); +} + +struct cgit_filter *new_filter(const char *cmd, int extra_args) +{ + struct cgit_filter *f; + + if (!cmd || !cmd[0]) + return NULL; + + f = xmalloc(sizeof(struct cgit_filter)); + f->cmd = xstrdup(cmd); + f->argv = xmalloc((2 + extra_args) * sizeof(char *)); + f->argv[0] = f->cmd; + f->argv[1] = NULL; + return f; +} + +static void process_cached_repolist(const char *path); + void config_cb(const char *name, const char *value) { if (!strcmp(name, "root-title")) @@ -31,6 +56,8 @@ void config_cb(const char *name, const char *value) ctx.cfg.favicon = xstrdup(value); else if (!strcmp(name, "footer")) ctx.cfg.footer = xstrdup(value); + else if (!strcmp(name, "head-include")) + ctx.cfg.head_include = xstrdup(value); else if (!strcmp(name, "header")) ctx.cfg.header = xstrdup(value); else if (!strcmp(name, "logo")) @@ -49,6 +76,10 @@ void config_cb(const char *name, const char *value) ctx.cfg.virtual_root = ""; } else if (!strcmp(name, "nocache")) ctx.cfg.nocache = atoi(value); + else if (!strcmp(name, "noplainemail")) + ctx.cfg.noplainemail = atoi(value); + else if (!strcmp(name, "noheader")) + ctx.cfg.noheader = atoi(value); else if (!strcmp(name, "snapshots")) ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); else if (!strcmp(name, "enable-index-links")) @@ -67,10 +98,18 @@ void config_cb(const char *name, const char *value) 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-scanrc-ttl")) + ctx.cfg.cache_scanrc_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, "about-filter")) + ctx.cfg.about_filter = new_filter(value, 0); + else if (!strcmp(name, "commit-filter")) + ctx.cfg.commit_filter = new_filter(value, 0); + else if (!strcmp(name, "embedded")) + ctx.cfg.embedded = atoi(value); else if (!strcmp(name, "max-message-length")) ctx.cfg.max_msg_len = atoi(value); else if (!strcmp(name, "max-repodesc-length")) @@ -79,6 +118,13 @@ void config_cb(const char *name, const char *value) ctx.cfg.max_repo_count = atoi(value); else if (!strcmp(name, "max-commit-count")) ctx.cfg.max_commit_count = atoi(value); + else if (!strcmp(name, "scan-path")) + if (!ctx.cfg.nocache && ctx.cfg.cache_size) + process_cached_repolist(value); + else + scan_tree(value); + else if (!strcmp(name, "source-filter")) + ctx.cfg.source_filter = new_filter(value, 1); else if (!strcmp(name, "summary-log")) ctx.cfg.summary_log = atoi(value); else if (!strcmp(name, "summary-branches")) @@ -95,6 +141,8 @@ void config_cb(const char *name, const char *value) ctx.cfg.clone_prefix = xstrdup(value); else if (!strcmp(name, "local-time")) ctx.cfg.local_time = atoi(value); + else if (!prefixcmp(name, "mimetype.")) + add_mimetype(name + 9, value); else if (!strcmp(name, "repo.group")) ctx.cfg.repo_group = xstrdup(value); else if (!strcmp(name, "repo.url")) @@ -121,6 +169,12 @@ void config_cb(const char *name, const char *value) ctx.repo->max_stats = cgit_find_stats_period(value, NULL); else if (ctx.repo && !strcmp(name, "repo.module-link")) ctx.repo->module_link= xstrdup(value); + else if (ctx.repo && !strcmp(name, "repo.about-filter")) + ctx.repo->about_filter = new_filter(value, 0); + else if (ctx.repo && !strcmp(name, "repo.commit-filter")) + ctx.repo->commit_filter = new_filter(value, 0); + else if (ctx.repo && !strcmp(name, "repo.source-filter")) + ctx.repo->source_filter = new_filter(value, 1); else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { if (*value == '/') ctx.repo->readme = xstrdup(value); @@ -132,6 +186,9 @@ void config_cb(const char *name, const char *value) static void querystring_cb(const char *name, const char *value) { + if (!value) + value = ""; + if (!strcmp(name,"r")) { ctx.qry.repo = xstrdup(value); ctx.repo = cgit_get_repoinfo(value); @@ -170,6 +227,11 @@ static void querystring_cb(const char *name, const char *value) } } +char *xstrdupn(const char *str) +{ + return (str ? xstrdup(str) : NULL); +} + static void prepare_context(struct cgit_context *ctx) { memset(ctx, 0, sizeof(ctx)); @@ -181,9 +243,10 @@ static void prepare_context(struct cgit_context *ctx) ctx->cfg.cache_repo_ttl = 5; ctx->cfg.cache_root = CGIT_CACHE_ROOT; ctx->cfg.cache_root_ttl = 5; + ctx->cfg.cache_scanrc_ttl = 15; ctx->cfg.cache_static_ttl = -1; ctx->cfg.css = "/cgit.css"; - ctx->cfg.logo = "/git-logo.png"; + ctx->cfg.logo = "/cgit.png"; ctx->cfg.local_time = 0; ctx->cfg.max_repo_count = 50; ctx->cfg.max_commit_count = 50; @@ -200,6 +263,16 @@ static void prepare_context(struct cgit_context *ctx) ctx->cfg.summary_branches = 10; ctx->cfg.summary_log = 10; ctx->cfg.summary_tags = 10; + ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); + ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); + ctx->env.https = xstrdupn(getenv("HTTPS")); + ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); + ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); + ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); + ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); + ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); + ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); + ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); ctx->page.mimetype = "text/html"; ctx->page.charset = PAGE_ENCODING; ctx->page.filename = NULL; @@ -207,6 +280,13 @@ static void prepare_context(struct cgit_context *ctx) ctx->page.modified = time(NULL); ctx->page.expires = ctx->page.modified; ctx->page.etag = NULL; + memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); + if (ctx->env.script_name) + ctx->cfg.script_name = ctx->env.script_name; + if (ctx->env.query_string) + ctx->qry.raw = ctx->env.query_string; + if (!ctx->env.cgit_config) + ctx->env.cgit_config = CGIT_CONFIG; } struct refmatch { @@ -286,6 +366,8 @@ static int prepare_repo_cmd(struct cgit_context *ctx) if (get_sha1(ctx->qry.head, sha1)) { tmp = xstrdup(ctx->qry.head); ctx->qry.head = ctx->repo->defbranch; + ctx->page.status = 404; + ctx->page.statusmsg = "not found"; cgit_print_http_headers(ctx); cgit_print_docstart(ctx); cgit_print_pageheader(ctx); @@ -342,28 +424,93 @@ int cmp_repos(const void *a, const void *b) return strcmp(ra->url, rb->url); } -void print_repo(struct cgit_repo *repo) +void print_repo(FILE *f, struct cgit_repo *repo) { - printf("repo.url=%s\n", repo->url); - printf("repo.name=%s\n", repo->name); - printf("repo.path=%s\n", repo->path); + fprintf(f, "repo.url=%s\n", repo->url); + fprintf(f, "repo.name=%s\n", repo->name); + fprintf(f, "repo.path=%s\n", repo->path); if (repo->owner) - printf("repo.owner=%s\n", repo->owner); + fprintf(f, "repo.owner=%s\n", repo->owner); if (repo->desc) - printf("repo.desc=%s\n", repo->desc); + fprintf(f, "repo.desc=%s\n", repo->desc); if (repo->readme) - printf("repo.readme=%s\n", repo->readme); - printf("\n"); + fprintf(f, "repo.readme=%s\n", repo->readme); + fprintf(f, "\n"); } -void print_repolist(struct cgit_repolist *list) +void print_repolist(FILE *f, struct cgit_repolist *list, int start) { int i; - for(i = 0; i < list->count; i++) - print_repo(&list->repos[i]); + for(i = start; i < list->count; i++) + print_repo(f, &list->repos[i]); } +/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' + * and return 0 on success. + */ +static int generate_cached_repolist(const char *path, const char *cached_rc) +{ + char *locked_rc; + int idx; + FILE *f; + + locked_rc = xstrdup(fmt("%s.lock", cached_rc)); + f = fopen(locked_rc, "wx"); + if (!f) { + /* Inform about the error unless the lockfile already existed, + * since that only means we've got concurrent requests. + */ + if (errno != EEXIST) + fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", + locked_rc, strerror(errno), errno); + return errno; + } + idx = cgit_repolist.count; + scan_tree(path); + print_repolist(f, &cgit_repolist, idx); + if (rename(locked_rc, cached_rc)) + fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", + locked_rc, cached_rc, strerror(errno), errno); + fclose(f); + return 0; +} + +static void process_cached_repolist(const char *path) +{ + struct stat st; + char *cached_rc; + time_t age; + + cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, + hash_str(path))); + + if (stat(cached_rc, &st)) { + /* Nothing is cached, we need to scan without forking. And + * if we fail to generate a cached repolist, we need to + * invoke scan_tree manually. + */ + if (generate_cached_repolist(path, cached_rc)) + scan_tree(path); + return; + } + + parse_configfile(cached_rc, config_cb); + + /* If the cached configfile hasn't expired, lets exit now */ + age = time(NULL) - st.st_mtime; + if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) + return; + + /* The cached repolist has been parsed, but it was old. So lets + * rescan the specified path and generate a new cached repolist + * in a child-process to avoid latency for the current request. + */ + if (fork()) + return; + + exit(generate_cached_repolist(path, cached_rc)); +} static void cgit_parse_args(int argc, const char **argv) { @@ -377,6 +524,9 @@ static void cgit_parse_args(int argc, const char **argv) if (!strcmp(argv[i], "--nocache")) { ctx.cfg.nocache = 1; } + if (!strcmp(argv[i], "--nohttp")) { + ctx.env.no_http = "1"; + } if (!strncmp(argv[i], "--query=", 8)) { ctx.qry.raw = xstrdup(argv[i]+8); } @@ -397,7 +547,8 @@ static void cgit_parse_args(int argc, const char **argv) if (!strncmp(argv[i], "--ofs=", 6)) { ctx.qry.ofs = atoi(argv[i]+6); } - if (!strncmp(argv[i], "--scan-tree=", 12)) { + if (!strncmp(argv[i], "--scan-tree=", 12) || + !strncmp(argv[i], "--scan-path=", 12)) { scan++; scan_tree(argv[i] + 12); } @@ -405,7 +556,7 @@ static void cgit_parse_args(int argc, const char **argv) if (scan) { qsort(cgit_repolist.repos, cgit_repolist.count, sizeof(struct cgit_repo), cmp_repos); - print_repolist(&cgit_repolist); + print_repolist(stdout, &cgit_repolist, 0); exit(0); } } @@ -429,7 +580,6 @@ static int calc_ttl() int main(int argc, const char **argv) { - const char *cgit_config_env = getenv("CGIT_CONFIG"); const char *path; char *qry; int err, ttl; @@ -439,13 +589,8 @@ int main(int argc, const char **argv) cgit_repolist.count = 0; cgit_repolist.repos = NULL; - if (getenv("SCRIPT_NAME")) - ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME")); - if (getenv("QUERY_STRING")) - ctx.qry.raw = xstrdup(getenv("QUERY_STRING")); cgit_parse_args(argc, argv); - parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG, - config_cb); + parse_configfile(ctx.env.cgit_config, config_cb); ctx.repo = NULL; http_parse_querystring(ctx.qry.raw, querystring_cb); @@ -460,7 +605,7 @@ int main(int argc, const char **argv) * urls without the need for rewriterules in the webserver (as * long as PATH_INFO is included in the cache lookup key). */ - path = getenv("PATH_INFO"); + path = ctx.env.path_info; if (!ctx.qry.url && path) { if (path[0] == '/') path++; @@ -470,12 +615,14 @@ int main(int argc, const char **argv) ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); free(qry); } else - ctx.qry.raw = ctx.qry.url; + ctx.qry.raw = xstrdup(ctx.qry.url); cgit_parse_url(ctx.qry.url); } ttl = calc_ttl(); ctx.page.expires += ttl*60; + if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) + ctx.cfg.nocache = 1; if (ctx.cfg.nocache) ctx.cfg.cache_size = 0; err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,