all repos — cgit @ f34478cbe0214a201e7ecef3e79ed6c957b7beee

a hyperfast web frontend for git written in c

cgit.c (view raw)

  1/* cgit.c: cgi for the git scm
  2 *
  3 * Copyright (C) 2006 Lars Hjemli
  4 *
  5 * Licensed under GNU General Public License v2
  6 *   (see COPYING for full license text)
  7 */
  8
  9#include "cgit.h"
 10#include "cmd.h"
 11
 12static int cgit_prepare_cache(struct cacheitem *item)
 13{
 14	if (!ctx.repo && ctx.qry.repo) {
 15		ctx.page.title = fmt("%s - %s", ctx.cfg.root_title,
 16				      "Bad request");
 17		cgit_print_http_headers(&ctx);
 18		cgit_print_docstart(&ctx);
 19		cgit_print_pageheader(&ctx);
 20		cgit_print_error(fmt("Unknown repo: %s", ctx.qry.repo));
 21		cgit_print_docend();
 22		return 0;
 23	}
 24
 25	if (!ctx.repo) {
 26		item->name = xstrdup(fmt("%s/index.html", ctx.cfg.cache_root));
 27		item->ttl = ctx.cfg.cache_root_ttl;
 28		return 1;
 29	}
 30
 31	if (!cgit_cmd) {
 32		item->name = xstrdup(fmt("%s/%s/index.%s.html", ctx.cfg.cache_root,
 33					 cache_safe_filename(ctx.repo->url),
 34					 cache_safe_filename(ctx.qry.raw)));
 35		item->ttl = ctx.cfg.cache_repo_ttl;
 36	} else {
 37		item->name = xstrdup(fmt("%s/%s/%s/%s.html", ctx.cfg.cache_root,
 38					 cache_safe_filename(ctx.repo->url),
 39					 ctx.qry.page,
 40					 cache_safe_filename(ctx.qry.raw)));
 41		if (ctx.qry.has_symref)
 42			item->ttl = ctx.cfg.cache_dynamic_ttl;
 43		else if (ctx.qry.has_sha1)
 44			item->ttl = ctx.cfg.cache_static_ttl;
 45		else
 46			item->ttl = ctx.cfg.cache_repo_ttl;
 47	}
 48	return 1;
 49}
 50
 51struct refmatch {
 52	char *req_ref;
 53	char *first_ref;
 54	int match;
 55};
 56
 57int find_current_ref(const char *refname, const unsigned char *sha1,
 58		     int flags, void *cb_data)
 59{
 60	struct refmatch *info;
 61
 62	info = (struct refmatch *)cb_data;
 63	if (!strcmp(refname, info->req_ref))
 64		info->match = 1;
 65	if (!info->first_ref)
 66		info->first_ref = xstrdup(refname);
 67	return info->match;
 68}
 69
 70char *find_default_branch(struct cgit_repo *repo)
 71{
 72	struct refmatch info;
 73
 74	info.req_ref = repo->defbranch;
 75	info.first_ref = NULL;
 76	info.match = 0;
 77	for_each_branch_ref(find_current_ref, &info);
 78	if (info.match)
 79		return info.req_ref;
 80	else
 81		return info.first_ref;
 82}
 83
 84static int prepare_repo_cmd(struct cgit_context *ctx)
 85{
 86	char *tmp;
 87	unsigned char sha1[20];
 88	int nongit = 0;
 89
 90	setenv("GIT_DIR", ctx->repo->path, 1);
 91	setup_git_directory_gently(&nongit);
 92	if (nongit) {
 93		ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
 94				      "config error");
 95		tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
 96		ctx->repo = NULL;
 97		cgit_print_http_headers(ctx);
 98		cgit_print_docstart(ctx);
 99		cgit_print_pageheader(ctx);
100		cgit_print_error(tmp);
101		cgit_print_docend();
102		return 1;
103	}
104	ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
105
106	if (!ctx->qry.head) {
107		ctx->qry.head = xstrdup(find_default_branch(ctx->repo));
108		ctx->repo->defbranch = ctx->qry.head;
109	}
110
111	if (!ctx->qry.head) {
112		cgit_print_http_headers(ctx);
113		cgit_print_docstart(ctx);
114		cgit_print_pageheader(ctx);
115		cgit_print_error("Repository seems to be empty");
116		cgit_print_docend();
117		return 1;
118	}
119
120	if (get_sha1(ctx->qry.head, sha1)) {
121		tmp = xstrdup(ctx->qry.head);
122		ctx->qry.head = ctx->repo->defbranch;
123		cgit_print_http_headers(ctx);
124		cgit_print_docstart(ctx);
125		cgit_print_pageheader(ctx);
126		cgit_print_error(fmt("Invalid branch: %s", tmp));
127		cgit_print_docend();
128		return 1;
129	}
130	return 0;
131}
132
133static void process_request(struct cgit_context *ctx)
134{
135	struct cgit_cmd *cmd;
136
137	cmd = cgit_get_cmd(ctx);
138	if (!cmd) {
139		ctx->page.title = "cgit error";
140		ctx->repo = NULL;
141		cgit_print_http_headers(ctx);
142		cgit_print_docstart(ctx);
143		cgit_print_pageheader(ctx);
144		cgit_print_error("Invalid request");
145		cgit_print_docend();
146		return;
147	}
148
149	if (cmd->want_repo && prepare_repo_cmd(ctx))
150		return;
151
152	if (cmd->want_layout) {
153		cgit_print_http_headers(ctx);
154		cgit_print_docstart(ctx);
155		cgit_print_pageheader(ctx);
156	}
157
158	cmd->fn(ctx);
159
160	if (cmd->want_layout)
161		cgit_print_docend();
162}
163
164static long ttl_seconds(long ttl)
165{
166	if (ttl<0)
167		return 60 * 60 * 24 * 365;
168	else
169		return ttl * 60;
170}
171
172static void cgit_fill_cache(struct cacheitem *item, int use_cache)
173{
174	int stdout2;
175
176	if (use_cache) {
177		stdout2 = chk_positive(dup(STDOUT_FILENO),
178				       "Preserving STDOUT");
179		chk_zero(close(STDOUT_FILENO), "Closing STDOUT");
180		chk_positive(dup2(item->fd, STDOUT_FILENO), "Dup2(cachefile)");
181	}
182
183	ctx.page.modified = time(NULL);
184	ctx.page.expires = ctx.page.modified + ttl_seconds(item->ttl);
185	process_request(&ctx);
186
187	if (use_cache) {
188		chk_zero(close(STDOUT_FILENO), "Close redirected STDOUT");
189		chk_positive(dup2(stdout2, STDOUT_FILENO),
190			     "Restoring original STDOUT");
191		chk_zero(close(stdout2), "Closing temporary STDOUT");
192	}
193}
194
195static void cgit_check_cache(struct cacheitem *item)
196{
197	int i = 0;
198
199 top:
200	if (++i > ctx.cfg.max_lock_attempts) {
201		die("cgit_refresh_cache: unable to lock %s: %s",
202		    item->name, strerror(errno));
203	}
204	if (!cache_exist(item)) {
205		if (!cache_lock(item)) {
206			sleep(1);
207			goto top;
208		}
209		if (!cache_exist(item)) {
210			cgit_fill_cache(item, 1);
211			cache_unlock(item);
212		} else {
213			cache_cancel_lock(item);
214		}
215	} else if (cache_expired(item) && cache_lock(item)) {
216		if (cache_expired(item)) {
217			cgit_fill_cache(item, 1);
218			cache_unlock(item);
219		} else {
220			cache_cancel_lock(item);
221		}
222	}
223}
224
225static void cgit_print_cache(struct cacheitem *item)
226{
227	static char buf[4096];
228	ssize_t i;
229
230	int fd = open(item->name, O_RDONLY);
231	if (fd<0)
232		die("Unable to open cached file %s", item->name);
233
234	while((i=read(fd, buf, sizeof(buf))) > 0)
235		write(STDOUT_FILENO, buf, i);
236
237	close(fd);
238}
239
240static void cgit_parse_args(int argc, const char **argv)
241{
242	int i;
243
244	for (i = 1; i < argc; i++) {
245		if (!strncmp(argv[i], "--cache=", 8)) {
246			ctx.cfg.cache_root = xstrdup(argv[i]+8);
247		}
248		if (!strcmp(argv[i], "--nocache")) {
249			ctx.cfg.nocache = 1;
250		}
251		if (!strncmp(argv[i], "--query=", 8)) {
252			ctx.qry.raw = xstrdup(argv[i]+8);
253		}
254		if (!strncmp(argv[i], "--repo=", 7)) {
255			ctx.qry.repo = xstrdup(argv[i]+7);
256		}
257		if (!strncmp(argv[i], "--page=", 7)) {
258			ctx.qry.page = xstrdup(argv[i]+7);
259		}
260		if (!strncmp(argv[i], "--head=", 7)) {
261			ctx.qry.head = xstrdup(argv[i]+7);
262			ctx.qry.has_symref = 1;
263		}
264		if (!strncmp(argv[i], "--sha1=", 7)) {
265			ctx.qry.sha1 = xstrdup(argv[i]+7);
266			ctx.qry.has_sha1 = 1;
267		}
268		if (!strncmp(argv[i], "--ofs=", 6)) {
269			ctx.qry.ofs = atoi(argv[i]+6);
270		}
271	}
272}
273
274int main(int argc, const char **argv)
275{
276	struct cacheitem item;
277	const char *cgit_config_env = getenv("CGIT_CONFIG");
278
279	cgit_prepare_context(&ctx);
280	item.st.st_mtime = time(NULL);
281	cgit_repolist.length = 0;
282	cgit_repolist.count = 0;
283	cgit_repolist.repos = NULL;
284
285	cgit_read_config(cgit_config_env ? cgit_config_env : CGIT_CONFIG,
286			 cgit_global_config_cb);
287	if (getenv("SCRIPT_NAME"))
288		ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME"));
289	if (getenv("QUERY_STRING"))
290		ctx.qry.raw = xstrdup(getenv("QUERY_STRING"));
291	cgit_parse_args(argc, argv);
292	cgit_parse_query(ctx.qry.raw, cgit_querystring_cb);
293	if (!cgit_prepare_cache(&item))
294		return 0;
295	if (ctx.cfg.nocache) {
296		cgit_fill_cache(&item, 0);
297	} else {
298		cgit_check_cache(&item);
299		cgit_print_cache(&item);
300	}
301	return 0;
302}