#include "cache.h"
#include "shared/util.h"
#include "shared/log.h" // only LOG_DEBUG
#include "error.h"
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <assert.h>
int pt_cache_new (struct pt_cache **cache_ptr, const char *path, int mode)
{
struct pt_cache *cache;
int err;
// alloc
if ((cache = calloc(1, sizeof(*cache))) == NULL)
RETURN_ERROR(PT_ERR_MEM);
if ((cache->path = strdup(path)) == NULL)
JUMP_SET_ERROR(err, PT_ERR_MEM);
// init
cache->fd = -1;
cache->mode = mode;
// ok
*cache_ptr = cache;
return 0;
error:
// cleanup
if (cache)
pt_cache_destroy(cache);
return err;
}
/**
* Open the cache file as an fd for reading
*
* XXX: use some kind of locking?
*/
static int pt_cache_open_read_fd (struct pt_cache *cache, int *fd_ptr)
{
int fd;
// actual open()
if ((fd = open(cache->path, O_RDONLY)) < 0)
RETURN_ERROR_ERRNO(PT_ERR_OPEN_MODE, EACCES);
// ok
*fd_ptr = fd;
return 0;
}
/**
* Read in the cache header from the open file
*/
static int pt_cache_read_header (int fd, struct pt_cache_header *header)
{
size_t len = sizeof(*header);
char *buf = (char *) header;
// seek to start
if (lseek(fd, 0, SEEK_SET) != 0)
RETURN_ERROR(PT_ERR_CACHE_SEEK);
// write out full header
while (len) {
ssize_t ret;
// try and write out the header
if ((ret = read(fd, buf, len)) <= 0)
RETURN_ERROR(PT_ERR_CACHE_READ);
// update offset
buf += ret;
len -= ret;
}
// done
return 0;
}
/**
* Read and return the version number from the cache file, temporarily opening it if needed
*/
static int pt_cache_version (struct pt_cache *cache)
{
int fd;
struct pt_cache_header header;
int ret;
// already open?
if (cache->file)
return cache->file->header.version;
// temp. open
if ((ret = pt_cache_open_read_fd(cache, &fd)))
return ret;
// read header
if ((ret = pt_cache_read_header(fd, &header)))
JUMP_ERROR(ret);
// ok
ret = header.version;
error:
// close
close(fd);
return ret;
}
int pt_cache_status (struct pt_cache *cache, const char *img_path)
{
struct stat st_img, st_cache;
int ver;
// test original file
if (stat(img_path, &st_img) < 0)
RETURN_ERROR(PT_ERR_IMG_STAT);
// test cache file
if (stat(cache->path, &st_cache) < 0) {
// always stale if it doesn't exist yet
if (errno == ENOENT)
return PT_CACHE_NONE;
else
RETURN_ERROR(PT_ERR_CACHE_STAT);
}
// compare mtime
if (st_img.st_mtime > st_cache.st_mtime)
return PT_CACHE_STALE;
// read version
if ((ver = pt_cache_version(cache)) < 0)
// fail
return ver;
// compare version
if (ver != PT_CACHE_VERSION)
return PT_CACHE_INCOMPAT;
// ok, should be in order
return PT_CACHE_FRESH;
}
void pt_cache_info (struct pt_cache *cache, struct pt_image_info *info)
{
struct stat st;
if (cache->file)
// img info
pt_png_info(&cache->file->header.png, info);
// stat
if (stat(cache->path, &st) < 0) {
// unknown
info->cache_mtime = 0;
info->cache_bytes = 0;
info->cache_blocks = 0;
} else {
// store
info->cache_version = pt_cache_version(cache);
info->cache_mtime = st.st_mtime;
info->cache_bytes = st.st_size;
info->cache_blocks = st.st_blocks;
}
}
/**
* Abort any incomplete open operation, cleaning up
*/
static void pt_cache_abort (struct pt_cache *cache)
{
if (cache->file != NULL) {
munmap(cache->file, sizeof(struct pt_cache_file) + cache->file->header.data_size);
cache->file = NULL;
}
if (cache->fd >= 0) {
close(cache->fd);
cache->fd = -1;
}
}
/**
* Open the .tmp cache file as an fd for writing
*/
static int pt_cache_open_tmp_fd (struct pt_cache *cache, int *fd_ptr)
{
int fd;
char tmp_path[1024];
// get .tmp path
if (path_with_fext(cache->path, tmp_path, sizeof(tmp_path), ".tmp"))
RETURN_ERROR(PT_ERR_PATH);
// open for write, create
// XXX: locking? At least O_EXCL...
if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0)
RETURN_ERROR(PT_ERR_CACHE_OPEN_TMP);
// ok
*fd_ptr = fd;
return 0;
}
/**
* Mmap the pt_cache_file using sizeof(struct pt_cache_file) + data_size
*/
static int pt_cache_open_mmap (struct pt_cache *cache, void **addr_ptr, size_t data_size, bool readonly)
{
int prot = 0;
void *addr;
// determine prot
prot |= PROT_READ;
if (!readonly) {
assert(cache->mode & PT_OPEN_UPDATE);
prot |= PROT_WRITE;
}
// mmap() the full file including header
if ((addr = mmap(NULL, sizeof(struct pt_cache_file) + data_size, prot, MAP_SHARED, cache->fd, 0)) == MAP_FAILED)
RETURN_ERROR(PT_ERR_CACHE_MMAP);
// ok
*addr_ptr = addr;
return 0;
}
int pt_cache_open (struct pt_cache *cache)
{
struct pt_cache_header header;
int err;
// ignore if already open
if (cache->file)
return 0;
// open the .cache in readonly mode
if ((err = pt_cache_open_read_fd(cache, &cache->fd)))
return err;
// read in header
if ((err = pt_cache_read_header(cache->fd, &header)))
JUMP_ERROR(err);
// check version
if (header.version != PT_CACHE_VERSION)
JUMP_SET_ERROR(err, PT_ERR_CACHE_VERSION);
// mmap the header + data
if ((err = pt_cache_open_mmap(cache, (void **) &cache->file, header.data_size, true)))
JUMP_ERROR(err);
// done
return 0;
error:
// cleanup
pt_cache_abort(cache);
return err;
}
/**
* Write out the cache header into the opened file
*/
static int pt_cache_write_header (struct pt_cache *cache, const struct pt_cache_header *header)
{
size_t len = sizeof(*header);
const char *buf = (const char *) header;
// seek to start
if (lseek(cache->fd, 0, SEEK_SET) != 0)
RETURN_ERROR(PT_ERR_CACHE_SEEK);
// write out full header
while (len) {
ssize_t ret;
// try and write out the header
if ((ret = write(cache->fd, buf, len)) <= 0)
RETURN_ERROR(PT_ERR_CACHE_WRITE);
// update offset
buf += ret;
len -= ret;
}
// done
return 0;
}
/**
* Create a new .tmp cache file, open it, and write out the header.
*/
static int pt_cache_create (struct pt_cache *cache, struct pt_cache_header *header)
{
int err;
// no access
if (!(cache->mode & PT_OPEN_UPDATE))
RETURN_ERROR(PT_ERR_OPEN_MODE);
// open as .tmp
if ((err = pt_cache_open_tmp_fd(cache, &cache->fd)))
return err;
// write header
if ((err = pt_cache_write_header(cache, header)))
JUMP_ERROR(err);
// grow file
if (ftruncate(cache->fd, sizeof(struct pt_cache_file) + header->data_size) < 0)
JUMP_SET_ERROR(err, PT_ERR_CACHE_TRUNC);
// mmap header and data
if ((err = pt_cache_open_mmap(cache, (void **) &cache->file, header->data_size, false)))
JUMP_ERROR(err);
// done
return 0;
error:
// cleanup
pt_cache_abort(cache);
return err;
}
/**
* Rename the opened .tmp to .cache
*/
static int pt_cache_create_done (struct pt_cache *cache)
{
char tmp_path[1024];
// get .tmp path
if (path_with_fext(cache->path, tmp_path, sizeof(tmp_path), ".tmp"))
RETURN_ERROR(PT_ERR_PATH);
// rename
if (rename(tmp_path, cache->path) < 0)
RETURN_ERROR(PT_ERR_CACHE_RENAME_TMP);
// ok
return 0;
}
int pt_cache_update (struct pt_cache *cache, struct pt_png_img *img, const struct pt_image_params *params)
{
struct pt_cache_header header;
int err;
// XXX: check cache_mode
// XXX: close any already-opened cache file
// prep header
header.version = PT_CACHE_VERSION;
header.format = PT_IMG_PNG;
// read img header
if ((err = pt_png_read_header(img, &header.png, &header.data_size)))
return err;
// save any params
if (params)
header.params = *params;
// create/open .tmp and write out header
if ((err = pt_cache_create(cache, &header)))
return err;
// decode to disk
if ((err = pt_png_decode(img, &cache->file->header.png, &cache->file->header.params, cache->file->data)))
return err;
// done, commit .tmp
if ((err = pt_cache_create_done(cache)))
// XXX: pt_cache_abort?
return err;
return 0;
}
int pt_cache_tile (struct pt_cache *cache, struct pt_tile *tile)
{
int err;
// ensure open
if ((err = pt_cache_open(cache)))
return err;
// render
if ((err = pt_png_tile(&cache->file->header.png, cache->file->data, tile)))
return err;
return 0;
}
void pt_cache_destroy (struct pt_cache *cache)
{
free(cache->path);
pt_cache_abort(cache);
free(cache);
}