
/*
 * Copyright (C) Xiaozhe Wang (chaoslawful)
 * Copyright (C) Yichun Zhang (agentzh)
 */


#ifndef DDEBUG
#define DDEBUG 0
#endif
#include "ddebug.h"


#include <nginx.h>
#include "ngx_http_lua_clfactory.h"


#define CLFACTORY_BEGIN_CODE "return function() "
#define CLFACTORY_BEGIN_SIZE (sizeof(CLFACTORY_BEGIN_CODE) - 1)

#define CLFACTORY_END_CODE "\nend"
#define CLFACTORY_END_SIZE (sizeof(CLFACTORY_END_CODE) - 1)


/*
 * taken from chaoslawful:
 * Lua bytecode header        Luajit bytecode header
 * --------------              --------------
 * |  \033Lua   | 0-3          |  \033LJ    | 0-2
 * --------------              --------------
 * |    LuaC    | 4            |  bytecode  | 3
 * |   Version  |              |   version  |
 * --------------              --------------
 * |    LuaC    | 5            |  misc flag | 4 [F|S|B]
 * |   Format   |              --------------
 * --------------              |  chunkname | ULEB128 var-len
 * |   Endian   | 6            |     len    | encoded uint32
 * --------------              --------------
 * |   size of  | 7            |  chunkname |
 * |     int    |              |  str no \0 |
 * --------------              --------------
 * |   size of  | 8
 * |    size_t  |
 * --------------
 * |   size of  | 9
 * | instruction|
 * --------------
 * |   size of  | 10
 * |   number   |
 * --------------
 * |   number   | 11
 * |   is int?  |
 * --------------
*/


/*
 * CLOSURE 0 0 RETURN 0 2 RETURN 0 1
 * length(Instruction) = 4 or 8
 * little endian or big endian
*/
#define    LUA_LITTLE_ENDIAN_4BYTES_CODE                                     \
    "\x24\x00\x00\x00\x1e\x00\x00\x01\x1e\x00\x80\x00"
#define    LUA_LITTLE_ENDIAN_8BYTES_CODE                                     \
    "\x24\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x01"                       \
    "\x00\x00\x00\x00\x1e\x00\x80\x00\x00\x00\x00\x00"
#define    LUA_BIG_ENDIAN_4BYTES_CODE                                        \
    "\x00\x00\x00\x24\x01\x00\x00\x1e\x00\x08\x00\x1e"
#define    LUA_BIG_ENDIAN_8BYTES_CODE                                        \
    "\x00\x00\x00\x00\x00\x00\x00\x24\x00\x00\x00\x00"                       \
    "\x01\x00\x00\x1e\x00\x00\x00\x00\x00\x08\x00\x1e"
#define    LUA_LITTLE_ENDIAN_4BYTES_CODE_LEN        (4 + 4 + 4)
#define    LUA_LITTLE_ENDIAN_8BYTES_CODE_LEN        (8 + 8 + 8)
#define    LUA_BIG_ENDIAN_4BYTES_CODE_LEN           (4 + 4 + 4)
#define    LUA_BIG_ENDIAN_8BYTES_CODE_LEN           (8 + 8 + 8)
#define    LUAC_HEADERSIZE         12
#define    LUAC_VERSION            0x51


/*
 * taken from chaoslawful:
 *  Lua Proto
 * ---------------------
 * | String            | Can be empty string
 * | [source]          | (stripped or internal function)
 * ---------------------
 * | Int               | At which line this function is defined
 * | [linedefined]     |
 * ---------------------
 * | Int               | At while line this function definition ended
 * | [lastlinedefined] |
 * ---------------------
 * | Char              | Number of upvalues referenced by this function
 * | [nups]            |
 * ---------------------
 * | Char              | Number of parameters of this function
 * | [numparams]       |
 * ---------------------
 * | Char              | Does this function has variable number of arguments?
 * | [is_var_arg]      | main function always set to VARARG_ISVARARG (2)
 * ---------------------
 * | Char              | Maximum stack size this function used
 * | [maxstacksize]    | Initially set to 2
 * ---------------------
 * | Vector(instr)     | Code instructions of this function
 * | [code]            |
 * ---------------------
 * | Int               | Number of constants referenced by this function
 * | [sizek]           |
 * ---------------------
 * | Char              | ------------------------------------
 * | type of [k[i]]    |  The type and content of constants |
 * ---------------------                                    |-> repeat for i in
 * | Char if boolean   |  No content part if type is NIL    |   [1..sizek]
 * | Number if number  | ------------------------------------
 * | String if string  |
 * ---------------------
 * | Int               | Number of internal functions
 * | [sizep]           |
 * ---------------------
 * | Function          | -> repeat for i in [1..sizep]
 * | at [p[i]]         |
 * ---------------------
 * | Vector            | Debug lineinfo vector
 * | [lineinfo]        | Empty vector here if dubug info is stripped
 * ---------------------
 * | Int               | Number of local variable in this function
 * | [sizelocvars]     | 0 if debug info is stripped
 * ---------------------
 * | String            | ------------------------------------
 * | [locvars[i]]      |  Name of local var i               |
 * |  .varname]        |                                    |
 * ---------------------                                    |
 * | Int               |  instruction counter               |
 * | [locvars[i]]      |  where lcoal var i start to be     |-> repeat for i in
 * |  .startpc]        |  referenced                        |  [0..sizelocvars]
 * ---------------------                                    |
 * | Int               |  instruction counter, where local  |
 * | [locvars[i]]      |  var i ceased to be referenced     |
 * |  .endpc]          | ------------------------------------
 * ---------------------
 * | Int               | Number of upvalues referenced by this function,
 * | [sizeupvalues]    | 0 if stripped
 * ---------------------
 * | String            | -> repeat for i in[0..sizeupvalues]
 * | [upvalues[i]]     |
 * ---------------------
*/

#define    POS_SOURCE_STR_LEN      LUAC_HEADERSIZE
#define    POS_START_LINE          (POS_SOURCE_STR_LEN + sizeof(size_t))
#define    POS_LAST_LINE           (POS_START_LINE + sizeof(int))
#define    POS_NUM_OF_UPVS         (POS_LAST_LINE + sizeof(int))
#define    POS_NUM_OF_PARA         (POS_NUM_OF_UPVS + sizeof(char))
#define    POS_IS_VAR_ARG          (POS_NUM_OF_PARA + sizeof(char))
#define    POS_MAX_STACK_SIZE      (POS_IS_VAR_ARG + sizeof(char))
#define    POS_NUM_OF_INST         (POS_MAX_STACK_SIZE +sizeof(char))
#define    POS_BYTECODE            (POS_NUM_OF_INST + sizeof(int))
#define    MAX_BEGIN_CODE_SIZE                                               \
    (POS_BYTECODE + LUA_LITTLE_ENDIAN_8BYTES_CODE_LEN                        \
    + sizeof(int) + sizeof(int))
#define    MAX_END_CODE_SIZE       (sizeof(int) + sizeof(int) + sizeof(int))

/*
 * taken from chaoslawful:
 * Luajit bytecode format
 * ---------------------
 * | HEAD              | Luajit bytecode head
 * ---------------------
 * | Internal          | All internal functions
 * | functions         |
 * ---------------------
 * | ULEB128           | Rest data total length of this function
 * | [Date len of      | (not include itself)
 * |  this function]   |
 * ---------------------
 * | Char              | F(ffi) | V(vararg)| C(has internal funcs)
 * | [func flag]       |
 * ---------------------
 * | Char              | Number of parameters of this function
 * | [numparams]       |
 * ---------------------
 * | Char              |
 * | [framesize]       |
 * ---------------------
 * | Char              | Number of upvalues referenced by this function
 * | [sizeupvalues]    |
 * ---------------------
 * | ULEB128           | Number of collectable constants referenced
 * | [sizekgc]         | by this function
 * ---------------------
 * | ULEB128           | Number of lua number constants referenced
 * | [sizekn]          | by this function
 * ---------------------
 * | ULEB128           | Number of bytecode instructions of this function
 * | [sizebc]m1        | minus 1 to omit the BC_FUNCV/BC_FUNCF header bytecode
 * ---------------------
 * | ULEB128           |
 * | [size of dbg      | Size of debug lineinfo map, available when not stripped
 * |  lineinfo]        |
 * ---------------------
 * | ULEB128           | Available when not stripped
 * | [firstline]       | The first line of this function's definition
 * ---------------------
 * | ULEB128           | Available when not stripped
 * | [numline]         | The number of lines of this function's definition
 * ---------------------
 * | [bytecode]        | Bytecode instructions of this function
 * ---------------------
 * |[upvalue ref slots]| [sizeupvalues] * 2
 * ---------------------
 * | [collectable      | [sizekgc] elems, variable length
 * |  constants]       |
 * ---------------------
 * | [lua number       | [sizekn] elems, variable length
 * |  constants]       |
 * ---------------------
 * | [debug lineinfo   | Length is the calculated size of debug lineinfo above
 * |                   | Only available if not stripped
 * ---------------------
 * | Char              |
 * | [\x00]            | Footer
 * ---------------------
*/

/* bytecode for luajit 2.0 */

#define    LJ20_LITTLE_ENDIAN_CODE_STRIPPED                                  \
    "\x14\x03\x00\x01\x00\x01\x00\x03"                                       \
    "\x31\x00\x00\x00\x30\x00\x00\x80\x48\x00\x02\x00"                       \
    "\x00\x00"

#define    LJ20_BIG_ENDIAN_CODE_STRIPPED                                     \
    "\x14\x03\x00\x01\x00\x01\x00\x03"                                       \
    "\x00\x00\x00\x31\x80\x00\x00\x30\x00\x02\x00\x48"                       \
    "\x00\x00"

#define    LJ20_LITTLE_ENDIAN_CODE                                           \
    "\x15\x03\x00\x01\x00\x01\x00\x03\x00"                                   \
    "\x31\x00\x00\x00\x30\x00\x00\x80\x48\x00\x02\x00"                       \
    "\x00\x00"

#define    LJ20_BIG_ENDIAN_CODE                                              \
    "\x15\x03\x00\x01\x00\x01\x00\x03\x00"                                   \
    "\x00\x00\x00\x31\x80\x00\x00\x30\x00\x02\x00\x48"                       \
    "\x00\x00"

/* bytecode for luajit 2.1 */

#define    LJ21_LITTLE_ENDIAN_CODE_STRIPPED                                  \
    "\x14\x03\x00\x01\x00\x01\x00\x03"                                       \
    "\x33\x00\x00\x00\x32\x00\x00\x80\x4c\x00\x02\x00"                       \
    "\x00\x00"

#define    LJ21_BIG_ENDIAN_CODE_STRIPPED                                     \
    "\x14\x03\x00\x01\x00\x01\x00\x03"                                       \
    "\x00\x00\x00\x33\x80\x00\x00\x32\x00\x02\x00\x4c"                       \
    "\x00\x00"

#define    LJ21_LITTLE_ENDIAN_CODE                                           \
    "\x15\x03\x00\x01\x00\x01\x00\x03\x00"                                   \
    "\x33\x00\x00\x00\x32\x00\x00\x80\x4c\x00\x02\x00"                       \
    "\x00\x00"

#define    LJ21_BIG_ENDIAN_CODE                                              \
    "\x15\x03\x00\x01\x00\x01\x00\x03\x00"                                   \
    "\x00\x00\x00\x33\x80\x00\x00\x32\x00\x02\x00\x4c"                       \
    "\x00\x00"

#define    LJ_CODE_LEN              23
#define    LJ_CODE_LEN_STRIPPED     22
#define    LJ_HEADERSIZE            5
#define    LJ_BCDUMP_F_BE           0x01
#define    LJ_BCDUMP_F_STRIP        0x02
#define    LJ21_BCDUMP_VERSION        2
#define    LJ20_BCDUMP_VERSION        1
#define    LJ_SIGNATURE             "\x1b\x4c\x4a"


typedef enum {
    NGX_LUA_TEXT_FILE,
    NGX_LUA_BT_LUA,
    NGX_LUA_BT_LJ
} ngx_http_lua_clfactory_file_type_e;


enum {
    NGX_LUA_READER_BUFSIZE = 4096
};


typedef struct {
    ngx_http_lua_clfactory_file_type_e file_type;

    int         sent_begin;
    int         sent_end;
    int         extraline;
    FILE       *f;
    size_t      begin_code_len;
    size_t      end_code_len;
    size_t      rest_len;
    union {
        char   *ptr;
        char    str[MAX_BEGIN_CODE_SIZE];
    }           begin_code;
    union {
        char   *ptr;
        char    str[MAX_END_CODE_SIZE];
    }           end_code;
    char        buff[NGX_LUA_READER_BUFSIZE];
} ngx_http_lua_clfactory_file_ctx_t;


typedef struct {
    int         sent_begin;
    int         sent_end;
    const char *s;
    size_t      size;
} ngx_http_lua_clfactory_buffer_ctx_t;


static const char *ngx_http_lua_clfactory_getF(lua_State *L, void *ud,
    size_t *size);
static int ngx_http_lua_clfactory_errfile(lua_State *L, const char *what,
    int fname_index);
static const char *ngx_http_lua_clfactory_getS(lua_State *L, void *ud,
    size_t *size);
static long ngx_http_lua_clfactory_file_size(FILE *f);


int
ngx_http_lua_clfactory_bytecode_prepare(lua_State *L,
    ngx_http_lua_clfactory_file_ctx_t *lf, int fname_index)
{
    int                 x = 1, size_of_int, size_of_size_t, little_endian,
                        size_of_inst, version, stripped;
    static int          num_of_inst = 3, num_of_inter_func = 1;
    const char         *emsg, *serr, *bytecode;
    size_t              size, bytecode_len;
    long                fsize;

    serr = NULL;

    *lf->begin_code.str = LUA_SIGNATURE[0];

    if (lf->file_type == NGX_LUA_BT_LJ) {
        size = fread(lf->begin_code.str + 1, 1, LJ_HEADERSIZE - 1, lf->f);

        if (size != LJ_HEADERSIZE - 1) {
            serr = strerror(errno);
            emsg = "cannot read header";
            goto error;
        }

        version = *(lf->begin_code.str + 3);

        dd("version: %d", (int) version);

        if (ngx_memcmp(lf->begin_code.str, LJ_SIGNATURE,
                       sizeof(LJ_SIGNATURE) - 1))
        {
            emsg = "bad byte-code header";
            goto error;
        }

#if defined(DDEBUG) && (DDEBUG)
        {
        dd("==LJ_BT_HEADER==");
        size_t i;
        for (i = 0; i < LJ_HEADERSIZE; i++) {
            dd("%ld: 0x%02X", i, (unsigned)(u_char) lf->begin_code.str[i]);
        }
        dd("==LJ_BT_HEADER_END==");
        }
#endif

        lf->begin_code_len = LJ_HEADERSIZE;
        little_endian = !((*(lf->begin_code.str + 4)) & LJ_BCDUMP_F_BE);
        stripped = (*(lf->begin_code.str + 4)) & LJ_BCDUMP_F_STRIP;

        dd("stripped: %d", (int) stripped);

        if (version == LJ21_BCDUMP_VERSION) {
            if (stripped) {
                if (little_endian) {
                    lf->end_code.ptr = LJ21_LITTLE_ENDIAN_CODE_STRIPPED;

                } else {
                    lf->end_code.ptr = LJ21_BIG_ENDIAN_CODE_STRIPPED;
                }

                lf->end_code_len = LJ_CODE_LEN_STRIPPED;

            } else {
                if (little_endian) {
                    lf->end_code.ptr = LJ21_LITTLE_ENDIAN_CODE;

                } else {
                    lf->end_code.ptr = LJ21_BIG_ENDIAN_CODE;
                }

                lf->end_code_len = LJ_CODE_LEN;
            }

        } else if (version == LJ20_BCDUMP_VERSION) {
            if (stripped) {
                if (little_endian) {
                    lf->end_code.ptr = LJ20_LITTLE_ENDIAN_CODE_STRIPPED;

                } else {
                    lf->end_code.ptr = LJ20_BIG_ENDIAN_CODE_STRIPPED;
                }

                lf->end_code_len = LJ_CODE_LEN_STRIPPED;

            } else {
                if (little_endian) {
                    lf->end_code.ptr = LJ20_LITTLE_ENDIAN_CODE;

                } else {
                    lf->end_code.ptr = LJ20_BIG_ENDIAN_CODE;
                }

                lf->end_code_len = LJ_CODE_LEN;
            }

        } else {
            emsg = "bytecode format version unsupported";
            goto error;
        }

        fsize = ngx_http_lua_clfactory_file_size(lf->f);
        if (fsize < 0) {
            serr = strerror(errno);
            emsg = "cannot fseek/ftell";
            goto error;
        }

        lf->rest_len = fsize - LJ_HEADERSIZE;

#if defined(DDEBUG) && (DDEBUG)
        {
        size_t i = 0;
        dd("==LJ_END_CODE: %ld rest_len: %ld==", lf->end_code_len,
           lf->rest_len);

        for (i = 0; i < lf->end_code_len; i++) {
            dd("%ld: 0x%02X", i, (unsigned) ((u_char) lf->end_code.ptr[i]));
        }
        dd("==LJ_END_CODE_END==");
        }
#endif

    } else {
        size = fread(lf->begin_code.str + 1, 1, LUAC_HEADERSIZE - 1, lf->f);

        if (size != LUAC_HEADERSIZE - 1) {
            serr = strerror(errno);
            emsg = "cannot read header";
            goto error;
        }

        version = *(lf->begin_code.str + 4);
        little_endian = *(lf->begin_code.str + 6);
        size_of_int = *(lf->begin_code.str + 7);
        size_of_size_t = *(lf->begin_code.str + 8);
        size_of_inst = *(lf->begin_code.str + 9);

#if defined(DDEBUG) && (DDEBUG)
        {
        dd("==LUA_BT_HEADER==");
        size_t i;
        for (i = 0; i < LUAC_HEADERSIZE; i++) {
            dd("%ld, 0x%02X", i, (unsigned)(u_char) lf->begin_code.str[i]);
        }
        dd("==LUA_BT_HEADER_END==");
        }
#endif

        if (ngx_memcmp(lf->begin_code.str, LUA_SIGNATURE,
                       sizeof(LUA_SIGNATURE) -1)
            || version != LUAC_VERSION
            || little_endian != (int) (*(char *) &x)
            || size_of_int != sizeof(int)
            || size_of_size_t != sizeof(size_t)
            || (size_of_inst != 4 && size_of_inst != 8))
        {
            emsg = "bad byte-code header";
            goto error;
        }

        /* clear the following fields to zero:
         * - source string length
         * - start line
         * - last line
         */
        ngx_memzero(lf->begin_code.str + POS_SOURCE_STR_LEN,
                    sizeof(size_t) + sizeof(int) * 2);
        /* number of upvalues */
        *(lf->begin_code.str + POS_NUM_OF_UPVS) = 0;
        /* number of parameters */
        *(lf->begin_code.str + POS_NUM_OF_PARA) = 0;
        /* is var-argument function? */
        *(lf->begin_code.str + POS_IS_VAR_ARG) = 2;
        /* max stack size */
        *(lf->begin_code.str + POS_MAX_STACK_SIZE) = 2;
        /* number of bytecode instructions */
        ngx_memcpy(lf->begin_code.str + POS_NUM_OF_INST, &num_of_inst,
                   sizeof(int));

        lf->begin_code_len = POS_BYTECODE;

        if (little_endian) {
            if (size_of_inst == 4) {
                bytecode = LUA_LITTLE_ENDIAN_4BYTES_CODE;
                bytecode_len = LUA_LITTLE_ENDIAN_4BYTES_CODE_LEN;

            } else {
                bytecode = LUA_LITTLE_ENDIAN_8BYTES_CODE;
                bytecode_len = LUA_LITTLE_ENDIAN_8BYTES_CODE_LEN;
            }

        } else {
            if (size_of_inst == 4) {
                bytecode = LUA_BIG_ENDIAN_4BYTES_CODE;
                bytecode_len = LUA_BIG_ENDIAN_4BYTES_CODE_LEN;

            } else {
                bytecode = LUA_BIG_ENDIAN_8BYTES_CODE;
                bytecode_len = LUA_BIG_ENDIAN_8BYTES_CODE_LEN;
            }
        }

        /* bytecode */
        ngx_memcpy(lf->begin_code.str + POS_BYTECODE, bytecode, bytecode_len);

        /* number of consts */
        ngx_memzero(lf->begin_code.str + POS_BYTECODE + bytecode_len,
                    sizeof(int));
        /* number of internal functions */
        ngx_memcpy(lf->begin_code.str + POS_BYTECODE + bytecode_len
                   + sizeof(int), &num_of_inter_func, sizeof(int));

        lf->begin_code_len += bytecode_len + sizeof(int) + sizeof(int);

#if defined(DDEBUG) && (DDEBUG)
        {
        size_t i = 0;
        dd("==LUA_BEGIN_CODE: %ld==", lf->begin_code_len);
        for (i = 0; i < lf->begin_code_len; i++) {
            dd("%ld: 0x%02X", i, (unsigned) ((u_char) lf->begin_code.str[i]));
        }
        dd("==LUA_BEGIN_CODE_END==");
        }
#endif

        /* clear the following fields to zero:
         * - lineinfo vector size
         * - number of local vars
         * - number of upvalues
         */
        ngx_memzero(lf->end_code.str, sizeof(int) * 3);

        lf->end_code_len = sizeof(int) + sizeof(int) + sizeof(int);

#if defined(DDEBUG) && (DDEBUG)
        {
        size_t i = 0;
        dd("==LUA_END_CODE: %ld==", lf->end_code_len);
        for (i = 0; i < lf->end_code_len; i++) {
            dd("%ld: 0x%02X", i, (unsigned) ((u_char) lf->end_code.str[i]));
        }
        dd("==LUA_END_CODE_END==");
        }
#endif

    }

    return 0;

error:

    fclose(lf->f);  /* close file (even in case of errors) */

    if (serr) {
        lua_pushfstring(L, "%s: %s", emsg, serr);

    } else {
        lua_pushstring(L, emsg);
    }

    lua_remove(L, fname_index);

    return LUA_ERRFILE;
}


ngx_int_t
ngx_http_lua_clfactory_loadfile(lua_State *L, const char *filename)
{
    int                         c, status, readstatus;
    ngx_flag_t                  sharp;

    ngx_http_lua_clfactory_file_ctx_t        lf;

    /* index of filename on the stack */
    int                         fname_index;

    sharp = 0;
    fname_index = lua_gettop(L) + 1;

    lf.extraline = 0;
    lf.file_type = NGX_LUA_TEXT_FILE;

    lf.begin_code.ptr = CLFACTORY_BEGIN_CODE;
    lf.begin_code_len = CLFACTORY_BEGIN_SIZE;
    lf.end_code.ptr = CLFACTORY_END_CODE;
    lf.end_code_len = CLFACTORY_END_SIZE;

    lua_pushfstring(L, "@%s", filename);

    lf.f = fopen(filename, "r");
    if (lf.f == NULL) {
        return ngx_http_lua_clfactory_errfile(L, "open", fname_index);
    }

    c = getc(lf.f);

    if (c == '#') {  /* Unix exec. file? */
        lf.extraline = 1;

        while ((c = getc(lf.f)) != EOF && c != '\n') {
            /* skip first line */
        }

        if (c == '\n') {
            c = getc(lf.f);
        }

        sharp = 1;
    }

    if (c == LUA_SIGNATURE[0] && filename) {  /* binary file? */
        lf.f = freopen(filename, "rb", lf.f);  /* reopen in binary mode */

        if (lf.f == NULL) {
            return ngx_http_lua_clfactory_errfile(L, "reopen", fname_index);
        }

        /* check whether lib jit exists */
        luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1);
        lua_getfield(L, -1, "jit");  /* get _LOADED["jit"] */

        if (lua_istable(L, -1)) {
            lf.file_type = NGX_LUA_BT_LJ;

        } else {
            lf.file_type = NGX_LUA_BT_LUA;
        }

        lua_pop(L, 2);

        /*
         * Loading bytecode with an extra header is disabled for security
         * reasons. This may circumvent the usual check for bytecode vs.
         * Lua code by looking at the first char. Since this is a potential
         * security violation no attempt is made to echo the chunkname either.
         */
        if (lf.file_type == NGX_LUA_BT_LJ && sharp) {

            if (filename) {
                fclose(lf.f);  /* close file (even in case of errors) */
            }

            filename = lua_tostring(L, fname_index) + 1;
            lua_pushfstring(L, "bad byte-code header in %s", filename);
            lua_remove(L, fname_index);

            return LUA_ERRFILE;
        }

        while ((c = getc(lf.f)) != EOF && c != LUA_SIGNATURE[0]) {
            /* skip eventual `#!...' */
        }

        status = ngx_http_lua_clfactory_bytecode_prepare(L, &lf, fname_index);

        if (status != 0) {
            return status;
        }

        lf.extraline = 0;
    }

    if (lf.file_type == NGX_LUA_TEXT_FILE) {
        ungetc(c, lf.f);
    }

    lf.sent_begin = lf.sent_end = 0;
    status = lua_load(L, ngx_http_lua_clfactory_getF, &lf,
                      lua_tostring(L, -1));

    readstatus = ferror(lf.f);

    if (filename) {
        fclose(lf.f);  /* close file (even in case of errors) */
    }

    if (readstatus) {
        lua_settop(L, fname_index);  /* ignore results from `lua_load' */
        return ngx_http_lua_clfactory_errfile(L, "read", fname_index);
    }

    lua_remove(L, fname_index);

    return status;
}


ngx_int_t
ngx_http_lua_clfactory_loadbuffer(lua_State *L, const char *buff,
    size_t size, const char *name)
{
    ngx_http_lua_clfactory_buffer_ctx_t     ls;

    ls.s = buff;
    ls.size = size;
    ls.sent_begin = 0;
    ls.sent_end = 0;

    return lua_load(L, ngx_http_lua_clfactory_getS, &ls, name);
}


static const char *
ngx_http_lua_clfactory_getF(lua_State *L, void *ud, size_t *size)
{
    char                        *buf;
    size_t                       num;

    ngx_http_lua_clfactory_file_ctx_t        *lf;

    lf = (ngx_http_lua_clfactory_file_ctx_t *) ud;

    if (lf->extraline) {
        lf->extraline = 0;
        *size = 1;
        return "\n";
    }

    if (lf->sent_begin == 0) {
        lf->sent_begin = 1;
        *size = lf->begin_code_len;

        if (lf->file_type == NGX_LUA_TEXT_FILE) {
            buf = lf->begin_code.ptr;

        } else {
            buf = lf->begin_code.str;
        }

        return buf;
    }

    num = fread(lf->buff, 1, sizeof(lf->buff), lf->f);

    dd("fread returned %d", (int) num);

    if (num == 0) {
        if (lf->sent_end == 0) {
            lf->sent_end = 1;
            *size = lf->end_code_len;

            if (lf->file_type == NGX_LUA_BT_LUA) {
                buf = lf->end_code.str;

            } else {
                buf = lf->end_code.ptr;
            }

            return buf;
        }

        *size = 0;
        return NULL;
    }

    if (lf->file_type == NGX_LUA_BT_LJ) {
        /* skip the footer(\x00) in luajit */

        lf->rest_len -= num;

        if (lf->rest_len == 0) {
            if (--num == 0 && lf->sent_end == 0) {
                lf->sent_end = 1;
                buf = lf->end_code.ptr;
                *size = lf->end_code_len;

                return buf;
            }
        }
    }

    *size = num;
    return lf->buff;
}


static int
ngx_http_lua_clfactory_errfile(lua_State *L, const char *what, int fname_index)
{
    const char      *serr;
    const char      *filename;

    filename = lua_tostring(L, fname_index) + 1;

    if (errno) {
        serr = strerror(errno);
        lua_pushfstring(L, "cannot %s %s: %s", what, filename, serr);

    } else {
        lua_pushfstring(L, "cannot %s %s", what, filename);
    }

    lua_remove(L, fname_index);

    return LUA_ERRFILE;
}


static const char *
ngx_http_lua_clfactory_getS(lua_State *L, void *ud, size_t *size)
{
    ngx_http_lua_clfactory_buffer_ctx_t      *ls = ud;

    if (ls->sent_begin == 0) {
        ls->sent_begin = 1;
        *size = CLFACTORY_BEGIN_SIZE;

        return CLFACTORY_BEGIN_CODE;
    }

    if (ls->size == 0) {
        if (ls->sent_end == 0) {
            ls->sent_end = 1;
            *size = CLFACTORY_END_SIZE;
            return CLFACTORY_END_CODE;
        }

        return NULL;
    }

    *size = ls->size;
    ls->size = 0;

    return ls->s;
}


static long
ngx_http_lua_clfactory_file_size(FILE *f)
{
    long              cur_pos, len;

    cur_pos = ftell(f);
    if (cur_pos == -1) {
        return -1;
    }

    if (fseek(f, 0, SEEK_END) != 0) {
        return -1;
    }

    len = ftell(f);
    if (len == -1) {
        return -1;
    }

    if (fseek(f, cur_pos, SEEK_SET) != 0) {
        return -1;
    }

    return len;
}


/* vi:set ft=c ts=4 sw=4 et fdm=marker: */
