f
Content-Security-Policy: default-src 'none'
Content-Type: text/plain; charset=UTF-8
Content-Length: 10927
Content-Disposition: inline; filename="cache.c"
Last-Modified: Wed, 08 Jan 2025 13:24:34 GMT
Expires: Wed, 08 Jan 2025 17:24:34 GMT
ETag: "55199e8fe555d197427b6db3c25ca655d2e7804a"

/* cache.c: cache management
 *
 * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
 *
 * Licensed under GNU General Public License v2
 *   (see COPYING for full license text)
 *
 *
 * The cache is just a directory structure where each file is a cache slot,
 * and each filename is based on the hash of some key (e.g. the cgit url).
 * Each file contains the full key followed by the cached content for that
 * key.
 *
 */

#include "cgit.h"
#include "cache.h"
#include "html.h"
#ifdef HAVE_LINUX_SENDFILE
#include <sys/sendfile.h>
#endif

#define CACHE_BUFSIZE (1024 * 4)

struct cache_slot {
	const char *key;
	size_t keylen;
	int ttl;
	cache_fill_fn fn;
	int cache_fd;
	int lock_fd;
	int stdout_fd;
	const char *cache_name;
	const char *lock_name;
	int match;
	struct stat cache_st;
	int bufsize;
	char buf[CACHE_BUFSIZE];
};

/* Open an existing cache slot and fill the cache buffer with
 * (part of) the content of the cache file. Return 0 on success
 * and errno otherwise.
 */
static int open_slot(struct cache_slot *slot)
{
	char *bufz;
	ssize_t bufkeylen = -1;

	slot->cache_fd = open(slot->cache_name, O_RDONLY);
	if (slot->cache_fd == -1)
		return errno;

	if (fstat(slot->cache_fd, &slot->cache_st))
		return errno;

	slot->bufsize = xread(slot->cache_fd, slot->buf, sizeof(slot->buf));
	if (slot->bufsize < 0)
		return errno;

	bufz = memchr(slot->buf, 0, slot->bufsize);
	if (bufz)
		bufkeylen = bufz - slot->buf;

	if (slot->key)
		slot->match = bufkeylen == slot->keylen &&
		    !memcmp(slot->key, slot->buf, bufkeylen + 1);

	return 0;
}

/* Close the active cache slot */
static int close_slot(struct cache_slot *slot)
{
	int err = 0;
	if (slot->cache_fd > 0) {
		if (close(slot->cache_fd))
			err = errno;
		else
			slot->cache_fd = -1;
	}
	return err;
}

/* Print the content of the active cache slot (but skip the key). */
static int print_slot(struct cache_slot *slot)
{
#ifdef HAVE_LINUX_SENDFILE
	off_t start_off;
	int ret;

	start_off = slot->keylen + 1;

	do {
		ret = sendfile(STDOUT_FILENO, slot->cache_fd, &start_off,
				slot->cache_st.st_size - start_off);
		if (ret < 0) {
			if (errno == EAGAIN || errno == EINTR)
				continue;
			return errno;
		}
		return 0;
	} while (1);
#else
	ssize_t i, j;

	i = lseek(slot->cache_fd, slot->keylen + 1, SEEK_SET);
	if (i != slot->keylen + 1)
		return errno;

	do {
		i = j = xread(slot->cache_fd, slot->buf, sizeof(slot->buf));
		if (i > 0)
			j = xwrite(STDOUT_FILENO, slot->buf, i);
	} while (i > 0 && j == i);

	if (i < 0 || j != i)
		return errno;
	else
		return 0;
#endif
}

/* Check if the slot has expired */
static int is_expired(struct cache_slot *slot)
{
	if (slot->ttl < 0)
		return 0;
	else
		return slot->cache_st.st_mtime + slot->ttl * 60 < time(NULL);
}

/* Check if the slot has been modified since we opened it.
 * NB: If stat() fails, we pretend the file is modified.
 */
static int is_modified(struct cache_slot *slot)
{
	struct stat st;

	if (stat(slot->cache_name, &st))
		return 1;
	return (st.st_ino != slot->cache_st.st_ino ||
		st.st_mtime != slot->cache_st.st_mtime ||
		st.st_size != slot->cache_st.st_size);
}

/* Close an open lockfile */
static int close_lock(struct cache_slot *slot)
{
	int err = 0;
	if (slot->lock_fd > 0) {
		if (close(slot->lock_fd))
			err = errno;
		else
			slot->lock_fd = -1;
	}
	return err;
}

/* Create a lockfile used to store the generated content for a cache
 * slot, and write the slot key + \0 into it.
 * Returns 0 on success and errno otherwise.
 */
static int lock_slot(struct cache_slot *slot)
{
	struct flock lock = {
		.l_type = F_WRLCK,
		.l_whence = SEEK_SET,
		.l_start = 0,
		.l_len = 0,
	};

	slot->lock_fd = open(slot->lock_name, O_RDWR | O_CREAT,
			     S_IRUSR | S_IWUSR);
	if (slot->lock_fd == -1)
		return errno;
	if (fcntl(slot->lock_fd, F_SETLK, &lock) < 0) {
		int saved_errno = errno;
		close(slot->lock_fd);
		slot->lock_fd = -1;
		return saved_errno;
	}
	if (xwrite(slot->lock_fd, slot->key, slot->keylen + 1) < 0)
		return errno;
	return 0;
}

/* Release the current lockfile. If `replace_old_slot` is set the
 * lockfile replaces the old cache slot, otherwise the lockfile is
 * just deleted.
 */
static int unlock_slot(struct cache_slot *slot, int replace_old_slot)
{
	int err;

	if (replace_old_slot)
		err = rename(slot->lock_name, slot->cache_name);
	else
		err = unlink(slot->lock_name);

	/* Restore stdout and close the temporary FD. */
	if (slot->stdout_fd >= 0) {
		dup2(slot->stdout_fd, STDOUT_FILENO);
		close(slot->stdout_fd);
		slot->stdout_fd = -1;
	}

	if (err)
		return errno;

	return 0;
}

/* Generate the content for the current cache slot by redirecting
 * stdout to the lock-fd and invoking the callback function
 */
static int fill_slot(struct cache_slot *slot)
{
	/* Preserve stdout */
	slot->stdout_fd = dup(STDOUT_FILENO);
	if (slot->stdout_fd == -1)
		return errno;

	/* Redirect stdout to lockfile */
	if (dup2(slot->lock_fd, STDOUT_FILENO) == -1)
		return errno;

	/* Generate cache content */
	slot->fn();

	/* Make sure any buffered data is flushed to the file */
	if (fflush(stdout))
		return errno;

	/* update stat info */
	if (fstat(slot->lock_fd, &slot->cache_st))
		retu<style>pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */</style><div class="highlight"><pre><span></span><span class="ch">#!/bin/sh</span>

.<span class="w"> </span>./setup.sh

prepare_tests<span class="w"> </span><span class="s1">&#39;Validate cache&#39;</span>

run_test<span class="w"> </span><span class="s1">&#39;verify cache-size=0&#39;</span><span class="w"> </span><span class="s1">&#39;</span>

<span class="s1">	rm -f trash/cache/* &amp;&amp;</span>
<span class="s1">	sed -i -e &quot;s/cache-size=1021$/cache-size=0/&quot; trash/cgitrc &amp;&amp;</span>
<span class="s1">	cgit_url &quot;&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/refs&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/tree&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/log&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/diff&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/patch&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/refs&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/tree&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/log&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/diff&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/patch&quot; &amp;&amp;</span>
<span class="s1">	test 0 -eq $(ls trash/cache | wc -l)</span>
<span class="s1">&#39;</span>

run_test<span class="w"> </span><span class="s1">&#39;verify cache-size=1&#39;</span><span class="w"> </span><span class="s1">&#39;</span>

<span class="s1">	rm -f trash/cache/* &amp;&amp;</span>
<span class="s1">	sed -i -e &quot;s/cache-size=0$/cache-size=1/&quot; trash/cgitrc &amp;&amp;</span>
<span class="s1">	cgit_url &quot;&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/refs&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/tree&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/log&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/diff&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/patch&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/refs&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/tree&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/log&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/diff&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/patch&quot; &amp;&amp;</span>
<span class="s1">	test 1 -eq $(ls trash/cache | wc -l)</span>
<span class="s1">&#39;</span>

run_test<span class="w"> </span><span class="s1">&#39;verify cache-size=1021&#39;</span><span class="w"> </span><span class="s1">&#39;</span>

<span class="s1">	rm -f trash/cache/* &amp;&amp;</span>
<span class="s1">	sed -i -e &quot;s/cache-size=1$/cache-size=1021/&quot; trash/cgitrc &amp;&amp;</span>
<span class="s1">	cgit_url &quot;&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/refs&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/tree&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/log&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/diff&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;foo/patch&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/refs&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/tree&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/log&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/diff&quot; &amp;&amp;</span>
<span class="s1">	cgit_url &quot;bar/patch&quot; &amp;&amp;</span>
<span class="s1">	test 13 -eq $(ls trash/cache | wc -l)</span>
<span class="s1">&#39;</span>

tests_done
</pre></div>
</code></pre></td></tr></table>
</div> <!-- class=content -->
<div class='footer'>generated by <a href='https://git.zx2c4.com/cgit/about/'>cgit v1.2.3-54-g00ecf</a> (<a href='https://git-scm.com/'>git 2.39.0</a>) at 2025-01-08 13:24:35 +0000</div>
</div> <!-- id=cgit -->
</body>
</html>
