#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <asl.h>
#include <asl_private.h>
#include <asl_core.h>
#include <asl_store.h>
#include <notify.h>
extern time_t asl_parse_time(const char *str);
extern uint64_t asl_file_cursor(asl_file_t *s);
extern uint32_t asl_file_match_start(asl_file_t *s, uint64_t start_id, int32_t direction);
extern uint32_t asl_file_match_next(asl_file_t *s, aslresponse query, asl_msg_t **msg, uint64_t *last_id, int32_t direction, int32_t ruid, int32_t rgid);
extern int asl_file_create(const char *path, uid_t uid, gid_t gid, mode_t mode);
#define SECONDS_PER_DAY 86400
static time_t
_asl_start_today()
{
time_t now;
struct tm ctm;
memset(&ctm, 0, sizeof(struct tm));
now = time(NULL);
if (localtime_r((const time_t *)&now, &ctm) == NULL) return 0;
ctm.tm_sec = 0;
ctm.tm_min = 0;
ctm.tm_hour = 0;
return mktime(&ctm);
}
uint32_t
asl_store_open_write(const char *basedir, asl_store_t **s)
{
asl_store_t *out;
struct stat sb;
uint32_t i, flags;
char *path;
FILE *sd;
uint64_t last_id;
time_t start;
if (s == NULL) return ASL_STATUS_INVALID_ARG;
start = _asl_start_today();
if (start == 0) return ASL_STATUS_FAILED;
if (basedir == NULL) basedir = PATH_ASL_STORE;
memset(&sb, 0, sizeof(struct stat));
if (stat(basedir, &sb) != 0) return ASL_STATUS_INVALID_STORE;
if (!S_ISDIR(sb.st_mode)) return ASL_STATUS_INVALID_STORE;
path = NULL;
asprintf(&path, "%s/%s", basedir, FILE_ASL_STORE_DATA);
if (path == NULL) return ASL_STATUS_NO_MEMORY;
sd = NULL;
memset(&sb, 0, sizeof(struct stat));
if (stat(path, &sb) != 0)
{
if (errno != ENOENT)
{
free(path);
return ASL_STATUS_FAILED;
}
sd = fopen(path, "w+");
free(path);
if (sd == NULL) return ASL_STATUS_FAILED;
last_id = 0;
if (fwrite(&last_id, sizeof(uint64_t), 1, sd) != 1)
{
fclose(sd);
return ASL_STATUS_WRITE_FAILED;
}
flags = 0;
if (fwrite(&flags, sizeof(uint32_t), 1, sd) != 1)
{
fclose(sd);
return ASL_STATUS_WRITE_FAILED;
}
fflush(sd);
}
else
{
sd = fopen(path, "r+");
free(path);
if (sd == NULL) return ASL_STATUS_FAILED;
if (fread(&last_id, sizeof(uint64_t), 1, sd) != 1)
{
fclose(sd);
return ASL_STATUS_READ_FAILED;
}
last_id = asl_core_ntohq(last_id);
}
out = (asl_store_t *)calloc(1, sizeof(asl_store_t));
if (out == NULL)
{
fclose(sd);
return ASL_STATUS_NO_MEMORY;
}
if (basedir == NULL) out->base_dir = strdup(PATH_ASL_STORE);
else out->base_dir = strdup(basedir);
if (out->base_dir == NULL)
{
fclose(sd);
free(out);
return ASL_STATUS_NO_MEMORY;
}
out->start_today = start;
out->start_tomorrow = out->start_today + SECONDS_PER_DAY;
out->storedata = sd;
out->next_id = last_id + 1;
for (i = 0; i < FILE_CACHE_SIZE; i++)
{
memset(&out->file_cache[i], 0, sizeof(asl_cached_file_t));
out->file_cache[i].u = -1;
out->file_cache[i].g = -1;
}
*s = out;
return ASL_STATUS_OK;
}
uint32_t
asl_store_statistics(asl_store_t *s, aslmsg *msg)
{
aslmsg out;
if (s == NULL) return ASL_STATUS_INVALID_STORE;
if (msg == NULL) return ASL_STATUS_INVALID_ARG;
out = asl_new(ASL_TYPE_MSG);
if (out == NULL) return ASL_STATUS_NO_MEMORY;
*msg = out;
return ASL_STATUS_OK;
}
uint32_t
asl_store_open_read(const char *basedir, asl_store_t **s)
{
asl_store_t *out;
struct stat sb;
if (s == NULL) return ASL_STATUS_INVALID_ARG;
if (basedir == NULL) basedir = PATH_ASL_STORE;
memset(&sb, 0, sizeof(struct stat));
if (stat(basedir, &sb) != 0) return ASL_STATUS_INVALID_STORE;
if (!S_ISDIR(sb.st_mode)) return ASL_STATUS_INVALID_STORE;
out = (asl_store_t *)calloc(1, sizeof(asl_store_t));
if (out == NULL) return ASL_STATUS_NO_MEMORY;
if (basedir == NULL) out->base_dir = strdup(PATH_ASL_STORE);
else out->base_dir = strdup(basedir);
if (out->base_dir == NULL)
{
free(out);
return ASL_STATUS_NO_MEMORY;
}
*s = out;
return ASL_STATUS_OK;
}
uint32_t
asl_store_max_file_size(asl_store_t *s, size_t max)
{
if (s == NULL) return ASL_STATUS_INVALID_STORE;
s->max_file_size = max;
return ASL_STATUS_OK;
}
__private_extern__ void
asl_store_file_closeall(asl_store_t *s)
{
uint32_t i;
if (s == NULL) return;
for (i = 0; i < FILE_CACHE_SIZE; i++)
{
if (s->file_cache[i].f != NULL) asl_file_close(s->file_cache[i].f);
s->file_cache[i].f = NULL;
if (s->file_cache[i].path != NULL) free(s->file_cache[i].path);
s->file_cache[i].path = NULL;
s->file_cache[i].u = -1;
s->file_cache[i].g = -1;
s->file_cache[i].bb = 0;
s->file_cache[i].ts = 0;
}
}
uint32_t
asl_store_close(asl_store_t *s)
{
if (s == NULL) return ASL_STATUS_OK;
if (s->base_dir != NULL) free(s->base_dir);
s->base_dir = NULL;
asl_store_file_closeall(s);
if (s->storedata != NULL) fclose(s->storedata);
free(s);
return ASL_STATUS_OK;
}
uint32_t
asl_store_signal_sweep(asl_store_t *s)
{
char *str;
int semfd;
uint64_t xid;
uint32_t status;
if (s == NULL) return ASL_STATUS_INVALID_STORE;
asprintf(&str, "%s/%s", s->base_dir, FILE_ASL_STORE_SWEEP_SEMAPHORE);
if (str == NULL) return ASL_STATUS_NO_MEMORY;
semfd = open(str, O_WRONLY | O_CREAT | O_NONBLOCK, 0644);
free(str);
if (semfd < 0) return ASL_STATUS_WRITE_FAILED;
status = ASL_STATUS_OK;
xid = asl_core_htonq(s->next_id - 1);
if (write(semfd, &xid, sizeof(uint64_t)) != sizeof(uint64_t)) status = ASL_STATUS_WRITE_FAILED;
close(semfd);
return status;
}
static uint32_t
asl_store_file_cache_lru(asl_store_t *s, time_t now, uint32_t ignorex)
{
time_t min;
uint32_t i, x;
if (s == NULL) return 0;
x = 0;
min = now - FILE_CACHE_TTL;
for (i = 0; i < FILE_CACHE_SIZE; i++)
{
if ((i != ignorex) && (s->file_cache[i].ts < min))
{
asl_file_close(s->file_cache[i].f);
s->file_cache[i].f = NULL;
if (s->file_cache[i].path != NULL) free(s->file_cache[i].path);
s->file_cache[i].path = NULL;
s->file_cache[i].u = -1;
s->file_cache[i].g = -1;
s->file_cache[i].bb = 0;
s->file_cache[i].ts = 0;
}
if (s->file_cache[i].ts < s->file_cache[x].ts) x = i;
}
return x;
}
uint32_t
asl_store_sweep_file_cache(asl_store_t *s)
{
if (s == NULL) return ASL_STATUS_INVALID_STORE;
asl_store_file_cache_lru(s, time(NULL), FILE_CACHE_SIZE);
return ASL_STATUS_OK;
}
static char *
asl_store_make_ug_path(const char *dir, const char *base, const char *ext, uid_t ruid, gid_t rgid, uid_t *u, gid_t *g, mode_t *m)
{
char *path = NULL;
*u = 0;
*g = 0;
*m = 0644;
if (ruid == -1)
{
if (rgid == -1)
{
if (ext == NULL) asprintf(&path, "%s/%s", dir, base);
else asprintf(&path, "%s/%s.%s", dir, base, ext);
}
else
{
*g = rgid;
*m = 0600;
if (ext == NULL) asprintf(&path, "%s/%s.G%d", dir, base, *g);
else asprintf(&path, "%s/%s.G%d.%s", dir, base, *g, ext);
}
}
else
{
*u = ruid;
if (rgid == -1)
{
*m = 0600;
if (ext == NULL) asprintf(&path, "%s/%s.U%d", dir, base, *u);
else asprintf(&path, "%s/%s.U%d.%s", dir, base, *u, ext);
}
else
{
*g = rgid;
*m = 0600;
if (ext == NULL) asprintf(&path, "%s/%s.U%d.G%d", dir, base, *u, *g);
else asprintf(&path, "%s/%s.U%d.G%u.%s", dir, base, *u, *g, ext);
}
}
return path;
}
static uint32_t
asl_store_file_open_write(asl_store_t *s, char *tstring, int32_t ruid, int32_t rgid, time_t bb, asl_file_t **f, time_t now, uint32_t check_cache)
{
char *path;
mode_t m;
int32_t i, x;
uid_t u;
gid_t g;
uint32_t status;
asl_file_t *out;
if (s == NULL) return ASL_STATUS_INVALID_STORE;
for (i = 0; i < FILE_CACHE_SIZE; i++)
{
if ((s->file_cache[i].u == ruid) && (s->file_cache[i].g == rgid) && (s->file_cache[i].bb == bb) && (s->file_cache[i].f != NULL))
{
s->file_cache[i].ts = now;
*f = s->file_cache[i].f;
if (check_cache == 1) asl_store_file_cache_lru(s, now, i);
return ASL_STATUS_OK;
}
}
u = 0;
g = 0;
m = 0644;
path = asl_store_make_ug_path(s->base_dir, tstring, "asl", (uid_t)ruid, (gid_t)rgid, &u, &g, &m);
if (path == NULL) return ASL_STATUS_NO_MEMORY;
out = NULL;
status = asl_file_open_write(path, m, u, g, &out);
if (status != ASL_STATUS_OK)
{
free(path);
return status;
}
x = asl_store_file_cache_lru(s, now, FILE_CACHE_SIZE);
if (s->file_cache[x].f != NULL) asl_file_close(s->file_cache[x].f);
if (s->file_cache[x].path != NULL) free(s->file_cache[x].path);
s->file_cache[x].f = out;
s->file_cache[x].path = path;
s->file_cache[x].u = ruid;
s->file_cache[x].g = rgid;
s->file_cache[x].bb = bb;
s->file_cache[x].ts = time(NULL);
*f = out;
return ASL_STATUS_OK;
}
__private_extern__ char *
asl_store_file_path(asl_store_t *s, asl_file_t *f)
{
uint32_t i;
if (s == NULL) return NULL;
for (i = 0; i < FILE_CACHE_SIZE; i++)
{
if (s->file_cache[i].f == f)
{
if (s->file_cache[i].path == NULL) return NULL;
return strdup(s->file_cache[i].path);
}
}
return NULL;
}
__private_extern__ void
asl_store_file_close(asl_store_t *s, asl_file_t *f)
{
uint32_t i;
if (s == NULL) return;
if (f == NULL) return;
for (i = 0; i < FILE_CACHE_SIZE; i++)
{
if (s->file_cache[i].f == f)
{
asl_file_close(s->file_cache[i].f);
s->file_cache[i].f = NULL;
if (s->file_cache[i].path != NULL) free(s->file_cache[i].path);
s->file_cache[i].path = NULL;
s->file_cache[i].u = -1;
s->file_cache[i].g = -1;
s->file_cache[i].bb = 0;
s->file_cache[i].ts = 0;
return;
}
}
}
uint32_t
asl_store_save(asl_store_t *s, aslmsg msg)
{
struct tm ctm;
time_t msg_time, now, bb;
char *path, *tmp_path, *tstring, *scratch;
const char *val;
uid_t ruid;
gid_t rgid;
asl_file_t *f;
uint32_t status, check_cache, signal_sweep, len;
uint64_t xid, ftime;
size_t fsize;
if (s == NULL) return ASL_STATUS_INVALID_STORE;
if (msg == NULL) return ASL_STATUS_INVALID_ARG;
now = time(NULL);
check_cache = 0;
if ((s->last_write + FILE_CACHE_TTL) <= now) check_cache = 1;
signal_sweep = 0;
msg_time = 0;
val = asl_get(msg, ASL_KEY_TIME);
if (val == NULL) msg_time = now;
else msg_time = asl_parse_time(val);
if (msg_time >= s->start_tomorrow)
{
if (now >= s->start_tomorrow)
{
check_cache = 0;
signal_sweep = 1;
asl_store_file_closeall(s);
s->start_today = _asl_start_today();
if (s->start_today == 0) s->start_today = s->start_tomorrow;
s->start_tomorrow = s->start_today + SECONDS_PER_DAY;
}
}
val = asl_get(msg, ASL_KEY_READ_UID);
ruid = -1;
if (val != NULL) ruid = atoi(val);
val = asl_get(msg, ASL_KEY_READ_GID);
rgid = -1;
if (val != NULL) rgid = atoi(val);
bb = 0;
val = asl_get(msg, ASL_KEY_EXPIRE_TIME);
if (val != NULL)
{
bb = 1;
msg_time = asl_parse_time(val);
}
if (fseeko(s->storedata, 0, SEEK_SET) != 0) return ASL_STATUS_WRITE_FAILED;
xid = asl_core_htonq(s->next_id);
if (fwrite(&xid, sizeof(uint64_t), 1, s->storedata) != 1) return ASL_STATUS_WRITE_FAILED;
fflush(s->storedata);
xid = s->next_id;
s->next_id++;
s->last_write = now;
if (localtime_r((const time_t *)&msg_time, &ctm) == NULL) return ASL_STATUS_FAILED;
tstring = NULL;
if (bb == 1)
{
ctm.tm_sec = 0;
ctm.tm_min = 0;
ctm.tm_hour = 0;
ctm.tm_mday = 0;
ctm.tm_mon += 1;
bb = mktime(&ctm);
if (localtime_r((const time_t *)&bb, &ctm) == NULL) return ASL_STATUS_FAILED;
asprintf(&tstring, "BB.%d.%02d.%02d", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
}
else
{
asprintf(&tstring, "%d.%02d.%02d", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
}
if (tstring == NULL) return ASL_STATUS_NO_MEMORY;
status = asl_store_file_open_write(s, tstring, ruid, rgid, bb, &f, now, check_cache);
free(tstring);
tstring = NULL;
if (status != ASL_STATUS_OK) return status;
status = asl_file_save(f, msg, &xid);
if (status != ASL_STATUS_OK) return status;
fsize = asl_file_size(f);
ftime = asl_file_ctime(f);
if ((s->max_file_size != 0) && (fsize > s->max_file_size))
{
signal_sweep = 1;
status = ASL_STATUS_OK;
path = asl_store_file_path(s, f);
asl_store_file_close(s, f);
if (path != NULL)
{
tmp_path = NULL;
len = strlen(path);
if ((len >= 4) && (!strcmp(path + len - 4, ".asl")))
{
scratch = strdup(path);
if (scratch != NULL)
{
scratch[len - 4] = '\0';
asprintf(&tmp_path, "%s.%llu.asl", scratch, ftime);
free(scratch);
}
}
else
{
asprintf(&tmp_path, "%s.%llu", path, ftime);
}
if (tmp_path == NULL)
{
status = ASL_STATUS_NO_MEMORY;
}
else
{
if (rename(path, tmp_path) != 0) status = ASL_STATUS_FAILED;
free(tmp_path);
}
free(path);
}
}
if (signal_sweep != 0) asl_store_signal_sweep(s);
return status;
}
static uint32_t
asl_store_mkdir(asl_store_t *s, const char *dir, mode_t m)
{
char *tstring = NULL;
int status;
struct stat sb;
asprintf(&tstring, "%s/%s", s->base_dir, dir);
if (tstring == NULL) return ASL_STATUS_NO_MEMORY;
memset(&sb, 0, sizeof(struct stat));
status = stat(tstring, &sb);
if (status == 0)
{
if (!S_ISDIR(sb.st_mode))
{
free(tstring);
return ASL_STATUS_INVALID_STORE;
}
}
else
{
if (errno == ENOENT)
{
if (mkdir(tstring, m) != 0)
{
free(tstring);
return ASL_STATUS_WRITE_FAILED;
}
}
else
{
free(tstring);
return ASL_STATUS_FAILED;
}
}
free(tstring);
return ASL_STATUS_OK;
}
uint32_t
asl_store_open_aux(asl_store_t *s, aslmsg msg, int *out_fd, char **url)
{
struct tm ctm;
time_t msg_time, bb;
char *path, *dir, *tstring;
const char *val;
uid_t ruid, u;
gid_t rgid, g;
mode_t m;
uint32_t status;
uint64_t fid;
int fd;
if (s == NULL) return ASL_STATUS_INVALID_STORE;
if (msg == NULL) return ASL_STATUS_INVALID_ARG;
if (out_fd == NULL) return ASL_STATUS_INVALID_ARG;
if (url == NULL) return ASL_STATUS_INVALID_ARG;
msg_time = time(NULL);
val = asl_get(msg, ASL_KEY_READ_UID);
ruid = -1;
if (val != NULL) ruid = atoi(val);
val = asl_get(msg, ASL_KEY_READ_GID);
rgid = -1;
if (val != NULL) rgid = atoi(val);
bb = 0;
val = asl_get(msg, ASL_KEY_EXPIRE_TIME);
if (val != NULL)
{
bb = 1;
msg_time = asl_parse_time(val);
}
if (localtime_r((const time_t *)&msg_time, &ctm) == NULL) return ASL_STATUS_FAILED;
dir = NULL;
if (bb == 1)
{
ctm.tm_sec = 0;
ctm.tm_min = 0;
ctm.tm_hour = 0;
ctm.tm_mday = 0;
ctm.tm_mon += 1;
bb = mktime(&ctm);
if (localtime_r((const time_t *)&bb, &ctm) == NULL) return ASL_STATUS_FAILED;
asprintf(&dir, "BB.AUX.%d.%02d.%02d", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
}
else
{
asprintf(&dir, "AUX.%d.%02d.%02d", ctm.tm_year + 1900, ctm.tm_mon + 1, ctm.tm_mday);
}
if (dir == NULL) return ASL_STATUS_NO_MEMORY;
status = asl_store_mkdir(s, dir, 0755);
if (status != ASL_STATUS_OK)
{
free(dir);
return status;
}
fid = s->next_id;
s->next_id++;
tstring = NULL;
asprintf(&tstring, "%s/%llu", dir, fid);
free(dir);
if (tstring == NULL) return ASL_STATUS_NO_MEMORY;
u = 0;
g = 0;
m = 0644;
path = asl_store_make_ug_path(s->base_dir, tstring, NULL, ruid, rgid, &u, &g, &m);
free(tstring);
if (path == NULL) return ASL_STATUS_NO_MEMORY;
fd = asl_file_create(path, u, g, m);
if (fd < 0)
{
free(path);
*out_fd = -1;
return ASL_STATUS_WRITE_FAILED;
}
*url = NULL;
asprintf(url, "file://%s", path);
free(path);
*out_fd = fd;
return status;
}
uint32_t
asl_store_match_timeout(asl_store_t *s, aslresponse query, aslresponse *res, uint64_t *last_id, uint64_t start_id, uint32_t count, int32_t direction, uint32_t usec)
{
DIR *dp;
struct dirent *dent;
uint32_t status;
asl_file_t *f;
char *path;
asl_file_list_t *files;
if (s == NULL) return ASL_STATUS_INVALID_STORE;
if (res == NULL) return ASL_STATUS_INVALID_ARG;
files = NULL;
dp = opendir(s->base_dir);
if (dp == NULL) return ASL_STATUS_READ_FAILED;
while ((dent = readdir(dp)) != NULL)
{
if (dent->d_name[0] == '.') continue;
path = NULL;
asprintf(&path, "%s/%s", s->base_dir, dent->d_name);
status = asl_file_open_read(path, &f);
if (path != NULL) free(path);
if ((status != ASL_STATUS_OK) || (f == NULL)) continue;
files = asl_file_list_add(files, f);
}
closedir(dp);
status = asl_file_list_match_timeout(files, query, res, last_id, start_id, count, direction, usec);
asl_file_list_close(files);
return status;
}
uint32_t
asl_store_match(asl_store_t *s, aslresponse query, aslresponse *res, uint64_t *last_id, uint64_t start_id, uint32_t count, int32_t direction)
{
return asl_store_match_timeout(s, query, res, last_id, start_id, count, direction, 0);
}
uint32_t
asl_store_match_start(asl_store_t *s, uint64_t start_id, int32_t direction)
{
DIR *dp;
struct dirent *dent;
uint32_t status;
asl_file_t *f;
char *path;
asl_file_list_t *files;
if (s == NULL) return ASL_STATUS_INVALID_STORE;
if (s->work != NULL) asl_file_list_match_end(s->work);
s->work = NULL;
files = NULL;
dp = opendir(s->base_dir);
if (dp == NULL) return ASL_STATUS_READ_FAILED;
while ((dent = readdir(dp)) != NULL)
{
if (dent->d_name[0] == '.') continue;
path = NULL;
asprintf(&path, "%s/%s", s->base_dir, dent->d_name);
status = asl_file_open_read(path, &f);
if (path != NULL) free(path);
if ((status != ASL_STATUS_OK) || (f == NULL)) continue;
files = asl_file_list_add(files, f);
}
closedir(dp);
s->work = asl_file_list_match_start(files, start_id, direction);
if (s->work == NULL) return ASL_STATUS_FAILED;
return ASL_STATUS_OK;
}
uint32_t
asl_store_match_next(asl_store_t *s, aslresponse query, aslresponse *res, uint32_t count)
{
if (s == NULL) return ASL_STATUS_INVALID_STORE;
if (s->work == NULL) return ASL_STATUS_OK;
return asl_file_list_match_next(s->work, query, res, count);
}