├── CHANGELOG.txt ├── LICENSE ├── README.md ├── bootstrap.bat ├── bootstrap.sh ├── exe ├── dogfood52_win_x86-32.zip ├── dogfood52_win_x86-64.zip ├── dogfood53_win_x86-32.zip ├── dogfood53_win_x86-64.zip ├── dogfood54_win_x86-32.zip └── dogfood54_win_x86-64.zip ├── makefile ├── src ├── dog.c └── food.lua └── test ├── bar.lua ├── bar └── baz.lua ├── foo.lua └── pg1003.c /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | # 1.1.0 2 | 3 | Changes: 4 | - Added for Windows the option to compile dogfood with support for an embed Lua DLL 5 | in the executable's resource. 6 | This is enabled by setting a EMBED_LUA_DLL preprocessor define. 7 | Details about embedding the DLL can be found in dog.c. 8 | - Refactored to remove duplicate code and to reduce and simplify buffering while 9 | reading Lua data from executable. 10 | 11 | # 1.0.2 12 | 13 | Changes: 14 | - Use lua_pcall when calling the main module and report errors to the user. 15 | - Output all errors to stderr like the PUC Lua interpreter does. 16 | - Output a warning for C modules that are found at one of package.cpath locations. 17 | 18 | # 1.0.1 19 | 20 | Changes: 21 | - Added the '-v' and '--lua-version' options to show the Lua language version. 22 | 23 | # v1.0.0 24 | 25 | - Initial release 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 PG1003 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A tool for building self contained Lua executables 2 | 3 | This tool is an alternative for ```srlua``` which can be found [here](http://tecgraf.puc-rio.br/~lhf/ftp/lua/). 4 | 5 | The main points where ```dogfood``` differs from ```srlua``` are: 6 | 7 | * One self contained executable. 8 | * Can embed multiple Lua modules in one executable. 9 | * No support for Lua 5.1 and older. 10 | 11 | Other features: 12 | 13 | * Does not depend on a C compiler to build self contained Lua executables. 14 | * ```dogfood``` self can be build for Unix like operating systems _and_ Windows. 15 | 16 | ```dogfood``` consists of two parts; a Lua interpreter and a Lua module that creates the self executable Lua programs. 17 | The interpreter runs the dogfood's Lua module that is appended at the end of the executable. 18 | The module reuses the interpreter when it builds a self contained Lua executable by copying its interpreter and then append the user's Lua modules. 19 | 20 | ```dogfood``` owes its name to the reuse of its own interpreter. 21 | Using your own software products is also knowns as [dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food). 22 | 23 | ## Requirements 24 | 25 | * Lua version 5.2, 5.3 or 5.4 26 | 27 | ## Usage of dogfood 28 | 29 | ``` sh 30 | dogfood [OPTIONS] OUT MODULE... 31 | ``` 32 | 33 | ```OUT``` is the destination of the resulting program 34 | The first ```MODULE``` from the list is the entry point of the program while the optional extra modules are accessable by ```require```. 35 | The modules are provided _without_ the '.lua' extention. 36 | C modules cannot be embedded in the resulting program but the program can load C modules when they are placed in one of the ```package.cpath``` search paths. 37 | 38 | The following ```OPTIONS``` are available. 39 | 40 | |Option | Description| 41 | |-------|------------| 42 | |-c, --compile | Compile the modules and embed them as bytecode.| 43 | |-s, --strip-debug-information | Strips the debug information from the bytecode.| 44 | |-h, --help | Shows the help.| 45 | |-m | Adds the given path to ```package.loaded``` to search for modules that are provided as parameter.| 46 | |-v, --lua-version | Shows the Lua language version of the interpreter used by the resulting program..| 47 | 48 | ## Dogfood binaries for Windows 49 | 50 | Building dogfood binaries for Windows requires more effort than for Unix like operating systems. 51 | The following executables are available for download to lower the bar for Windows users that want to try ```dogfood``` or don't have the knowledge to build it. 52 | 53 | | Lua version | dogfood v1.1.0 x86-64 | dogfood v1.1.0 x86-32 | 54 | |-------------|-----|-----| 55 | | 5.2.4 | [download](https://raw.githubusercontent.com/PG1003/dogfood/master/exe/dogfood52_win_x86-64.zip) | [download](https://raw.githubusercontent.com/PG1003/dogfood/master/exe/dogfood52_win_x86-32.zip) | 56 | | 5.3.6 | [download](https://raw.githubusercontent.com/PG1003/dogfood/master/exe/dogfood53_win_x86-64.zip) | [download](https://raw.githubusercontent.com/PG1003/dogfood/master/exe/dogfood53_win_x86-32.zip) | 57 | | 5.4.7 | [download](https://raw.githubusercontent.com/PG1003/dogfood/master/exe/dogfood54_win_x86-64.zip) | [download](https://raw.githubusercontent.com/PG1003/dogfood/master/exe/dogfood54_win_x86-32.zip) | 58 | 59 | ## Bootstrapping 60 | 61 | Right after compilation, a dogfood binary must be bootstrapped before it is able to build self contained Lua executables. 62 | The makefile provided with the sources already takes care of this. 63 | In case when you have your own build environment, a bootstrap shell script is provided for Unix like environments and for Windows. 64 | 65 | The usage of the bootstrap script is as follows; 66 | 67 | ``` sh 68 | bootstrap.sh EXECUTABLE food.lua OUT 69 | ``` 70 | 71 | For Windows ```bootstrap.bat``` instead of ```bootstrap.sh``` is used but the commandline prameters are same. 72 | 73 | ```EXECUTABLE``` is the path to the executable that needs to be bootstrapped. 74 | The file ```food.lua``` contains dogfood's logic. 75 | You can find this file in the [source](/src) directory. 76 | ```OUT``` is the destination of the resulting executable. 77 | ```EXECUTABLE``` and ```OUT``` can point to the same file. 78 | -------------------------------------------------------------------------------- /bootstrap.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | :: Usage: bootstrap.bat EXE SCRIPT OUTPUT 4 | :: EXE Path of executable part of the dogfood program 5 | :: SCRIPT Path of the lua script of the dogfood program 6 | :: OUTPUT Path of the resulting dogfood executable program 7 | :: 8 | :: EXE and OUTPUT can target the same file which in this case will 9 | :: append SCRIPT to EXE 10 | 11 | setlocal 12 | 13 | :: tohex strips the leading zeros when setting the return value 14 | 15 | set dogsize=0 16 | call :tohex dogsize %~z1 17 | set dogsize=00000000%dogsize% 18 | 19 | set foodsize=0 20 | call :tohex foodsize %~z2 21 | set foodsize=00000000%foodsize% 22 | 23 | if /I not %1==%3 copy /B /Y %1 %3 24 | 25 | echo.>> %3 26 | echo -- food %foodsize:~-8%>> %3 27 | 28 | type %2 >> %3 29 | 30 | echo.>> %3 31 | echo -- dogfood %dogsize:~-8%>> %3 32 | 33 | endlocal 34 | 35 | goto exit 36 | 37 | 38 | :: https://www.dostips.com/DtTipsArithmetic.php#toHex 39 | :tohex 40 | SETLOCAL ENABLEDELAYEDEXPANSION 41 | set /A dec=%~2 42 | set "hex=" 43 | set "map=0123456789ABCDEF" 44 | for /L %%N in (1,1,8) do ( 45 | set /A "d=dec&15,dec>>=4" 46 | for %%D in (!d!) do set "hex=!map:~%%D,1!!hex!" 47 | ) 48 | ENDLOCAL & SET %1=%hex% 49 | 50 | 51 | :exit 52 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Usage: bootstrap.sh EXE SCRIPT OUTPUT 4 | # EXE Path of executable part of the dogfood program 5 | # SCRIPT Path of the lua script of the dogfood program 6 | # OUTPUT Path of the resulting dogfood executable program 7 | # 8 | # EXE and OUTPUT can target the same file which in this case will 9 | # append SCRIPT to EXE 10 | 11 | PAYLOAD=$(printf "\r\n-- dogfood %08X\r\n" $(wc -c < $1)) 12 | 13 | if [[ ! $1 -ef $3 ]] 14 | then 15 | cp -f $1 $3 16 | fi 17 | 18 | printf "\r\n-- food %08X\r\n" $(wc -c < $2) >> $3 19 | cat $2 >> $3 20 | echo $PAYLOAD >> $3 21 | -------------------------------------------------------------------------------- /exe/dogfood52_win_x86-32.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PG1003/dogfood/de508d0f70ce4a4217ece054afc2b873ab80d211/exe/dogfood52_win_x86-32.zip -------------------------------------------------------------------------------- /exe/dogfood52_win_x86-64.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PG1003/dogfood/de508d0f70ce4a4217ece054afc2b873ab80d211/exe/dogfood52_win_x86-64.zip -------------------------------------------------------------------------------- /exe/dogfood53_win_x86-32.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PG1003/dogfood/de508d0f70ce4a4217ece054afc2b873ab80d211/exe/dogfood53_win_x86-32.zip -------------------------------------------------------------------------------- /exe/dogfood53_win_x86-64.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PG1003/dogfood/de508d0f70ce4a4217ece054afc2b873ab80d211/exe/dogfood53_win_x86-64.zip -------------------------------------------------------------------------------- /exe/dogfood54_win_x86-32.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PG1003/dogfood/de508d0f70ce4a4217ece054afc2b873ab80d211/exe/dogfood54_win_x86-32.zip -------------------------------------------------------------------------------- /exe/dogfood54_win_x86-64.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PG1003/dogfood/de508d0f70ce4a4217ece054afc2b873ab80d211/exe/dogfood54_win_x86-64.zip -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -Wall -Wextra -Wpedantic -O3 3 | INCLUDES = -I "./src" 4 | LDFLAGS = -llua 5 | 6 | OS = $(shell uname -s) 7 | ifeq ($(OS),Darwin) 8 | LIB_FLAGS = -bundle -undefined dynamic_lookup 9 | else 10 | LIB_FLAGS = -shared -fPIC 11 | endif 12 | 13 | SRCDIR = src/ 14 | OUTDIR = out/ 15 | TSTDIR = test/ 16 | 17 | SRC = $(shell find $(SRCDIR) -type f -name '*.c') 18 | OBJ = $(patsubst $(SRCDIR)%.c, $(OUTDIR)%.o, $(SRC)) 19 | 20 | .phony: dogfood test clean 21 | 22 | dogfood: $(OUTDIR)dogfood 23 | 24 | $(OUTDIR)dogfood: $(OUTDIR) ./bootstrap.sh $(OUTDIR)dog $(SRCDIR)food.lua 25 | ./bootstrap.sh $(OUTDIR)dog $(SRCDIR)food.lua $@ 26 | 27 | $(OUTDIR)dog: $(OBJ) 28 | $(CC) $(LDFLAGS) $+ -o $@ 29 | 30 | $(OUTDIR)%.o: $(SRCDIR)%.c 31 | $(CC) $(CFLAGS) -c $? -o $(patsubst $(SRCDIR)%.c, $(OUTDIR)%.o, $@) 32 | 33 | $(OUTDIR): 34 | mkdir $@ 35 | 36 | # The first part of the testing dogfood is done by building the foobar excutable successfully. 37 | # Then the tests below are executed to test the foobar executable and by trying some error conditions. 38 | test: $(OUTDIR)foobar $(OUTDIR)pg1005.so 39 | @echo "Running tests..." 40 | @echo "> Test 1; happy flow foobar" 41 | @cd $(OUTDIR); ./foobar -param && : || { echo ">>> Test 1 failed!"; exit 1; } 42 | @echo "> Test 2; expect a failed assertion from foobar" 43 | @cd $(OUTDIR); ./foobar && { echo ">>> Test 2 failed!"; exit 1; } || : 44 | @echo "> Test 3; expect error from dogfood about an issue with the pg1005 module" 45 | @cd $(OUTDIR); ./dogfood -m ../$(TSTDIR)?.lua foobar_failed foo bar pg1005 && { echo ">>> Test 3 failed!"; exit 1; } || : 46 | @echo "" 47 | @echo "...tests completed" 48 | @echo " _" 49 | @echo " /(|" 50 | @echo " ( :" 51 | @echo " __\ \ _____" 52 | @echo " (____) '|" 53 | @echo "(____)| |" 54 | @echo " (____).__|" 55 | @echo " (___)__.|_____" 56 | # https://asciiart.website/index.php?art=people/body%20parts/hand%20gestures 57 | 58 | 59 | $(OUTDIR)foobar: $(OUTDIR)dogfood $(TSTDIR)foo.lua $(TSTDIR)bar.lua $(TSTDIR)bar/baz.lua $(OUTDIR)pg1003.so 60 | cd $(OUTDIR) && ./dogfood -c -s -m ../$(TSTDIR)?.lua foobar foo foo bar bar.baz pg1003 61 | chmod u+x $@ 62 | 63 | $(OUTDIR)pg1003.so: $(TSTDIR)pg1003.c 64 | $(CC) $(CFLAGS) $(LIB_FLAGS) -o $@ $+ 65 | 66 | # By copying pg1003.so to pg1005.so we have created an invalid lua library (wrong entry point) that is used to test dogfood 67 | $(OUTDIR)pg1005.so: $(OUTDIR)pg1003.so 68 | cp $(OUTDIR)pg1003.so $(OUTDIR)pg1005.so 69 | 70 | clean: 71 | rm -rf $(OUTDIR) 72 | -------------------------------------------------------------------------------- /src/dog.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 PG1003 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to 5 | // deal in the Software without restriction, including without limitation the 6 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | // sell copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #if defined( _WIN32 ) && defined( EMBED_LUA_DLL ) 30 | #define LOAD_EMBEDDED_LUA_DLL 31 | 32 | #include "resource.h" 33 | #include 34 | #include 35 | #include 36 | #endif 37 | 38 | #define STR_HELPER(x) #x 39 | #define STR(x) STR_HELPER(x) 40 | 41 | #define MAX_NAME_LENGTH 127 42 | #define MAX_NAME_LENGTH_STR STR( MAX_NAME_LENGTH ) 43 | 44 | 45 | #if LUA_VERSION_NUM < 502 || LUA_VERSION_NUM > 504 46 | #error "Unsupported Lua version" 47 | #endif 48 | 49 | #if defined( LOAD_EMBEDDED_LUA_DLL ) 50 | 51 | /* This function loads Lua DLL from its resource. 52 | * Embedding the Lua DLL simplifies the distribution of dogfood and its derived executables. 53 | * 54 | * This feature is specific for Windows and is enabled by a EMBED_LUA_DLL preprocessor define. 55 | * 56 | * The function requires that: 57 | * 1) The data is a valid Lua DLL that targets the same platform as dogfood's executable. 58 | * 2) The DLL is delay loaded. 59 | * 3) The ID for DLL in the resource is named IRD_LUADLL. 60 | * 4) The resource type of the Lua DLL is RCDATA. 61 | * 62 | * The resource file should contain a line that look like the following; 63 | * 64 | * IDR_LUADLL RCDATA "PATH_TO_YOUR\Lua.dll" 65 | * 66 | * Its possible to link dogfood static with Lua but then support for C mudules will be effectively 67 | * disabled. C modules will likely require a Lua DLL that conflicts with the statically linked Lua 68 | * instance in dogfood. In this case a 'multiple Lua VMs detected' message will be displayed in 69 | * the console output when you run the resulting executable. 70 | */ 71 | static void load_lua_dll_from_resouce() 72 | { 73 | /* https://stackoverflow.com/questions/17774103/using-an-embedded-dll-in-an-executable 74 | * https://learn.microsoft.com/en-us/cpp/windows/how-to-include-resources-at-compile-time */ 75 | 76 | #pragma warning( push, 3 ) 77 | const HRSRC resource_handle = FindResource( NULL, 78 | MAKEINTRESOURCE( IDR_LUADLL ), 79 | RT_RCDATA ); 80 | assert( resource_handle ); 81 | #pragma warning( pop ) 82 | 83 | const HGLOBAL file_resouce_handle = LoadResource( NULL, resource_handle ); 84 | assert( file_resouce_handle ); 85 | 86 | const LPVOID file_at_resouce = LockResource( file_resouce_handle ); 87 | assert( file_at_resouce ); 88 | 89 | const DWORD file_size = SizeofResource( NULL, resource_handle ); 90 | 91 | const TCHAR lua_dll_name[] = _T("Lua" LUA_VERSION_MAJOR LUA_VERSION_MINOR ".dll"); 92 | const HANDLE file_handle = CreateFile( lua_dll_name, 93 | GENERIC_READ | GENERIC_WRITE, 94 | 0, 95 | NULL, 96 | CREATE_ALWAYS, 97 | FILE_ATTRIBUTE_NORMAL, 98 | NULL ); 99 | assert( file_handle ); 100 | 101 | const HANDLE file_mapping_handle = CreateFileMapping( file_handle, 102 | NULL, 103 | PAGE_READWRITE, 104 | 0, 105 | file_size, 106 | NULL ); 107 | assert( file_mapping_handle ); 108 | 109 | const LPVOID mapped_file_address = MapViewOfFile( file_mapping_handle, 110 | FILE_MAP_WRITE, 111 | 0, 0, 0 ); 112 | assert( mapped_file_address ); 113 | 114 | CopyMemory( mapped_file_address, file_at_resouce, file_size ); 115 | 116 | UnmapViewOfFile( mapped_file_address ); 117 | CloseHandle( file_mapping_handle ); 118 | CloseHandle( file_handle ); 119 | } 120 | 121 | #endif 122 | 123 | static void dogfood_error( lua_State * L, const char * const format, ... ) 124 | { 125 | va_list args; 126 | va_start( args, format ); 127 | 128 | fputs( "Dogfood error: ", stderr ); 129 | vfprintf( stderr, format, args ); 130 | fputc( '\n', stderr ); 131 | 132 | lua_close( L ); 133 | exit( 1 ); 134 | } 135 | 136 | static void dogfood_errno( lua_State * L ) 137 | { 138 | perror( "Dogfood error" ); 139 | lua_close( L ); 140 | exit( 1 ); 141 | } 142 | 143 | typedef char module_name_buffer[ MAX_NAME_LENGTH + 1 ]; 144 | 145 | static void read_module_header( lua_State * L, 146 | FILE * f, 147 | module_name_buffer module_name, 148 | unsigned long * module_size ) 149 | { 150 | const char header[] = "\r\n-- %" MAX_NAME_LENGTH_STR "s %08lX%c%c"; 151 | char carriage_return = 0; 152 | char new_line = 0; 153 | 154 | const int result = fscanf( f, header, 155 | module_name, module_size, &carriage_return, &new_line ); 156 | 157 | if( result == EOF && ferror( f ) ) 158 | { 159 | dogfood_errno( L ); 160 | } 161 | 162 | if( result != 4 || 163 | module_size == 0 || 164 | carriage_return != '\r' || 165 | new_line != '\n' ) 166 | { 167 | dogfood_error( L, "No valid start of module '%s' found.", module_name ); 168 | } 169 | } 170 | 171 | typedef struct 172 | { 173 | FILE * f; 174 | const char * module_name; 175 | size_t remaining; 176 | size_t available; 177 | char buffer[ 4096 ]; 178 | } module_reader_ctx; 179 | 180 | static const char * module_reader( lua_State *L, void * p, size_t * size ) 181 | { 182 | module_reader_ctx * const ctx = ( module_reader_ctx * )p; 183 | 184 | if( ctx->remaining == 0u ) 185 | { 186 | return NULL; 187 | } 188 | 189 | const size_t max_buffer = sizeof( ctx->buffer ); 190 | const size_t n_to_read = ctx->remaining < max_buffer ? ctx->remaining : max_buffer; 191 | 192 | *size = fread( ctx->buffer, sizeof( char ), n_to_read, ctx->f ); 193 | if( ferror( ctx->f ) ) 194 | { 195 | dogfood_errno( L ); 196 | } 197 | 198 | if( *size != n_to_read ) 199 | { 200 | dogfood_error( L, "Cannot read the entire module '%s'.", ctx->module_name ); 201 | } 202 | 203 | ctx->remaining -= *size; 204 | 205 | return ctx->buffer; 206 | } 207 | 208 | static void load_module( lua_State * L, 209 | FILE * const f, 210 | module_name_buffer module_name, 211 | size_t module_size ) 212 | { 213 | module_reader_ctx ctx; 214 | ctx.f = f; 215 | ctx.module_name = module_name; 216 | ctx.remaining = module_size; 217 | ctx.available = 0u; 218 | 219 | const int result = lua_load( L, module_reader, &ctx, module_name, NULL ); 220 | switch( result ) 221 | { 222 | case LUA_OK: 223 | case LUA_YIELD: 224 | break; 225 | 226 | case LUA_ERRSYNTAX: 227 | case LUA_ERRMEM: 228 | #if LUA_VERSION_NUM < 504 229 | case LUA_ERRGCMM: 230 | #endif 231 | case LUA_ERRERR: 232 | { 233 | const char * const extra = lua_tostring( L, -1 ); 234 | dogfood_error( L, 235 | extra ? "Error while loading module '%s';\n%s" : 236 | "An error has occurred while loading module '%s'.", 237 | module_name, extra ); 238 | } 239 | } 240 | } 241 | 242 | int main( int argc, char *argv[] ) 243 | { 244 | #if defined( LOAD_EMBEDDED_LUA_DLL ) 245 | load_lua_dll_from_resouce(); 246 | #endif 247 | 248 | lua_State * const L = luaL_newstate(); 249 | luaL_openlibs( L ); 250 | 251 | /* Create and fill argument table */ 252 | lua_createtable( L, argc, 0 ); 253 | int i = 0; 254 | for( ; i < argc ; ++i ) 255 | { 256 | lua_pushstring( L, argv[ i ] ); 257 | lua_rawseti( L, -2, i ); 258 | } 259 | lua_setglobal( L, "arg" ); 260 | 261 | /* Open the executable to read the modules that are appended the file */ 262 | FILE * const f = fopen( argv[ 0 ], "rb" ); 263 | if( !f ) 264 | { 265 | dogfood_errno( L ); 266 | } 267 | 268 | const long payload_end_marker_length = sizeof( "\r\n-- dogfood DEADBEEF\r\n" ) - 1; 269 | if( fseek( f, -payload_end_marker_length, SEEK_END ) ) 270 | { 271 | dogfood_errno( L ); 272 | } 273 | 274 | const long end_of_payload = ftell( f ); 275 | if( end_of_payload == -1L ) 276 | { 277 | dogfood_errno( L ); 278 | } 279 | 280 | unsigned long payload_offset = 0UL; 281 | if( fscanf( f, "\r\n-- dogfood %08lX\r\n", &payload_offset ) != 1 && 282 | payload_offset == 0 ) 283 | { 284 | dogfood_error( L, "No valid payload end marker found." ); 285 | } 286 | 287 | if( fseek( f, ( long )payload_offset, SEEK_SET ) ) 288 | { 289 | dogfood_errno( L ); 290 | } 291 | 292 | /* The main module is the first module in the payload */ 293 | module_name_buffer main_module_name = { 0 }; 294 | unsigned long main_module_size = 0UL; 295 | read_module_header( L, f, main_module_name, &main_module_size ); 296 | 297 | const long main_module_pos = ftell( f ); 298 | if( !( main_module_pos != -1L && 299 | fseek( f, ( long )main_module_size, SEEK_CUR ) == 0 ) ) 300 | { 301 | dogfood_errno( L ); 302 | } 303 | 304 | /* Load modules (byte)code and add it to the package.loaded table */ 305 | lua_getglobal( L, "package" ); 306 | lua_getfield( L, -1, "loaded" ); 307 | 308 | long pos = ftell( f ); 309 | while( ( pos != -1L ) && ( pos < end_of_payload ) ) 310 | { 311 | module_name_buffer module_name = { 0 }; 312 | unsigned long module_size = 0UL; 313 | 314 | read_module_header( L, f, module_name, &module_size ); 315 | load_module( L, f, module_name, module_size ); 316 | 317 | lua_call( L, 0, 1 ); 318 | lua_setfield( L, -2, module_name ); 319 | 320 | pos = ftell( f ); 321 | } 322 | 323 | /* Load and execute the main module (byte)code */ 324 | if( ferror( f ) || 325 | fseek( f, main_module_pos, SEEK_SET ) ) 326 | { 327 | dogfood_errno( L ); 328 | } 329 | 330 | load_module( L, f, main_module_name, main_module_size ); 331 | 332 | fclose( f ); 333 | 334 | const int status = lua_pcall( L, 0, 1, 0 ); 335 | 336 | if( status != LUA_OK ) 337 | { 338 | /* Show Lua errors. 339 | * Do not prepend 'Dogfood error:' to the error message. The error has nothing 340 | * to do with dogfood since it is emited by the Lua VM or the Lua modules. */ 341 | const char * const msg = lua_tostring( L, -1 ); 342 | fprintf( stderr, "%s\n", msg ? msg : "An error has occurred." ); 343 | } 344 | 345 | /* Get the exit status if the return value is a number */ 346 | const int exit_status = ( int )( lua_isnumber( L, 0 ) ? lua_tonumber( L, 0 ) : 0.0 ); 347 | 348 | lua_close( L ); 349 | 350 | /* Return exit status when OK, else default to 1 when there was an error */ 351 | return status == LUA_OK ? exit_status : 1; 352 | } 353 | -------------------------------------------------------------------------------- /src/food.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2019 PG1003 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy 4 | -- of this software and associated documentation files (the "Software"), to 5 | -- deal in the Software without restriction, including without limitation the 6 | -- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | -- sell copies of the Software, and to permit persons to whom the Software is 8 | -- furnished to do so, subject to the following conditions: 9 | 10 | -- The above copyright notice and this permission notice shall be included in 11 | -- all copies or substantial portions of the Software. 12 | 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | -- SOFTWARE. 20 | 21 | 22 | local help = [[dogfood v1.1.0 23 | 24 | A tool for creating self running Lua programs. 25 | 26 | Usage: dogfood [OPTIONS] OUT MODULE... 27 | 28 | Creates a program at location OUT that runs its embedded Lua MODULEs. 29 | The first MODULE from the list is the entry point of the program while 30 | the optional extra modules are accessable by require. The modules are 31 | provided without the '.lua' extention. 32 | C modules cannot be embedded in the resulting program but the program can 33 | load C modules when they are placed in one of the package.cpath search paths. 34 | 35 | The following OPTIONS are available; 36 | -c, --compile Compile the modules and embed them as bytecode. 37 | -s, --strip-debug-information Strips the debug information from the bytecode. 38 | -h, --help Show this help. 39 | -m Adds the given path to package.loaded to search 40 | for modules that are provided as parameter. 41 | -v, --lua-version Show the Lua language version of the 42 | interpreter used by the resulting program.]] 43 | 44 | 45 | local function dogfood_error( msg ) 46 | io.stderr:write( "Dogfood error: " .. msg .. "\n" ) 47 | os.exit( 1 ) 48 | end 49 | 50 | local function dogfood_warn( msg ) 51 | io.stderr:write( "Dogfood warning: " .. msg .. "\n" ) 52 | end 53 | 54 | -- 55 | -- Parse and validate the commandline parameters 56 | -- 57 | 58 | local compile = false 59 | local strip_debug_information = false 60 | local out = false 61 | local modules = {} 62 | 63 | do 64 | if #arg == 0 then 65 | dogfood_error( "Use '-h' or '--help' as parameters for information about the usage of dogfood." ) 66 | end 67 | 68 | local displayed_help = false 69 | local displayed_lua_version = false 70 | 71 | local option = false 72 | for _, param in ipairs( arg ) do 73 | if option then 74 | if option == "-m" then 75 | local path = param:gsub( "\"", "" ) 76 | package.path = package.path .. ";" .. path 77 | end 78 | option = false 79 | elseif param == "-h" or param == "--help" then 80 | print( help ) 81 | displayed_help = true 82 | elseif param == "-c" or param == "--compile" then 83 | compile = true 84 | elseif param == "-s" or param == "--strip-debug-information" then 85 | strip_debug_information = true 86 | elseif param == "-v" or param == "--lua-version" then 87 | print( _VERSION ) 88 | displayed_lua_version = true 89 | elseif param == "-m" then 90 | option = param 91 | else 92 | if not out then 93 | out = param:gsub( "\"", "" ) 94 | else 95 | modules[ #modules + 1 ] = { name = param } 96 | end 97 | end 98 | end 99 | 100 | -- '-h' and '-v' options display only information and do not require the OUT and MODULE parameters 101 | if ( displayed_help or displayed_lua_version ) and not out and #modules == 0 then 102 | os.exit( 0 ) 103 | end 104 | 105 | if not out then 106 | dogfood_error( "No output file provided." ) 107 | end 108 | 109 | if #modules == 0 then 110 | dogfood_error( "No modules provided." ) 111 | end 112 | end 113 | 114 | -- 115 | -- Validate modules 116 | -- 117 | 118 | do 119 | local found = {} 120 | local purge = {} 121 | for i, mod in ipairs( modules ) do 122 | if found[ mod.name ] then 123 | -- Duplicates 124 | purge[ #purge + 1 ] = i 125 | else 126 | local path = package.searchpath( mod.name, package.path ) 127 | if path then 128 | mod.path = path 129 | else 130 | path = package.searchpath( mod.name, package.cpath ) 131 | if path then 132 | local ok, result = pcall( require, mod.name ) 133 | if ok then 134 | dogfood_warn( "Module '" .. mod.name .. 135 | "' is a C module found at location '" .. 136 | path .. "'." ) 137 | -- C modules cannot be embedded 138 | purge[ #purge + 1 ] = i 139 | else 140 | dogfood_error( result ) 141 | end 142 | else 143 | dogfood_error( "Cannot find module '" .. mod.name .. "'." ) 144 | end 145 | end 146 | found[ mod.name ] = true 147 | end 148 | end 149 | 150 | for i = #purge, 1, -1 do 151 | table.remove( modules, purge[ i ] ) 152 | end 153 | end 154 | 155 | -- 156 | -- Open the output file to write the executable data to 157 | -- 158 | 159 | local output = io.open( out, "w+b" ) 160 | if not output then 161 | dogfood_error( "Cannot create output file '" .. out .. "'." ) 162 | end 163 | 164 | local payload_end = false 165 | 166 | -- 167 | -- Copy the 'dog' part of this dogfood executable to the new executable 168 | -- 169 | 170 | do 171 | local dogfood = io.open( arg[ 0 ], "rb" ) 172 | if not dogfood then 173 | dogfood_error( "Cannot read own dogfood executable." ) 174 | end 175 | 176 | if not dogfood:seek( "end", -23 ) then 177 | dogfood_error( "Cannot seek to end of the dogfood executable." ) 178 | end 179 | 180 | payload_end = dogfood:read( 23 ) 181 | if not payload_end then 182 | dogfood_error( "No end-of-payload marker found." ) 183 | end 184 | 185 | local dog_bytes = payload_end:match( "%s-- dogfood (%x%x%x%x%x%x%x%x)%s" ) 186 | if not dog_bytes then 187 | dogfood_error( "Invalid end-of-payload marker found." ) 188 | end 189 | 190 | dog_bytes = assert( tonumber( dog_bytes, 16 ) ) 191 | if dog_bytes >= ( dogfood:seek() - 23 ) then 192 | dogfood_error( "Invalid end-of-payload marker value." ) 193 | end 194 | 195 | dogfood:seek( "set", 0 ) 196 | repeat 197 | local count = dog_bytes > 4096 and 4096 or dog_bytes 198 | local chunk = assert( dogfood:read( count ) ) 199 | output:write( chunk ) 200 | dog_bytes = dog_bytes - count 201 | until dog_bytes <= 0 202 | dogfood:close() 203 | end 204 | 205 | -- 206 | -- Append payload 207 | -- 208 | 209 | do 210 | for _, mod in ipairs( modules ) do 211 | -- Validate if the module has syntactic errors by loading the file 212 | local mod_chunk, error_msg = loadfile( mod.path, "t" ); 213 | if mod_chunk == nil then 214 | dogfood_error( "Error while loading module.\n" .. error_msg ) 215 | end 216 | 217 | local mod_data 218 | if compile then 219 | mod_data = assert( string.dump( mod_chunk, strip_debug_information ) ) 220 | else 221 | -- Work around for 'read all' which has changed with 5.3 222 | local file = assert( io.open( mod.path, "rb" ) ) 223 | file:seek( "end", 0 ) 224 | local file_size = file:seek( "cur", 0 ) 225 | file:seek( "set", 0 ) 226 | mod_data = assert( file:read( file_size ) ) 227 | file:close() 228 | end 229 | output:write( string.format( "\r\n-- %s %08X\r\n", mod.name, #mod_data ) ) 230 | output:write( mod_data ) 231 | end 232 | 233 | output:write( payload_end ) 234 | output:close() 235 | end 236 | 237 | return 0 238 | -------------------------------------------------------------------------------- /test/bar.lua: -------------------------------------------------------------------------------- 1 | 2 | local bar = {} 3 | 4 | function bar.bar() 5 | return "bar" 6 | end 7 | 8 | return bar 9 | -------------------------------------------------------------------------------- /test/bar/baz.lua: -------------------------------------------------------------------------------- 1 | local baz = {} 2 | 3 | function baz.baz() 4 | return "baz" 5 | end 6 | 7 | return baz 8 | -------------------------------------------------------------------------------- /test/foo.lua: -------------------------------------------------------------------------------- 1 | local bar = require( "bar" ) 2 | local baz = require( "bar.baz" ) 3 | local pg1003 = require( "pg1003" ) 4 | 5 | -- Test if we get the expected arguments 6 | assert( string.match( arg[ 0 ], "/foobar$" ) ) 7 | assert( arg[ 1 ] == "-param" ) 8 | 9 | -- Call functions from other modules 10 | assert( bar.bar() == "bar" ) 11 | assert( baz.baz() == "baz" ) 12 | assert( pg1003.pg1003() == "PG1003" ) 13 | 14 | -- Retun success 15 | return 0 16 | -------------------------------------------------------------------------------- /test/pg1003.c: -------------------------------------------------------------------------------- 1 | #include "lua.h" 2 | #include "lauxlib.h" 3 | 4 | static int pg1003( lua_State *L ) 5 | { 6 | lua_checkstack( L, 1 ); 7 | lua_pushliteral( L, "PG1003" ); 8 | 9 | return 1; 10 | } 11 | 12 | static const struct luaL_Reg pg1003_functions[] = 13 | { 14 | { "pg1003", pg1003 }, 15 | { NULL, NULL } 16 | }; 17 | 18 | LUALIB_API int luaopen_pg1003( lua_State *L ) 19 | { 20 | luaL_newlib( L, pg1003_functions ); 21 | 22 | return 1; 23 | } 24 | --------------------------------------------------------------------------------