summaryrefslogtreecommitdiff
path: root/src/mod_magnet_cache.c
blob: f03d9a454501459b12a64b1789e70209a0da3ce0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#include "first.h"

#include "mod_magnet_cache.h"
#include "stat_cache.h"

#include <errno.h>
#include <stdlib.h>
#include <string.h>     /* strstr() */
#include <unistd.h>     /* lseek() read() */

#include <lualib.h>
#include <lauxlib.h>

__attribute_cold__
static script *script_init(void)
{
    return ck_calloc(1, sizeof(script));
}

__attribute_cold__
static void script_free(script *sc)
{
    if (!sc) return;
    lua_close(sc->L);
    free(sc->name.ptr);
    free(sc->etag.ptr);
    free(sc);
}

#if 0
script_cache *script_cache_init(void)
{
    return ck_calloc(1, sizeof(script_cache));
}
#endif

void script_cache_free_data(script_cache *p)
{
    if (!p) return;
    for (uint32_t i = 0; i < p->used; ++i)
        script_free(p->ptr[i]);
    free(p->ptr);
}

__attribute_cold__
__attribute_noinline__
static lua_State *script_cache_load_script(script * const sc, int etag_flags)
{
    /* read file and use luaL_loadbuffer()
     * eliminate TOC-TOU race w/ independent stat() in stat_cache_get_entry() */

    stat_cache_entry * const sce = stat_cache_get_entry_open(&sc->name, 1);
    buffer_clear(&sc->etag);
    if (NULL == sce || sce->fd < 0 || -1 == lseek(sce->fd, 0, SEEK_SET)) {
        /*(sce->fd < 0 might indicate empty file, which is not a valid script)*/
        if (NULL != sce) errno = EBADF;
        return NULL;
    }
    const buffer * const etag = stat_cache_etag_get(sce, etag_flags);
    if (etag)
        buffer_copy_buffer(&sc->etag, etag);

    const off_t sz = sce->st.st_size;
    char * const buf = ck_malloc(sz+1);

    ssize_t rd = 0;
    off_t off = 0;
    do {
        rd = read(sce->fd, buf+off, (size_t)(sz-off));
    } while (rd > 0 ? (off += rd) != sz : rd < 0 && errno == EINTR);
    if (off != sz) { /*(file truncated?)*/
        if (rd >= 0) errno = EIO;
        free(buf);
        return NULL;
    }

    /*(coarse heuristic to detect if script needs req_env initialized)*/
    buf[sz] = '\0'; /* for strstr() */
    sc->req_env_init = (NULL != strstr(buf, "req_env"));

    int rc = luaL_loadbuffer(sc->L, buf, (size_t)sz, sc->name.ptr);
    free(buf);

    if (0 != rc) {
        /* oops, an error, return it */
        return sc->L;
    }

    force_assert(lua_isfunction(sc->L, -1));
    return sc->L;
}

__attribute_cold__
__attribute_nonnull__()
__attribute_returns_nonnull__
static script *script_cache_new_script(script_cache * const cache, const buffer * const name)
{
    script * const sc = script_init();

    if (!(cache->used & (16-1)))
        ck_realloc_u32((void **)&cache->ptr,cache->used,16,sizeof(*cache->ptr));
    cache->ptr[cache->used++] = sc;

    buffer_copy_buffer(&sc->name, name);
    sc->L = luaL_newstate();
    luaL_openlibs(sc->L);
    return sc;
}

script *script_cache_get_script(script_cache *cache, const buffer *name)
{
    for (uint32_t i = 0; i < cache->used; ++i) {
        script * const sc = cache->ptr[i];
        if (buffer_is_equal(&sc->name, name))
            return sc;
    }
    return script_cache_new_script(cache, name);
}

lua_State *script_cache_check_script(script * const sc, int etag_flags)
{
    if (lua_gettop(sc->L) == 0)
        return script_cache_load_script(sc, etag_flags);

    /*force_assert(lua_gettop(sc->L) == 4);*/
    /*force_assert(lua_isfunction(sc->L, 1));*/

    stat_cache_entry * const sce = stat_cache_get_entry(&sc->name);
    if (NULL == sce) {
        lua_settop(sc->L, 0); /* pop the old function; clear stack */
        return script_cache_load_script(sc, etag_flags);
    }

    const buffer * const etag = stat_cache_etag_get(sce, etag_flags);
    if (NULL == etag || !buffer_is_equal(&sc->etag, etag)) {
        if (0 == etag_flags)
            return sc->L;
        /* the etag is outdated, reload the function */
        lua_settop(sc->L, 0); /* pop the old function; clear stack */
        return script_cache_load_script(sc, etag_flags);
    }

    return sc->L;
}