[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = tab\nindent_size = tab\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": ".github/workflows/build.yaml",
    "content": "# Based on https://gist.github.com/domenic/ec8b0fc8ab45f39403dd\nname: Build Modules\non:\n  pull_request:\n    branches:\n    - master\n  push:\n    branches:\n    - master\njobs:\n  build:\n    name: Build ${{ matrix.kernel-version }}\n    strategy:\n      matrix:\n        kernel-version: [5.15, 6.11]\n      fail-fast: false\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v4\n      with:\n        submodules: recursive\n    - name: Install dependencies\n      run: |\n        sudo apt-get update -qq\n        sudo apt-get install -yq build-essential bc bison flex libssl-dev dwarves libelf-dev libncurses-dev clang llvm pkg-config libpcap-dev lua5.4 lua5.4-dev m4\n    - name: Setup kernel ${{ matrix.kernel-version }}\n      run: |\n        KERNEL_VERSION=\"${{ matrix.kernel-version }}\"\n        MAJOR=$(echo $KERNEL_VERSION | cut -d. -f1)\n        wget -q \"https://cdn.kernel.org/pub/linux/kernel/v${MAJOR}.x/linux-${KERNEL_VERSION}.tar.xz\"\n        tar xf linux-${KERNEL_VERSION}.tar.xz\n        cd linux-${KERNEL_VERSION}\n        make mrproper\n        make defconfig\n        echo \"CONFIG_STACK_VALIDATION=n\" >> .config\n        echo \"CONFIG_WERROR=n\" >> .config\n        if [[ \"$KERNEL_VERSION\" == \"5.\"* ]]; then\n          sed -i 's/-Werror//g' tools/objtool/Makefile\n          sed -i 's/-Werror//g' tools/lib/subcmd/Makefile\n          export HOSTCFLAGS=\"-Wno-error=deprecated-declarations -Wno-error=use-after-free\"\n        fi\n        make modules_prepare -j$(nproc)\n        echo \"KERNEL_DIR=$(pwd)\" >> $GITHUB_ENV\n    - name: Build Lunatik\n      run: |\n        make clean MODULES_BUILD_PATH=\"${{ env.KERNEL_DIR }}\" || true\n        make -j$(nproc) MODULES_BUILD_PATH=\"${{ env.KERNEL_DIR }}\" KBUILD_MODPOST_WARN=1\n    - name: Verify modules built\n      run: |\n        test -f lunatik.ko || { echo \"Build failed\" ; exit 1; }\n        echo \"Lunatik built successfully for kernel ${{ matrix.kernel-version }}\"\n\n"
  },
  {
    "path": ".github/workflows/doc.yml",
    "content": "# Based on https://gist.github.com/domenic/ec8b0fc8ab45f39403dd\nname: Build Docs\non:\n  pull_request:\n    branches:\n    - master\n  push:\n    branches:\n    - master\njobs:\n  build:\n    name: Build docs\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v3\n    - name: Setup Lua\n      uses: leafo/gh-actions-lua@v8\n      with:\n        luaVersion: 5.4\n    - name: Setup Lua Rocks\n      uses: leafo/gh-actions-luarocks@v4\n    - name: Setup dependencies\n      run: luarocks install ldoc\n    - name: Build docs\n      run: make doc-site\n    - name: Deploy\n      if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}\n      uses: peaceiris/actions-gh-pages@v3\n      with:\n        github_token: ${{ secrets.GITHUB_TOKEN }}\n        publish_dir: doc\n\n"
  },
  {
    "path": ".gitignore",
    "content": "*.[aod]\n*.cmd\n*.ko\n*.mod\n*.mod.*\n*.so\nModule.symvers\nmodules.builtin\nmodules.order\nbuild/\nlunatik_sym.h\nmoontastik*\ndnstest\ndoc/*\n!doc/capi.md\n\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"lua\"]\n\tpath = lua\n\turl = https://github.com/luainkernel/lua.git\n[submodule \"klibc\"]\n\tpath = klibc\n\turl = https://github.com/luainkernel/klibc.git\n"
  },
  {
    "path": "Kbuild",
    "content": "# SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n\nifeq ($(ARCH), x86)\n\tifdef CONFIG_X86_32\n\t\tKLIBC_ARCH := i386\n\t\tasflags-y += -D_REGPARM\n\telse\n\t\tKLIBC_ARCH := x86_64\n\tendif\nelse\n\tKLIBC_ARCH := $(ARCH)\nendif\n\nKLIBC_USR := klibc/usr\nKLIBC_INC := $(KLIBC_USR)/include/arch/$(KLIBC_ARCH)\nKLIBC_LIBGCC := $(KLIBC_USR)/klibc/libgcc\n\nLUNATIK_FLAGS := -D_LUNATIK -D_KERNEL -I${PWD}/$(KLIBC_INC)\n\nasflags-y += $(LUNATIK_FLAGS)\nccflags-y += $(LUNATIK_FLAGS) -DLUNATIK_RUNTIME=$(CONFIG_LUNATIK_RUNTIME) \\\n\t-Wimplicit-fallthrough=0 -I$(src) -I${PWD} -I${PWD}/include -I${PWD}/lua\nsubdir-ccflags-y += $(ccflags-y)\n\nobj-$(CONFIG_LUNATIK) += lunatik.o\n\nlunatik-objs += lua/lapi.o lua/lcode.o lua/lctype.o lua/ldebug.o lua/ldo.o \\\n\tlua/ldump.o lua/lfunc.o lua/lgc.o lua/llex.o lua/lmem.o \\\n\tlua/lobject.o lua/lopcodes.o lua/lparser.o lua/lstate.o \\\n\tlua/lstring.o lua/ltable.o lua/ltm.o \\\n\tlua/lundump.o lua/lvm.o lua/lzio.o lua/lauxlib.o lua/lbaselib.o \\\n\tlua/lcorolib.o lua/ldblib.o lua/lstrlib.o \\\n\tlua/ltablib.o lua/lutf8lib.o lua/lmathlib.o lua/liolib.o lua/linit.o \\\n\tlua/loadlib.o $(KLIBC_USR)/klibc/arch/$(KLIBC_ARCH)/setjmp.o \\\n\tlunatik_aux.o lunatik_obj.o lunatik_val.o lunatik_core.o\n\nifeq ($(CONFIG_64BIT),)\nlunatik-objs += $(KLIBC_LIBGCC)/__udivmoddi4.o\t\\\n\t$(KLIBC_LIBGCC)/__divdi3.o $(KLIBC_LIBGCC)/__udivdi3.o \\\n\t$(KLIBC_LIBGCC)/__moddi3.o $(KLIBC_LIBGCC)/__umoddi3.o\nendif\n\nobj-$(CONFIG_LUNATIK_RUN) += lunatik_run.o\nobj-y += lib/\n\n"
  },
  {
    "path": "Kconfig",
    "content": "# SPDX-FileCopyrightText: (c) 2026 Tanmay Deobhankar <emailtotanmay@gmail.com>\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n\nmenuconfig LUNATIK\n\ttristate \"Lunatik Lua Interpreter\"\n\tdefault m\n\thelp\n\t  Lunatik is a framework for scripting the Linux kernel with Lua.\n\t  It allows you to write kernel drivers and extensions using Lua scripts.\n\nif LUNATIK\n\nconfig LUNATIK_RUNTIME\n\tbool\n\tdefault y\n\tselect LUNATIK_DEVICE\n\tselect LUNATIK_RCU\n\thelp\n\t  Enables the core Lunatik runtime environment, allowing creation of\n\t  isolated Lua states within the kernel. This requires both device\n\t  and RCU support.\n\nconfig LUNATIK_RUN\n\ttristate \"Lunatik Run (Execution Support)\"\n\tdefault y\n\thelp\n\t  Support for running Lua scripts in the kernel. This is required\n\t  for basic script execution functionality.\n\nconfig LUNATIK_DEVICE\n\ttristate \"Lunatik Device Support\"\n\tdefault m\n\thelp\n\t  Allows creating character devices using Lua.\n\nconfig LUNATIK_LINUX\n\ttristate \"Lunatik Linux API Bindings\"\n\tdefault m\n\thelp\n\t  Provides Lua bindings for common Linux kernel functions (random, stat, etc.).\n\nconfig LUNATIK_NOTIFIER\n\ttristate \"Lunatik Notifier Support\"\n\tdefault m\n\thelp\n\t  Support for kernel notifiers in Lua.\n\nconfig LUNATIK_SOCKET\n\ttristate \"Lunatik Socket Support\"\n\tdefault m\n\thelp\n\t  Networking socket support for Lua scripts.\n\nconfig LUNATIK_RCU\n\ttristate \"Lunatik RCU Support\"\n\tdefault m\n\thelp\n\t  Read-Copy-Update (RCU) synchronization support.\n\nconfig LUNATIK_THREAD\n\ttristate \"Lunatik Thread Support\"\n\tdefault m\n\thelp\n\t  Kernel thread management from Lua.\n\nconfig LUNATIK_FIB\n\ttristate \"Lunatik FIB (Forwarding Information Base) Support\"\n\tdefault m\n\thelp\n\t  Access to the kernel FIB (routing capability).\n\nconfig LUNATIK_DATA\n\ttristate \"Lunatik Data Support\"\n\tdefault m\n\thelp\n\t  Shared data structures support.\n\nconfig LUNATIK_SKB\n\ttristate \"Lunatik SKB (Socket Buffer) Support\"\n\tdefault m\n\tdepends on LUNATIK_DATA\n\thelp\n\t  Socket buffer (skb) access support for Lua scripts.\n\t  Requires Lunatik Data support.\n\nconfig LUNATIK_PROBE\n\ttristate \"Lunatik Probe (Kprobe/Kretprobe) Support\"\n\tdefault m\n\thelp\n\t  Dynamic tracing support using kprobes.\n\nconfig LUNATIK_SYSCALL\n\ttristate \"Lunatik Syscall Support\"\n\tdefault m\n\thelp\n\t  System call tracking and manipulation.\n\nconfig LUNATIK_XDP\n\ttristate \"Lunatik XDP Support\"\n\tdefault m\n\thelp\n\t  Express Data Path (XDP) support for high-performance packet processing.\n\nconfig LUNATIK_FIFO\n\ttristate \"Lunatik FIFO Support\"\n\tdefault m\n\thelp\n\t  First-In-First-Out queue support.\n\nconfig LUNATIK_NETFILTER\n\ttristate \"Lunatik Netfilter bindings\"\n\tdefault m\n\tdepends on LUNATIK_SKB\n\thelp\n\t  New Netfilter API bindings.\n\t  Requires Lunatik SKB support.\n\nconfig LUNATIK_COMPLETION\n\ttristate \"Lunatik Completion Support\"\n\tdefault m\n\thelp\n\t  Kernel completion synchronization primitives.\n\nconfig LUNATIK_CRYPTO\n\ttristate \"Lunatik Crypto Support\"\n\tdefault m\n\thelp\n\t  Linux Crypto API support (hash, cipher, AEAD, RNG, compression).\n\nconfig LUNATIK_DARKEN\n\ttristate \"Lunatik Darken (Encrypted Script) Support\"\n\tdepends on LUNATIK_CRYPTO\n\tdefault m\n\thelp\n\t  AES-256-CTR encrypted Lua script support.\n\nconfig LUNATIK_CPU\n\ttristate \"Lunatik CPU Support\"\n\tdefault m\n\thelp\n\t  CPU management and information.\n\nconfig LUNATIK_HID\n\ttristate \"Lunatik HID (Human Interface Device) Support\"\n\tdefault m\n\thelp\n\t  HID driver support in Lua.\n\nconfig LUNATIK_SIGNAL\n\ttristate \"Lunatik Signal Support\"\n\tdefault m\n\thelp\n\t  Signal handling support.\n\nconfig LUNATIK_BYTEORDER\n\ttristate \"Lunatik Byteorder Support\"\n\tdefault m\n\thelp\n\t  Byte order swapping functions.\n\nendif\n \n"
  },
  {
    "path": "LICENSE-GPL",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "MIT License\n\nCopyright (c) 2023-2025 Ring Zero Desenvolvimento de Software LTDA\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "# SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n\nKERNEL_RELEASE ?= ${shell uname -r}\nMODULES_PATH := /lib/modules\nMODULES_RELEASE_PATH := ${MODULES_PATH}/${KERNEL_RELEASE}\n# needed when zfs module is installed\nMODULES_ORDER_LIST := kernel/zfs/zfs.ko kernel/zfs/zlua.ko updates/dkms/zfs.ko updates/dkms/zlua.ko\nMODULES_ORDER_FILE := ${MODULES_RELEASE_PATH}/modules.order\nBTF_INSTALL_PATH = ${MODULES_RELEASE_PATH}/build\nMODULES_BUILD_PATH ?= ${BTF_INSTALL_PATH}\nMODULES_INSTALL_PATH := ${MODULES_RELEASE_PATH}/kernel\nSCRIPTS_INSTALL_PATH := ${MODULES_PATH}/lua\nINCLUDE_PATH := ${MODULES_BUILD_PATH}/include\n\nLUA ?= lua5.4\nLUA_PATH ?= $(shell $(LUA) -e 'print(package.path:match(\"([^;]*)/%?%.lua;\"))')\n\nLUNATIK_INSTALL_PATH = /usr/local/sbin\nLUNATIK_EBPF_INSTALL_PATH = /usr/local/lib/bpf/lunatik\nLUNATIK_TESTS_INSTALL_PATH = /usr/local/share/lunatik/tests\nMOONTASTIK_RELEASE ?= v0.1c\nLUA_API = lua/lua.h lua/lauxlib.h lua/lualib.h\nRM = rm -f\nMKDIR = mkdir -p -m 0755\nLN = ln -sf\nINSTALL = install -o root -g root\n\nCONFIG_LUNATIK ?= m\nCONFIG_LUNATIK_RUNTIME ?= y\nCONFIG_LUNATIK_RUN ?= m\n\n# Order matters: modules are loaded left-to-right and unloaded right-to-left (rmmod).\n# A module must appear AFTER all modules it depends on (e.g. SKB before NETFILTER).\nLUNATIK_MODULES := DEVICE LINUX NOTIFIER SOCKET RCU THREAD FIB DATA PROBE SYSCALL XDP FIFO SKB NETFILTER \\\n\tCOMPLETION CRYPTO CPU HID SIGNAL BYTEORDER DARKEN\n\n$(foreach c,$(LUNATIK_MODULES),\\\n\t$(eval CONFIG_LUNATIK_$(c) ?= m))\n\n# disable modules here, e.g.:\n# CONFIG_LUNATIK_SYSCALL := n\n\nLUNATIK_CONFIG_MODULES := \\\n\t$(foreach c,$(LUNATIK_MODULES),CONFIG_LUNATIK_$(c)=$(CONFIG_LUNATIK_$(c)))\n\nLUNATIK_CONFIG_FLAGS := CONFIG_LUNATIK=$(CONFIG_LUNATIK) CONFIG_LUNATIK_RUNTIME=$(CONFIG_LUNATIK_RUNTIME) \\\n\tCONFIG_LUNATIK_RUN=$(CONFIG_LUNATIK_RUN) $(LUNATIK_CONFIG_MODULES)\n\nLUNATIK_MODULES := \\\n\t$(foreach c,$(LUNATIK_MODULES),\\\n\t\t$(if $(filter y m,$(CONFIG_LUNATIK_$(c))),$(c)))\n\nall: lunatik_sym.h configure\n\t${MAKE} -C ${MODULES_BUILD_PATH} M=${PWD} $(LUNATIK_CONFIG_FLAGS)\n\nclean:\n\t${MAKE} -C ${MODULES_BUILD_PATH} M=${PWD} clean\n\t${MAKE} -C examples/filter clean\n\t${RM} lunatik_sym.h\n\t${RM} autogen/lunatik/*.lua\n\t${RM} autogen/linux/*.lua\n\nscripts_install:\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/lunatik\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/socket\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/syscall\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/crypto\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/linux\n\t${MKDIR} ${LUA_PATH}/lunatik\n\t${INSTALL} -m 0644 driver.lua ${SCRIPTS_INSTALL_PATH}/\n\t${INSTALL} -m 0644 lib/mailbox.lua ${SCRIPTS_INSTALL_PATH}/\n\t${INSTALL} -m 0644 lib/net.lua ${SCRIPTS_INSTALL_PATH}/\n\t${INSTALL} -m 0644 lib/util.lua ${SCRIPTS_INSTALL_PATH}/\n\t${INSTALL} -m 0644 lib/lighten.lua ${SCRIPTS_INSTALL_PATH}/\n\t${INSTALL} -m 0644 lib/lunatik/*.lua ${SCRIPTS_INSTALL_PATH}/lunatik\n\t${INSTALL} -m 0644 lib/socket/*.lua ${SCRIPTS_INSTALL_PATH}/socket\n\t${INSTALL} -m 0644 lib/syscall/*.lua ${SCRIPTS_INSTALL_PATH}/syscall\n\t${INSTALL} -m 0644 lib/crypto/*.lua ${SCRIPTS_INSTALL_PATH}/crypto\n\t${INSTALL} -m 0644 autogen/linux/*.lua ${SCRIPTS_INSTALL_PATH}/linux\n\t${INSTALL} -m 0644 autogen/lunatik/*.lua ${SCRIPTS_INSTALL_PATH}/lunatik\n\t${LN} ${SCRIPTS_INSTALL_PATH}/lunatik/config.lua ${LUA_PATH}/lunatik/config.lua\n\t${INSTALL} -D -m 0755 bin/lunatik ${LUNATIK_INSTALL_PATH}/lunatik\n\nscripts_uninstall:\n\t${RM} ${SCRIPTS_INSTALL_PATH}/driver.lua\n\t${RM} ${SCRIPTS_INSTALL_PATH}/runner.lua\n\t${RM} ${SCRIPTS_INSTALL_PATH}/mailbox.lua\n\t${RM} ${SCRIPTS_INSTALL_PATH}/net.lua\n\t${RM} -r ${SCRIPTS_INSTALL_PATH}/lunatik\n\t${RM} -r ${SCRIPTS_INSTALL_PATH}/socket\n\t${RM} -r ${SCRIPTS_INSTALL_PATH}/syscall\n\t${RM} -r ${SCRIPTS_INSTALL_PATH}/crypto\n\t${RM} -r ${SCRIPTS_INSTALL_PATH}/linux\n\t${RM} ${LUNATIK_INSTALL_PATH}/lunatik\n\t${RM} -r ${LUA_PATH}/lunatik\n\n.PHONY: ebpf defines\nebpf:\n\t${MAKE} -C examples/filter\n\nebpf_install:\n\t${MKDIR} ${LUNATIK_EBPF_INSTALL_PATH}\n\t${INSTALL} -m 0644 examples/filter/https.o ${LUNATIK_EBPF_INSTALL_PATH}/\n\nebpf_uninstall:\n\t${RM} -r ${LUNATIK_EBPF_INSTALL_PATH}\n\nexamples_install:\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/examples\n\t${INSTALL} -m 0644 examples/*.lua ${SCRIPTS_INSTALL_PATH}/examples\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/examples/echod\n\t${INSTALL} -m 0644 examples/echod/*.lua ${SCRIPTS_INSTALL_PATH}/examples/echod\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/examples/filter\n\t${INSTALL} -m 0644 examples/filter/*.lua ${SCRIPTS_INSTALL_PATH}/examples/filter\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/examples/dnsblock\n\t${INSTALL} -m 0644 examples/dnsblock/*.lua ${SCRIPTS_INSTALL_PATH}/examples/dnsblock\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/examples/dnsdoctor\n\t${INSTALL} -m 0644 examples/dnsdoctor/*.lua ${SCRIPTS_INSTALL_PATH}/examples/dnsdoctor\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/examples/tcpreject\n\t${INSTALL} -m 0644 examples/tcpreject/*.lua ${SCRIPTS_INSTALL_PATH}/examples/tcpreject\n\nexamples_uninstall:\n\t${RM} -r ${SCRIPTS_INSTALL_PATH}/examples\n\ntests_install:\n\t${MKDIR} ${LUNATIK_TESTS_INSTALL_PATH}\n\t${INSTALL} -m 0755 tests/run.sh ${LUNATIK_TESTS_INSTALL_PATH}\n\t${INSTALL} -m 0644 tests/lib.sh ${LUNATIK_TESTS_INSTALL_PATH}\n\t${MKDIR} ${LUNATIK_TESTS_INSTALL_PATH}/rcu\n\t${INSTALL} -m 0755 tests/rcu/run.sh tests/rcu/map_sync.sh ${LUNATIK_TESTS_INSTALL_PATH}/rcu\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/tests/rcu\n\t${INSTALL} -m 0644 tests/rcu/*.lua ${SCRIPTS_INSTALL_PATH}/tests/rcu\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/tests/crypto\n\t${INSTALL} -m 0644 tests/crypto/*.lua ${SCRIPTS_INSTALL_PATH}/tests/crypto\n\t${MKDIR} ${LUNATIK_TESTS_INSTALL_PATH}/crypto\n\t${INSTALL} -m 0755 tests/crypto/run.sh ${LUNATIK_TESTS_INSTALL_PATH}/crypto\n\t${MKDIR} ${LUNATIK_TESTS_INSTALL_PATH}/monitor\n\t${INSTALL} -m 0755 tests/monitor/*.sh ${LUNATIK_TESTS_INSTALL_PATH}/monitor\n\t${INSTALL} -m 0644 tests/monitor/*.lua ${SCRIPTS_INSTALL_PATH}/tests/monitor\n\t${MKDIR} ${LUNATIK_TESTS_INSTALL_PATH}/thread\n\t${INSTALL} -m 0755 tests/thread/*.sh ${LUNATIK_TESTS_INSTALL_PATH}/thread\n\t${INSTALL} -m 0644 tests/thread/*.lua ${SCRIPTS_INSTALL_PATH}/tests/thread\n\t${MKDIR} ${LUNATIK_TESTS_INSTALL_PATH}/runtime\n\t${INSTALL} -m 0755 tests/runtime/*.sh ${LUNATIK_TESTS_INSTALL_PATH}/runtime\n\t${INSTALL} -m 0644 tests/runtime/*.lua ${SCRIPTS_INSTALL_PATH}/tests/runtime\n\t${MKDIR} ${LUNATIK_TESTS_INSTALL_PATH}/socket/unix\n\t${INSTALL} -m 0755 tests/socket/run.sh ${LUNATIK_TESTS_INSTALL_PATH}/socket\n\t${INSTALL} -m 0755 tests/socket/unix/*.sh ${LUNATIK_TESTS_INSTALL_PATH}/socket/unix\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/tests/socket/unix\n\t${INSTALL} -m 0644 tests/socket/unix/*.lua ${SCRIPTS_INSTALL_PATH}/tests/socket/unix\n\t${MKDIR} ${LUNATIK_TESTS_INSTALL_PATH}/io\n\t${INSTALL} -m 0755 tests/io/*.sh ${LUNATIK_TESTS_INSTALL_PATH}/io\n\t${MKDIR} ${SCRIPTS_INSTALL_PATH}/tests/io\n\t${INSTALL} -m 0644 tests/io/*.lua ${SCRIPTS_INSTALL_PATH}/tests/io\n\ntests_uninstall:\n\t${RM} -r ${SCRIPTS_INSTALL_PATH}/tests\n\t${RM} -r ${LUNATIK_TESTS_INSTALL_PATH}\n\nmodules_install:\n\t${MKDIR} ${MODULES_INSTALL_PATH}/lunatik\n\t${INSTALL} -m 0644 *.ko lib/*.ko ${MODULES_INSTALL_PATH}/lunatik\n\nbtf_install:\n\tcp /sys/kernel/btf/vmlinux ${BTF_INSTALL_PATH}\n\nmodules_uninstall:\n\t${RM} -r ${MODULES_INSTALL_PATH}/lunatik\n\ninstall: scripts_install modules_install\n\tfor mod in $(MODULES_ORDER_LIST); do \\\n\t\tsed -i \"\\|^$$mod$$|d\" $(MODULES_ORDER_FILE); \\\n\t\techo \"$$mod\" >> $(MODULES_ORDER_FILE); \\\n\tdone\n\tdepmod -a\n\nuninstall: scripts_uninstall modules_uninstall\n\tfor mod in $(MODULES_ORDER_LIST); do \\\n\t\tsed -i \"\\|^$$mod$$|d\" $(MODULES_ORDER_FILE); \\\n\tdone\n\tdepmod -a\n\nlunatik_sym.h: $(LUA_API) gensymbols.sh\n\t${shell CC='$(CC)' ./gensymbols.sh $(LUA_API) > lunatik_sym.h}\n\nconfigure:\n\tCC='$(CC)' \"$(LUA)\" configure.lua \"$(KERNEL_RELEASE)\" \"$(INCLUDE_PATH)\" \"$(LUNATIK_MODULES)\"\n\nmoontastik_install_%:\n\t[ $* ] || (echo \"usage: make moontastik_install_TARGET\" ; exit 1)\n\twget https://github.com/luainkernel/moontastik/releases/download/${MOONTASTIK_RELEASE}/moontastik_lua.zip -O moontastik_lua.zip\n\t[ -d moontastik_lua ] && ${RM} -r moontastik_lua || true\n\tunzip moontastik_lua.zip\n\tcd moontastik_lua/\"$*\" && ./install.sh ; cd -\n\nmoontastik_uninstall_%:\n\t[ $* ] || (echo \"usage: make moontastik_uninstall_TARGET\" ; exit 1)\n\t${RM} -r ${SCRIPTS_INSTALL_PATH}/$*\n\ndoc-site:\n\tldoc .\n\n"
  },
  {
    "path": "README.md",
    "content": "# Lunatik\n\nLunatik is a framework for scripting the Linux kernel with [Lua](https://www.lua.org/).\nIt is composed by the Lua interpreter modified to run in the kernel;\na [device driver](driver.lua) (written in Lua =)) and a [command line tool](bin/lunatik)\nto load and run scripts and manage runtime environments from the user space;\na [C API](doc/capi.md) to load and run scripts and manage runtime environments from the kernel;\nand [Lua APIs](#lunatik-lua-apis) for binding kernel facilities to Lua scripts.\n\n> Note: Lunatik supports Linux Kernel versions 5.15 and later\n\nFeel free to join us on [Matrix](https://matrix.to/#/#lunatik:matrix.org).\n\nHere is an example of a character device driver written in Lua using Lunatik\nto generate random ASCII printable characters:\n```Lua\n-- /lib/modules/lua/passwd.lua\n--\n-- implements /dev/passwd for generate passwords\n-- usage: $ sudo lunatik run passwd\n--        $ head -c <width> /dev/passwd\n\nlocal device = require(\"device\")\nlocal linux  = require(\"linux\")\n\nlocal s = linux.stat\nlocal driver = {name = \"passwd\", mode = s.IRUGO}\n\nfunction driver:read() -- read(2) callback\n\t-- generate random ASCII printable characters\n\treturn string.char(linux.random(32, 126))\nend\n\n-- creates a new character device\ndevice.new(driver)\n```\n\n## Setup\n\nInstall dependencies (here for Debian/Ubuntu, to be adapted to one's distribution):\n\n```sh\nsudo apt install git build-essential lua5.4 dwarves clang llvm libelf-dev linux-headers-$(uname -r) linux-tools-common linux-tools-$(uname -r) pkg-config libpcap-dev m4\n```\n\nInstall dependencies (here for Arch Linux):\n\n```sh\nsudo pacman -S git lua clang llvm m4 libpcap pkg-config build2 linux-tools linux-headers\n```\n\nThe `lua-readline` package is optional. When installed, the REPL gains line editing and command history:\n\n```sh\nsudo apt install lua-readline  # Debian/Ubuntu\n```\n\nCompile and install `lunatik`:\n\n```sh\nLUNATIK_DIR=~/lunatik  # to be adapted\nmkdir \"${LUNATIK_DIR}\" ; cd \"${LUNATIK_DIR}\"\ngit clone --depth 1 --recurse-submodules https://github.com/luainkernel/lunatik.git\ncd lunatik\nmake\nsudo make install\n```\n\nOnce done, the `debian_kernel_postinst_lunatik.sh` script from tools/ may be copied into\n`/etc/kernel/postinst.d/`: this ensures `lunatik` (and also the `xdp` needed libs) will get\ncompiled on kernel upgrade.\n\n### OpenWRT\n\nInstall Lunatik from our [package feed](https://github.com/luainkernel/openwrt_feed/tree/openwrt-23.05).\n\n## Usage\n\n```\nsudo lunatik # execute Lunatik REPL\nLunatik 4.2  Copyright (C) 2023-2026 Ring Zero Desenvolvimento de Software LTDA.\n> return 42 -- execute this line in the kernel\n42\n```\n\n### lunatik\n\n```Shell\nusage: lunatik [load|unload|reload|status|test|list] [run|spawn|stop <script>]\n```\n\n* `load`: load Lunatik kernel modules\n* `unload`: unload Lunatik kernel modules\n* `reload`: reload Lunatik kernel modules\n* `status`: show which Lunatik kernel modules are currently loaded\n* `test [suite]`: run installed test suites (see [Testing](#testing))\n* `list`: show which runtime environments are currently running\n* `run [softirq]`: create a new runtime environment to run the script `/lib/modules/lua/<script>.lua`; pass `softirq` for hooks that fire in atomic context (netfilter, XDP)\n* `spawn`: create a new runtime environment and spawn a thread to run the script `/lib/modules/lua/<script>.lua`\n* `stop`: stop the runtime environment created to run the script `<script>`\n* `default`: start a _REPL (Read–Eval–Print Loop)_\n\n### Testing\n\nInstall and run the test suites:\n\n```sh\nsudo make tests_install\nsudo lunatik test           # run all suites\nsudo lunatik test thread    # run a specific suite (monitor, thread, runtime)\n```\n\n## Lua Version\n\nLunatik 4.2 is based on\n[Lua 5.5 adapted](https://github.com/luainkernel/lua)\nto run in the kernel.\n\n### Floating-point numbers\n\nLunatik **does not** support floating-point arithmetic,\nthus it **does not** support `__div` nor `__pow`\n[metamethods](https://www.lua.org/manual/5.5/manual.html#2.4)\nand the type _number_ has only the subtype _integer_.\n\n### Lua API\n\nLunatik **does not** support the [os](https://www.lua.org/manual/5.5/manual.html#6.9) library,\nfloating-point arithmetic (`__div`, `__pow`), or `debug.debug`.\nThe `math` library is present but all floating-point functions are absent —\nonly integer operations are supported.\n\nThe [io](https://www.lua.org/manual/5.5/manual.html#6.8) library is supported with the\nfollowing limitations: there are no default streams (`io.stdin`, `io.stdout`, `io.stderr`),\nno default input/output (`io.read`, `io.write`, `io.input`, `io.output`), no process pipes\n(`io.popen`), no temporary files (`io.tmpfile`), and no buffering control (`file:setvbuf`).\nThe available functions are `io.open`, `io.lines`, `io.type`, and the file handle methods\n`read`, `write`, `lines`, `flush`, `seek`, and `close`.\nOn failure, error messages always read `\"I/O error\"` regardless of the underlying errno.\n\nLunatik **modifies** the following identifiers:\n* [\\_VERSION](https://www.lua.org/manual/5.5/manual.html#pdf-_VERSION): is defined as `\"Lua 5.5-kernel\"`.\n* [collectgarbage(\"count\")](https://www.lua.org/manual/5.5/manual.html#pdf-collectgarbage): returns the total memory in use by Lua in **bytes**, instead of _Kbytes_.\n* [package.path](https://www.lua.org/manual/5.5/manual.html#pdf-package.path): is defined as `\"/lib/modules/lua/?.lua;/lib/modules/lua/?/init.lua\"`.\n* [require](https://www.lua.org/manual/5.5/manual.html#pdf-require): only supports built-in or already linked C modules, that is, Lunatik **cannot** load kernel modules dynamically.\n\n## Lunatik Lua APIs\n\nLua APIs are documented with [LDoc](https://stevedonovan.github.io/ldoc/) and can be browsed at\n[luainkernel.github.io/lunatik](https://luainkernel.github.io/lunatik/).\n\nThe table below lists the available kernel Lua modules:\n\n| Module | Description |\n|--------|-------------|\n| `linux` | Kernel utilities: `schedule`, `time`, `random`, `stat` flags |\n| `thread` | Kernel threads: spawn, stop, `shouldstop` |\n| `socket` | Kernel sockets: TCP, UDP, AF\\_PACKET, AF\\_UNIX |\n| `data` | Raw memory buffer for binary data read/write |\n| `device` | Character device drivers |\n| `rcu` | RCU-protected shared hash table |\n| `netfilter` | Netfilter hooks: register packet processing callbacks |\n| `skb` | Socket buffer (`sk_buff`): inspect and modify packets |\n| `xdp` | XDP (eXpress Data Path) hooks |\n| `crypto` | Kernel crypto API: hash, cipher, AEAD, RNG, compression |\n| `hid` | HID device drivers |\n| `probe` | Kernel probes (kprobe / tracepoint) |\n| `fib` | FIB routing table lookup |\n| `fifo` | Kernel FIFO queues |\n| `signal` | POSIX signal management |\n| `byteorder` | Network byte order conversions |\n| `darken` | AES-256-CTR encrypted script execution |\n| `lighten` | Lua interface for running encrypted scripts via `darken` |\n| `notifier` | Kernel notifier chain registration |\n| `lunatik.runner` | Run, spawn, and stop scripts from within Lua |\n| `net` | Networking helpers |\n| `mailbox` | Asynchronous inter-runtime messaging |\n\n## Lunatik C API\n\nThe [C API](doc/capi.md) allows kernel modules to create and manage Lunatik runtime\nenvironments, define new object classes, and expose kernel facilities to Lua scripts.\nSee [doc/capi.md](doc/capi.md) for the full reference.\n\n# Examples\n\n### spyglass\n\n[spyglass](examples/spyglass.lua)\nis a kernel script that implements a _keylogger_ inspired by the\n[spy](https://github.com/jarun/spy) kernel module.\nThis kernel script logs the _keysym_ of the pressed keys in a device (`/dev/spyglass`).\nIf the _keysym_ is a printable character, `spyglass` logs the _keysym_ itself;\notherwise, it logs a mnemonic of the ASCII code, (e.g., `<del>` stands for `127`).\n\n#### Usage\n\n```\nsudo make examples_install          # installs examples\nsudo lunatik run examples/spyglass  # runs spyglass\nsudo tail -f /dev/spyglass          # prints the key log\nsudo sh -c \"echo 'enable=false' > /dev/spyglass\"       # disable the key logging\nsudo sh -c \"echo 'enable=true' > /dev/spyglass\"        # enable the key logging\nsudo sh -c \"echo 'net=127.0.0.1:1337' > /dev/spyglass\" # enable network support\nnc -lu 127.0.0.1 1337 &             # listen to UDP 127.0.0.1:1337\nsudo tail -f /dev/spyglass          # sends the key log through the network\n```\n\n### keylocker\n\n[keylocker](examples/keylocker.lua)\nis a kernel script that implements\n[Konami Code](https://en.wikipedia.org/wiki/Konami_Code)\nfor locking and unlocking the console keyboard.\nWhen the user types `↑ ↑ ↓ ↓ ← → ← → LCTRL LALT`,\nthe keyboard will be _locked_; that is, the system will stop processing any key pressed\nuntil the user types the same key sequence again.\n\n#### Usage\n\n```\nsudo make examples_install                     # installs examples\nsudo lunatik run examples/keylocker            # runs keylocker\n<↑> <↑> <↓> <↓> <←> <→> <←> <→> <LCTRL> <LALT> # locks keyboard\n<↑> <↑> <↓> <↓> <←> <→> <←> <→> <LCTRL> <LALT> # unlocks keyboard\n```\n\n### tap\n\n[tap](examples/tap.lua)\nis a kernel script that implements a _sniffer_ using `AF_PACKET` socket.\nIt prints destination and source MAC addresses followed by Ethernet type and the frame size.\n\n#### Usage\n\n```\nsudo make examples_install    # installs examples\nsudo lunatik run examples/tap # runs tap\ncat /dev/tap\n```\n\n### shared\n\n[shared](examples/shared.lua)\nis a kernel script that implements an in-memory key-value store using\n[rcu](https://github.com/luainkernel/lunatik#rcu),\n[data](https://github.com/luainkernel/lunatik#data),\n[socket](https://github.com/luainkernel/lunatik#socket) and\n[thread](https://github.com/luainkernel/lunatik#thread).\n\n#### Usage\n\n```\nsudo make examples_install         # installs examples\nsudo lunatik spawn examples/shared # spawns shared\nnc 127.0.0.1 90                    # connects to shared\nfoo=bar                            # assigns \"bar\" to foo\nfoo                                # retrieves foo\nbar\n^C                                 # finishes the connection\n```\n\n### echod\n\n[echod](examples/echod)\nis an echo server implemented as kernel scripts.\n\n#### Usage\n\n```\nsudo make examples_install               # installs examples\nsudo lunatik spawn examples/echod/daemon # runs echod\nnc 127.0.0.1 1337\nhello kernel!\nhello kernel!\n```\n\n### systrack\n\n[systrack](examples/systrack.lua)\nis a kernel script that implements a device driver to monitor system calls.\nIt prints the amount of times each [system call](examples/systrack.lua#L29)\nwas called since the driver has been installed.\n\n#### Usage\n\n```\nsudo make examples_install         # installs examples\nsudo lunatik run examples/systrack # runs systracker\ncat /dev/systrack\nwritev: 0\nclose: 1927\nwrite: 1085\nopenat: 2036\nread: 4131\nreadv: 0\n```\n\n### filter\n\n[filter](examples/filter) is a kernel extension composed by\na XDP/eBPF program to filter HTTPS sessions and\na Lua kernel script to filter [SNI](https://datatracker.ietf.org/doc/html/rfc3546#section-3.1) TLS extension.\nThis kernel extension drops any HTTPS request destinated to a\n[blacklisted](examples/filter/sni.lua#L35) server.\n\n#### Usage\n\nCompile and install `libbpf`, `libxdp` and `xdp-loader`:\n\n```sh\nmkdir -p \"${LUNATIK_DIR}\" ; cd \"${LUNATIK_DIR}\"  # LUNATIK_DIR must be set to the same value as above (Setup section)\ngit clone --depth 1 --recurse-submodules https://github.com/xdp-project/xdp-tools.git\ncd xdp-tools/lib/libbpf/src\nmake\nsudo DESTDIR=/ make install\ncd ../../../\nmake libxdp\ncd xdp-loader\nmake\nsudo make install\n```\n\nCome back to this repository, install and load the filter:\n\n```sh\ncd ${LUNATIK_DIR}/lunatik                    # cf. above\nsudo make btf_install                        # needed to export the 'bpf_luaxdp_run' kfunc\nsudo make examples_install                   # installs examples\nmake ebpf                                    # builds the XDP/eBPF program\nsudo make ebpf_install                       # installs the XDP/eBPF program\nsudo lunatik run examples/filter/sni softirq   # runs the Lua kernel script\nsudo xdp-loader load -m skb <ifname> https.o # loads the XDP/eBPF program\n```\n\nFor example, testing is easy thanks to [docker](https://www.docker.com).\nAssuming docker is installed and running:\n\n- in a terminal:\n```sh\nsudo xdp-loader load -m skb docker0 https.o\nsudo journalctl -ft kernel\n```\n- in another one:\n```sh\ndocker run --rm -it alpine/curl https://ebpf.io\n```\n\nThe system logs (in the first terminal) should display `filter_sni: ebpf.io DROP`, and the\n`docker run…` should return `curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to ebpf.io:443`.\n\n### filter in MoonScript\n\n[This other sni filter](https://github.com/luainkernel/snihook) uses netfilter api.\n\n### dnsblock\n\n[dnsblock](examples/dnsblock) is a kernel script that uses the netfilter framework ([luanetfilter](https://github.com/luainkernel/lunatik#netfilter)) to filter DNS packets.\nThis script drops any outbound DNS packet with question matching the blacklist provided by the user. By default, it will block DNS resolutions for the domains `github.com` and `gitlab.com`.\n\n#### Usage\n\n```\nsudo make examples_install              # installs examples\nsudo lunatik run examples/dnsblock/nf_dnsblock softirq\t# runs the Lua kernel script\n```\n\n### dnsdoctor\n\n[dnsdoctor](examples/dnsdoctor) is a kernel script that uses the netfilter framework ([luanetfilter](https://github.com/luainkernel/lunatik#netfilter)) to change the DNS response\nfrom Public IP to a Private IP if the destination IP matches the one provided by the user. For example, if the user\nwants to change the DNS response from `192.168.10.1` to `10.1.2.3` for the domain `lunatik.com` if the query is being sent to `10.1.1.2` (a private client), this script can be used.\n\n#### Usage\n\n```\nsudo make examples_install              # installs examples\nexamples/dnsdoctor/setup.sh             # sets up the environment\n\n# test the setup, a response with IP 192.168.10.1 should be returned\ndig lunatik.com\n\n# run the Lua kernel script\nsudo lunatik run examples/dnsdoctor/nf_dnsdoctor softirq\n\n# test the setup, a response with IP 10.1.2.3 should be returned\ndig lunatik.com\n\n# cleanup\nsudo lunatik unload\nexamples/dnsdoctor/cleanup.sh\n```\n\n### tcpreject\n\n[tcpreject](examples/tcpreject) is a kernel script that uses the\nnetfilter framework ([luanetfilter](https://github.com/luainkernel/lunatik#netfilter))\nand the socket buffer API ([luaskb](https://github.com/luainkernel/lunatik#skb))\nto inject a TCP RST toward the origin of forwarded packets.\n\nIt intercepts packets marked by an `nft` rule, builds a RST+ACK by\ncopying the original packet, inverting IP, MAC, and port addresses,\ntrimming the payload, and recomputing the checksums.\nIt supports both IPv4 and IPv6.\nBy default, it rejects forwarded HTTPS (TCP/443) connections to `8.8.8.8` (IPv4)\nand `2001:4860:4860::8888` (IPv6).\n\n#### Usage\n\n```\nsudo make examples_install              # installs examples\nsudo examples/tcpreject/setup.sh        # sets up namespace, nft mark rule, and loads the hook\n\n# connection is reset immediately (IPv4)\nip netns exec tcpreject curl --connect-timeout 2 https://8.8.8.8\n\n# connection is reset immediately (IPv6)\nip netns exec tcpreject curl --connect-timeout 2 https://[2001:4860:4860::8888]\n\n# cleanup\nsudo examples/tcpreject/cleanup.sh\n```\n\n### gesture\n\n[gesture](examples/gesture.lua)\nis a kernel script that implements a HID driver for QEMU USB Mouse (0627:0001).\nIt supports gestures: swiping right locks the mouse, and swiping left unlocks it.\n\n#### Usage\n\n1. You need to change the display protocal into `VNC` and enable USB mouse device in QEMU, the following configuration can help you disable PS2 mouse & enable USB mouse:\n\n```\n<features>\n\t<!-- ... -->\n\t<ps2 state=\"off\"/>\n\t<!-- ... -->\n</features>\n```\n\n2. run the gesture script:\n\n```\nsudo make examples_install \t\t\t# installs examples\nsudo lunatik run examples/gesture softirq \t# runs gesture\n# In QEMU window:\n# Drag right to lock the mouse\n# Drag left to unlock the mouse\n```\n\n### xiaomi\n\n[xiaomi](examples/xiaomi.lua)\nis a kernel script that ports the Xiaomi Silent Mouse driver to Lua using `luahid`.\nIt fixes the report descriptor for the device (`0x2717`:`0x5014`).\n\n#### Usage\n\n```\nsudo make examples_install \t\t# installs examples\nsudo lunatik run examples/xiaomi softirq \t# runs xiaomi driver\n```\n\nThen insert the Xiaomi Silent Mouse with bluetooth mode on and it should work properly.\n\n### lldpd\n\n[lldpd](examples/lldpd.lua) shows how to implement a simple LLDP transmitter in kernel space using Lunatik.\nIt periodically emits LLDP frames on a given interface using an AF_PACKET socket.\n\n#### Usage\n\n```\nsudo make examples_install                  # installs examples\n\n# the LLDP daemon sends frames on a single Ethernet interface\n# you may use an existing interface, or create a virtual one for testing\n\n# create a veth pair (the example uses veth0 by default)\nip link add veth0 type veth peer name veth1\nip link set veth0 up\nip link set veth1 up\n\nsudo lunatik spawn examples/lldpd           # runs lldpd\n\n# verify LLDP frames are being transmitted\nsudo tcpdump -i veth0 -e ether proto 0x88cc -vv\n```\n\n### cpuexporter\n\n[cpuexporter](examples/cpuexporter.lua) will gather CPU usage statistics and expose using [OpenMetrics text format](https://github.com/prometheus/OpenMetrics/blob/main/specification/OpenMetrics.md#text-format) at a UNIX socket file.\n\n#### Usage\n\n```shell\nsudo make examples_install         \t# installs examples\nsudo lunatik spawn examples/cpuexporter # runs cpuexporter\nsudo socat - UNIX-CONNECT:/tmp/cpuexporter.sock <<<\"\"\n# TYPE cpu_usage_system gauge\ncpu_usage_system{cpu=\"cpu1\"} 0.0000000000000000 1764094519529162\ncpu_usage_system{cpu=\"cpu0\"} 0.0000000000000000 1764094519529162\n# TYPE cpu_usage_idle gauge\ncpu_usage_idle{cpu=\"cpu1\"} 100.0000000000000000 1764094519529162\ncpu_usage_idle{cpu=\"cpu0\"} 100.0000000000000000 1764094519529162\n...\n```\n\n## References\n\n### Talks and Papers\n* [Scripting the Linux Routing Table with Lua](https://netdevconf.info/0x17/sessions/talk/scripting-the-linux-routing-table-with-lua.html) — Netdev 0x17 (2023)\n* [Linux Network Scripting with Lua](https://legacy.netdevconf.info/0x14/session.html?talk-linux-network-scripting-with-lua) — Netdev 0x14 (2020)\n* [Lua no Núcleo](https://www.youtube.com/watch?v=-ufBgy044HI) — Lua Workshop 2023, PUC-Rio (Portuguese)\n* [Scriptable Operating Systems with Lua](https://www.netbsd.org/~lneto/dls14.pdf) — DLS 2014\n* [Lua in Kernel](https://events.canonical.com/event/89/contributions/506/attachments/265/393/Lua%20in%20Kernel%20-%20OOSC%202024%20(2).pdf) — Opportunity Open Source Conference 2024\n\n### Articles\n* [From the Kernel to the Moon: A Journey into Lunatik Bindings](https://medium.com/@lourival.neto/from-the-kernel-to-the-moon-a-journey-into-lunatik-bindings-c2cac8816f9e) — Medium (2025)\n* [Is eBPF driving you crazy? Let it run Lunatik instead!](https://medium.com/@lourival.neto/is-ebpf-driving-you-crazy-let-it-run-lunatik-instead-4aca7d63e6fd) — Medium (2024)\n* [Lua in the kernel?](https://lwn.net/Articles/830154/) — LWN.net (2020)\n\n### GSoC Projects\n* [LuaHID](https://github.com/qrsikno2/GSoC2025) — LabLua (2025), Jieming Zhou\n* [Lunatik binding for Netfilter](https://summerofcode.withgoogle.com/archive/2024/projects/BIJAPZjf) — LabLua (2024), Mohammad Shehar Yaar Tausif\n* [Lua hook on kTLS](https://luainkernel.github.io/ktls/) — LabLua (2020), Xinzhe Wang\n* [Lunatik States Management](https://github.com/luainkernel/lunatik/pull/20) — LabLua (2020), Matheus Rodrigues\n* [XDP Lua](https://victornogueirario.github.io/xdplua/) — LabLua (2019), Victor Nogueira\n* [RCU binding for Lunatik](https://github.com/cmessias/lunatik) — LabLua (2018), Caio Messias\n* [Lunatik Socket Library](https://github.com/luainkernel/lunatik/pull/4) — LabLua (2018), Chengzhi Tan\n* [Port Lua Test Suite to the NetBSD Kernel](https://www.google-melange.com/archive/gsoc/2015/orgs/lablua/projects/gmesalazar.html) — LabLua (2015), Guilherme Salazar\n* [Lua scripting in the NetBSD kernel](http://netbsd-soc.sourceforge.net/projects/luakern/) — The NetBSD Foundation (2010), Lourival Vieira Neto\n\n## License\n\nLunatik is dual-licensed under [MIT](LICENSE-MIT) or [GPL-2.0-only](LICENSE-GPL).\n\n[Lua](https://github.com/luainkernel/lua) submodule is licensed under MIT.\nFor more details, see its [Copyright Notice](https://github.com/luainkernel/lua/blob/lunatik/lua.h#L530-L556).\n\n[Klibc](https://github.com/luainkernel/klibc) submodule is dual-licensed under BSD 3-Clause or GPL-2.0-only.\nFor more details, see its [LICENCE](https://github.com/luainkernel/klibc/blob/lunatik/usr/klibc/LICENSE) file.\n\n"
  },
  {
    "path": "autogen/linux/.gitignore",
    "content": "# Ignore everything in this directory\n*\n# Except this file\n!.gitignore\n\n"
  },
  {
    "path": "autogen/lunatik/.gitignore",
    "content": "# Ignore everything in this directory\n*\n# Except this file\n!.gitignore\n\n"
  },
  {
    "path": "bin/lunatik",
    "content": "#!/usr/bin/lua5.4\n--\n-- SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal config = require(\"lunatik.config\")\n\nlocal device    = \"/dev/lunatik\"\nlocal copyright = \"Copyright (C) 2023-2026 Ring Zero Desenvolvimento de Software LTDA.\"\nlocal modules   = config.modules\n\nlocal function probe()\n\treturn os.execute(\"[ -e \" .. device .. \" ]\")\nend\n\nlocal function dostring(chunk)\n\tdo\n\t\tlocal loader <close> = assert(io.open(device, \"r+\"))\n\t\tloader:write(chunk)\n\tend\n\tlocal reader <close> = assert(io.open(device, \"r\"))\n\treturn reader:read(\"a\")\nend\n\nlocal function sh(command, module, loaded)\n\tlocal op = loaded and \"&&\" or \"||\"\n\tos.execute(string.format(\"MODULE=%s;grep -wq $MODULE /proc/modules %s%s\", module, op, command))\nend\n\nlocal function load_modules()\n\tfor _, m in ipairs(modules) do\n\t\tsh(\"modprobe $MODULE\", m)\n\tend\n\tif not probe() then\n\t\terror(\"couldn't create \" .. device)\n\tend\n\tlocal result = dostring[[\n\t\tlunatik = require(\"lunatik\")\n\t\tlunatik.runner = require(\"lunatik.runner\")\n\t\tlunatik.runner.startup()\n\t]]\n\tif result ~= \"\" then error(result) end\nend\n\nlocal function unload_modules()\n\tif probe() then\n\t\tdostring(\"lunatik.runner.shutdown()\")\n\tend\n\tfor i = #modules, 1, -1 do\n\t\tsh(\"rmmod $MODULE\", modules[i], true)\n\tend\nend\n\nlocal function reload_modules()\n\tunload_modules()\n\tload_modules()\nend\n\nlocal function status_modules()\n\tfor _, m in ipairs(modules) do\n\t\tsh(\"echo $MODULE is loaded || echo $MODULE is not loaded\", m, true)\n\tend\nend\n\nlocal function ensure_loaded()\n\tif not probe() then load_modules() end\n\tlocal result = dostring[[\n\t\tlunatik = lunatik or require(\"lunatik\")\n\t\tlunatik.runner = lunatik.runner or require(\"lunatik.runner\")\n\t\tlunatik.runner.startup()\n\t]]\n\tif result ~= \"\" then error(result) end\nend\n\nlocal tests_path = \"/usr/local/share/lunatik/tests\"\n\nlocal function test_modules()\n\tensure_loaded()\n\tlocal suite  = arg[2]\n\tlocal target = suite and (tests_path .. \"/\" .. suite .. \"/run.sh\")\n\t                      or (tests_path .. \"/run.sh\")\n\tif not os.execute(\"[ -f \" .. target .. \" ]\") then\n\t\terror(\"tests not installed; run: sudo make tests_install\")\n\tend\n\tos.execute(\"bash \" .. target)\nend\n\nlocal commands = {\n\tload   = load_modules,\n\tunload = unload_modules,\n\treload = reload_modules,\n\tstatus = status_modules,\n\ttest   = test_modules,\n}\n\nlocal function readline_reader()\n\tlocal rl = require(\"readline\")\n\trl.set_readline_name(\"lunatik\")\n\tlocal function read_line(prompt)\n\t\tlocal line = rl.readline(prompt)\n\t\tif line and line ~= \"\" then rl.add_history(line) end\n\t\treturn line\n\tend\n\treturn read_line\nend\n\nlocal function fallback_reader(prompt)\n\tio.write(prompt)\n\tio.flush()\n\treturn io.read(\"l\")\nend\n\nlocal function try_as_expr(chunk)\n\treturn load(\"return \" .. chunk .. \";\") ~= nil\nend\n\nlocal function is_incomplete(chunk)\n\tlocal _, err = load(chunk)\n\treturn err ~= nil and err:sub(-5) == \"<eof>\"\nend\n\nlocal function repl(read_line)\n\tlocal line = read_line(\"> \")\n\twhile line ~= nil do\n\t\tlocal chunk = line\n\t\twhile is_incomplete(chunk) do\n\t\t\tlocal more = read_line(\">> \")\n\t\t\tif more == nil then break end\n\t\t\tchunk = chunk .. \"\\n\" .. more\n\t\tend\n\t\tlocal send = try_as_expr(chunk) and (\"return \" .. chunk) or chunk\n\t\tlocal res = dostring(send)\n\t\tif res ~= \"\" then print(res) end\n\t\tline = read_line(\"> \")\n\tend\nend\n\nlocal function start_repl()\n\tensure_loaded()\n\tlocal version = dostring(\"return _LUNATIK_VERSION\")\n\tprint(version .. \"  \" .. copyright)\n\tlocal ok, read_line = pcall(readline_reader)\n\tif not ok then read_line = fallback_reader end\n\trepl(read_line)\nend\n\nlocal cmd = commands[arg[1]]\nif cmd then\n\tcmd()\n\tos.exit()\nend\n\nif #arg >= 1 then\n\tlocal token  = arg[1]\n\tlocal script = arg[2]\n\tlocal tokens = {run = true, spawn = true, stop = true, list = true}\n\tlocal needs_script = {run = true, spawn = true, stop = true}\n\tif not tokens[token] or (needs_script[token] and not script) then\n\t\tprint(\"usage: lunatik [load|unload|reload|status|test|list] [run|spawn|stop <script>]\")\n\t\tos.exit(false)\n\tend\n\tensure_loaded()\n\tlocal parm  = arg[3] and (', \"' .. arg[3] .. '\"') or \"\"\n\tlocal ret   = token == \"list\" and \"return\" or \"\"\n\tlocal chunk = string.format(\"%s lunatik.runner.%s('%s'%s)\", ret, token, script, parm)\n\tprint(dostring(chunk))\n\tos.exit()\nend\n\nstart_repl()\n\n"
  },
  {
    "path": "config.ld",
    "content": "-- LDoc configuration file for the Lunatik project\n-- Place this file at the root of your project and run 'ldoc .'\n\n-- The name of your project\nproject = 'Lunatik'\n\ndescription = 'Lunatik is a framework for scripting the Linux kernel with Lua.'\n\n-- The title for the generated documentation\ntitle = 'Lunatik API Documentation'\n\n-- Source files to document.\n-- LDoc will recursively scan directories.\n-- By manually specifying order, we ensure menu order.\nfile = {\n\t'./lib/luabyteorder.c',\n\t'./lib/luacompletion.c',\n\t'./lib/luacpu.c',\n\t'./lib/crypto/hkdf.lua',\n\t'./lib/luacrypto_aead.c',\n\t'./lib/luacrypto_comp.c',\n\t'./lib/luacrypto_core.c',\n\t'./lib/luacrypto_rng.c',\n\t'./lib/luacrypto_shash.c',\n\t'./lib/luacrypto_skcipher.c',\n\t'./lib/luadarken.c',\n\t'./lib/luadata.c',\n\t'./lib/luadevice.c',\n\t'./lib/luafib.c',\n\t'./lib/luafifo.c',\n\t'./lib/luahid.c',\n\t'./lib/lighten.lua',\n\t'./lunatik_core.c',\n\t'./lib/lunatik/runner.lua',\n\t'./lib/lualinux.c',\n\t'./lib/mailbox.lua',\n\t'./lib/net.lua',\n\t'./lib/luanetfilter.h',\n\t'./lib/luanetfilter.c',\n\t'./lib/luaskb.c',\n\t'./lib/luanotifier.c',\n\t'./lib/luaprobe.c',\n\t'./lib/luarcu.c',\n\t'./lib/luasignal.c',\n\t'./lib/luasocket.c',\n\t'./lib/socket/inet.lua',\n\t'./lib/socket/raw.lua',\n\t'./lib/socket/unix.lua',\n\t'./lib/luasyscall.c',\n\t'./lib/syscall/table.lua',\n\t'./lib/luathread.c',\n\t'./lib/luaxdp.c',\n}\n\n-- examples = \"./examples\"\n\n-- The directory where LDoc will output the generated documentation.\ndir = 'doc'\n\n-- The output format. Common options: 'markdown', 'html'.\n-- 'markdown' is good for GitHub pages or further processing.\n-- 'html' generates a browsable website.\nformat = 'markdown'\n\n-- For HTML output, you can specify a style.\n-- style = '!fixed' -- A common built-in style\n-- style = '!menu'  -- Another built-in style with a navigation menu\n-- new_style = true -- Use with '!menu' for a more modern look\n\n-- If you have a README.md file you'd like to use as the main page content:\nreadme = 'README.md'\n\nboilerplate = true\n\n-- Merge documentation for modules that might be split across multiple files\n-- or have parts in C and Lua.\nmerge = true\n\n-- Sort modules and functions alphabetically in the output.\nsort = true\n\n-- By default, LDoc only documents exported functions and tables.\n-- Set to true to document everything, including local functions (usually not for API docs).\n-- all = false -- This is the default\n\n-- For more LDoc options, see: https://github.com/lunarmodules/LDoc\n\n"
  },
  {
    "path": "configure.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ashwani Kumar Kamal <ashwanikamal.im421@gmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal OUTPUT_DIR = \"autogen\"\nlocal SCRIPT  = arg[0]\nlocal KERNEL  = arg[1]\nlocal INCLUDE = arg[2]\nlocal MODULES = arg[3]\n\nlocal specs = {\n\t{\n\t\theader = \"uapi/linux/if_ether.h\",\n\t\tprefix = \"ETH_P_\",\n\t\tmodule_name = \"eth\",\n\t},\n\t{\n\t\theader = \"uapi/linux/stat.h\",\n\t\tprefix = \"S_\",\n\t\tmodule_name = \"stat\",\n\t},\n\t{\n\t\theader = \"uapi/linux/signal.h\",\n\t\tprefix = \"SIG\",\n\t\tmodule_name = \"signal\",\n\t}\n}\n\nlocal function exit(msg)\n\tio.stderr:write(string.format(\"%s: %s\\n\", SCRIPT, msg))\n\tos.exit(1)\nend\n\nif not KERNEL or not INCLUDE or not MODULES then\n\texit(\"usage: lua5.4 \" .. SCRIPT .. \" <KERNEL> <INCLUDE> <MODULES>\")\nend\n\nlocal CC = os.getenv(\"CC\") or \"cc\"\nlocal CPP = string.format(\"%s -E -dM -I%s\", CC, INCLUDE)\n\nlocal function preprocess(header_path)\n\tlocal cmd = string.format(\"%s %s/%s\", CPP, INCLUDE, header_path)\n\tlocal pipe <close> = io.popen(cmd)\n\tif not pipe then\n\t\texit(\"failed to run preprocessor on \" .. header_path)\n\tend\n\treturn pipe:read(\"*a\")\nend\n\nlocal function collect_constants(cpp_output, prefix)\n\tlocal constants = {}\n\tlocal macro_names = {}\n\n\tfor macro, literal in cpp_output:gmatch(\"#define%s+(\" .. prefix .. \"%w+)%s+(%S+)\") do\n\t\tconstants[macro] = literal\n\t\ttable.insert(macro_names, macro)\n\tend\n\n\ttable.sort(macro_names)\n\treturn constants, macro_names\nend\n\nlocal function resolve_value(value, constants, prefix, module_name, seen)\n\tif tonumber(value) then return value end\n\n\tif not constants[value] then return nil end\n\n\tseen = seen or {}\n\t-- recursion check: avoid cyclic defines\n\tif seen[value] then return nil end\n\tseen[value] = true\n\n\tif value:find(\"^\" .. prefix) then\n\t\tlocal stripped = value:gsub(\"^\" .. prefix, \"\")\n\t\treturn string.format('%s[\"%s\"]', module_name, stripped)\n\tend\n\n\treturn resolve_value(constants[value], constants, prefix, module_name, seen)\nend\n\nlocal function write_constants(file, module, spec, constants, macro_names)\n\tfor _, macro in ipairs(macro_names) do\n\t\tlocal field_name = macro:gsub(\"^\" .. spec.prefix, \"\")\n\t\tlocal resolved_value = resolve_value(constants[macro], constants, spec.prefix, spec.module_name)\n\t\tif resolved_value then\n\t\t\tfile:write(string.format('%s[\"%s\"]\\t= %s\\n', spec.module_name, field_name, resolved_value))\n\t\tend\n\tend\nend\n\nlocal function write_module(dir, module, writer, ...)\n\tlocal filepath = string.format(\"%s/%s/%s.lua\", OUTPUT_DIR, dir, module)\n\tlocal file <close>, err = io.open(filepath, \"w\")\n\tif not file then\n\t\texit(\"cannot open \" .. filepath .. \": \" .. err)\n\tend\n\tfile:write(\"-- auto-generated, do not edit\\n\")\n\tfile:write(\"-- kernel: \" .. KERNEL .. \"\\n\\n\")\n\tfile:write(\"local \" .. module .. \" = {}\\n\\n\")\n\twriter(file, module, ...)\n\tfile:write(\"\\nreturn \" .. module .. \"\\n\\n\")\nend\n\nlocal function write_config(file, module)\n\tfile:write(string.format('%s.kernel_version = \"%s\"\\n', module, KERNEL))\n\tfile:write(string.format('%s.modules = {\\n\\t\"lunatik\",\\n', module))\n\tfor m in MODULES:gmatch(\"%S+\") do\n\t\tfile:write(string.format('\\t\"lua%s\",\\n', m:lower()))\n\tend\n\tfile:write('\\t\"lunatik_run\"\\n}\\n')\nend\n\nfor _, spec in ipairs(specs) do\n\tlocal cpp_output = preprocess(spec.header)\n\tlocal constants, macro_names = collect_constants(cpp_output, spec.prefix)\n\twrite_module(\"linux\", spec.module_name, write_constants, spec, constants, macro_names)\nend\n\nwrite_module(\"lunatik\", \"config\", write_config)\n\n"
  },
  {
    "path": "doc/capi.md",
    "content": "# Lunatik C API\n\n```C\n#include <lunatik.h>\n```\n\n## Types\n\n### lunatik\\_class\\_t\n```C\ntypedef struct lunatik_class_s {\n\tconst char     *name;\n\tconst luaL_Reg *methods;\n\tvoid          (*release)(void *);\n\tlunatik_opt_t   opt;\n} lunatik_class_t;\n```\nDescribes a Lunatik object class.\n\n- `name`: class name; used as the argument to `require` and to identify the class.\n- `methods`: `NULL`-terminated array of Lua methods registered in the metatable.\n- `release`: called when the object's reference counter reaches zero; may be `NULL`.\n- `opt`: bitmask of `LUNATIK_OPT_*` flags controlling class behaviour. Flags are inherited by\n  every instance via `object->opt = opt | class->opt` (see `lunatik_newobject`). Flags differ\n  in whether they act as **constraints** or **capabilities**:\n  - `LUNATIK_OPT_SOFTIRQ` *(constraint)*: all instances use a spinlock and `GFP_ATOMIC`; absence\n    means mutex and `GFP_KERNEL`. Because this flag is always inherited, a SOFTIRQ class can never\n    produce a non-SOFTIRQ instance.\n  - `LUNATIK_OPT_MONITOR` *(capability)*: the class supports a monitored metatable that wraps Lua\n    method calls with the object lock, enabling safe concurrent access from multiple runtimes.\n    Inherited by default but cancelled when an instance is created with `LUNATIK_OPT_SINGLE`.\n  - `LUNATIK_OPT_SINGLE` *(constraint)*: all instances are private and non-shareable by default.\n    Like `SOFTIRQ`, this is always inherited and cannot be overridden per instance.\n  - `LUNATIK_OPT_EXTERNAL` *(constraint)*: `object->private` holds an external pointer — Lunatik\n    will not free it on release.\n\n### lunatik\\_reg\\_t\n```C\ntypedef struct lunatik_reg_s {\n\tconst char  *name;\n\tlua_Integer  value;\n} lunatik_reg_t;\n```\nA name–integer-value pair used to export constants to Lua. Arrays must be terminated by `{NULL, 0}`.\n\n### lunatik\\_namespace\\_t\n```C\ntypedef struct lunatik_namespace_s {\n\tconst char          *name;\n\tconst lunatik_reg_t *reg;\n} lunatik_namespace_t;\n```\nA named table of `lunatik_reg_t` constants. Passed to `LUNATIK_NEWLIB` to create sub-tables\nin the module table (e.g., `netfilter.action`, `netfilter.family`). Terminated by `{NULL, NULL}`.\n\n---\n\n## Runtime\n\n### lunatik\\_runtime\n```C\nint lunatik_runtime(lunatik_object_t **pruntime, const char *script, bool sleep);\n```\n_lunatik\\_runtime()_ creates a new `runtime` environment then loads and runs the script\n`/lib/modules/lua/<script>.lua` as the entry point for this environment.\nIt _must_ only be called from _process context_.\nThe `runtime` environment is a Lunatik object that holds\na [Lua state](https://www.lua.org/manual/5.5/manual.html#lua_State).\nLunatik objects are special\nLua [userdata](https://www.lua.org/manual/5.5/manual.html#2.1)\nwhich also hold\na [lock type](https://docs.kernel.org/locking/locktypes.html) and\na [reference counter](https://www.kernel.org/doc/Documentation/kref.txt).\nIf `sleep` is _true_, _lunatik\\_runtime()_ will use a\n[mutex](https://docs.kernel.org/locking/mutex-design.html)\nfor locking the `runtime` environment and the\n[GFP\\_KERNEL](https://www.kernel.org/doc/html/latest/core-api/memory-allocation.html)\nflag for allocating new memory later on\n[lunatik\\_run()](#lunatik_run) calls.\nOtherwise, it will use a [spinlock](https://docs.kernel.org/locking/locktypes.html#raw-spinlock-t-and-spinlock-t) and [GFP\\_ATOMIC](https://www.kernel.org/doc/html/latest/core-api/memory-allocation.html).\n_lunatik\\_runtime()_ opens the Lua standard libraries\n[present on Lunatik](https://github.com/luainkernel/lunatik#c-api).\nIf successful, _lunatik\\_runtime()_ sets the address pointed by `pruntime` and\n[Lua's extra space](https://www.lua.org/manual/5.5/manual.html#lua_getextraspace)\nwith a pointer for the new created `runtime` environment,\nsets the _reference counter_ to `1` and then returns `0`.\nOtherwise, it returns `-ENOMEM`, if insufficient memory is available;\nor `-EINVAL`, if it fails to load or run the `script`.\n\n#### Example\n```Lua\n-- /lib/modules/lua/mydevice.lua\nfunction myread(len, off)\n\treturn \"42\"\nend\n```\n\n```C\nstatic lunatik_object_t *runtime;\n\nstatic int __init mydevice_init(void)\n{\n\treturn lunatik_runtime(&runtime, \"mydevice\", true);\n}\n```\n\n### lunatik\\_stop\n```C\nint lunatik_stop(lunatik_object_t *runtime);\n```\n_lunatik\\_stop()_\n[closes](https://www.lua.org/manual/5.5/manual.html#lua_close)\nthe\n[Lua state](https://www.lua.org/manual/5.5/manual.html#lua_State)\ncreated for this `runtime` environment and decrements the\n[reference counter](https://www.kernel.org/doc/Documentation/kref.txt).\nOnce the reference counter is decremented to zero, the\n[lock type](https://docs.kernel.org/locking/locktypes.html)\nand the memory allocated for the `runtime` environment are released.\nIf the `runtime` environment has been released, it returns `1`;\notherwise, it returns `0`.\n\n### lunatik\\_run\n```C\nvoid lunatik_run(lunatik_object_t *runtime, <inttype> (*handler)(...), <inttype> &ret, ...);\n```\n_lunatik\\_run()_ locks the `runtime` environment and calls the `handler`\npassing the associated Lua state as the first argument followed by the variadic arguments.\nIf the Lua state has been closed, `ret` is set with `-ENXIO`;\notherwise, `ret` is set with the result of `handler(L, ...)` call.\nThen, it restores the Lua stack and unlocks the `runtime` environment.\nIt is defined as a macro.\n\n#### Example\n```C\nstatic int l_read(lua_State *L, char *buf, size_t len, loff_t *off)\n{\n\tsize_t llen;\n\tconst char *lbuf;\n\n\tlua_getglobal(L, \"myread\");\n\tlua_pushinteger(L, len);\n\tlua_pushinteger(L, *off);\n\tif (lua_pcall(L, 2, 2, 0) != LUA_OK) { /* calls myread(len, off) */\n\t\tpr_err(\"%s\\n\", lua_tostring(L, -1));\n\t\treturn -ECANCELED;\n\t}\n\n\tlbuf = lua_tolstring(L, -2, &llen);\n\tllen = min(len, llen);\n\tif (copy_to_user(buf, lbuf, llen) != 0)\n\t\treturn -EFAULT;\n\n\t*off = (loff_t)luaL_optinteger(L, -1, *off + llen);\n\treturn (ssize_t)llen;\n}\n\nstatic ssize_t mydevice_read(struct file *f, char *buf, size_t len, loff_t *off)\n{\n\tssize_t ret;\n\tlunatik_object_t *runtime = (lunatik_object_t *)f->private_data;\n\n\tlunatik_run(runtime, l_read, ret, buf, len, off);\n\treturn ret;\n}\n```\n\n### lunatik\\_handle\n```C\nvoid lunatik_handle(lunatik_object_t *runtime, <inttype> (*handler)(...), <inttype> &ret, ...);\n```\nLike `lunatik_run`, but without acquiring the runtime lock. Use this when the lock is\nalready held, or when calling from within a `lunatik_run` handler. Defined as a macro.\n\n### lunatik\\_toruntime\n```C\nlunatik_object_t *lunatik_toruntime(lua_State *L);\n```\nReturns the `runtime` environment referenced by `L`'s\n[extra space](https://www.lua.org/manual/5.5/manual.html#lua_getextraspace).\nDefined as a macro.\n\n### lunatik\\_isready\n```C\nbool lunatik_isready(lua_State *L);\n```\nReturns `true` if the script associated with `L` has finished loading (i.e., the top-level\nchunk has returned). Use this to guard operations that must not run during module\ninitialization — for example, spawning a kernel thread from a `runner.spawn` callback.\n\n---\n\n## Object Lifecycle\n\n### lunatik\\_newobject\n```C\nlunatik_object_t *lunatik_newobject(lua_State *L, const lunatik_class_t *class, size_t size, lunatik_opt_t opt);\n```\n_lunatik\\_newobject()_ allocates a new Lunatik object and pushes a userdata\ncontaining a pointer to the object onto the Lua stack.\n\n`object->opt` is computed as `opt | class->opt`: all class flags are inherited by the instance.\n`opt` may add flags on top (e.g. `LUNATIK_OPT_SOFTIRQ` for a non-sleepable runtime instance).\n\n- Pass `LUNATIK_OPT_MONITOR` to wrap method calls with the object lock, enabling safe concurrent\n  access from multiple runtimes.\n- Pass `LUNATIK_OPT_SINGLE` for a private, non-shareable instance. The object cannot be cloned or\n  passed to another runtime via `_ENV` or `resume`. `SINGLE` cancels `MONITOR` inheritance: a\n  `SINGLE` instance of a `MONITOR` class does **not** get monitor wrappers, since non-shared\n  objects do not need them.\n- Pass `LUNATIK_OPT_NONE` (`0`) to inherit only the class flags.\n\nIt allocates `size` bytes for the object's private data, unless `LUNATIK_OPT_EXTERNAL` is set in\n`class->opt`, in which case `object->private` is expected to be set by the caller.\n\n### lunatik\\_createobject\n```C\nlunatik_object_t *lunatik_createobject(const lunatik_class_t *class, size_t size, lunatik_opt_t opt);\n```\n_lunatik\\_createobject()_ creates a Lunatik object independently of any Lua\nstate. This is intended for objects created in C that will be shared\nwith Lua runtimes later via `lunatik_cloneobject`.\n\nLike `lunatik_newobject`, `object->opt` is computed as `opt | class->opt`.\nSleep mode is determined by `LUNATIK_OPT_SOFTIRQ` in `object->opt`.\nReturns a pointer to the `lunatik_object_t` on success, or `NULL` if memory allocation fails.\n\n### lunatik\\_cloneobject\n```C\nvoid lunatik_cloneobject(lua_State *L, lunatik_object_t *object);\n```\n_lunatik\\_cloneobject()_ pushes `object` onto the Lua stack as a userdata with the correct\nmetatable. It calls `lunatik_require(L, class->name)` internally to ensure the class\nmetatable is registered even if the script never called `require` itself.\nThe object must not have `LUNATIK_OPT_SINGLE` set; otherwise a Lua error is raised.\n\nUse together with `lunatik_createobject` for C-owned objects that must be passed to Lua:\n```C\nobj = lunatik_createobject(&luafoo_class, sizeof(foo_t), LUNATIK_OPT_MONITOR);\nlunatik_run(runtime, my_handler, ret, obj);\n\n/* inside my_handler: */\nlunatik_cloneobject(L, obj);   /* pushes userdata, increments refcount */\nlunatik_getobject(obj);\n```\n\n### lunatik\\_getobject\n```C\nvoid lunatik_getobject(lunatik_object_t *object);\n```\nIncrements the [reference counter](https://www.kernel.org/doc/Documentation/kref.txt) of `object`.\n\n### lunatik\\_putobject\n```C\nint lunatik_putobject(lunatik_object_t *object);\n```\nDecrements the [reference counter](https://www.kernel.org/doc/Documentation/kref.txt) of `object`.\nIf the object has been released, returns `1`; otherwise returns `0`.\n\n---\n\n## Object Access\n\n### lunatik\\_checkobject\n```C\nlunatik_object_t *lunatik_checkobject(lua_State *L, int i);\n```\nReturns the Lunatik object at stack position `i`. Raises a Lua error if the value is not a\nLunatik object. Defined as a macro.\n\n### lunatik\\_toobject\n```C\nlunatik_object_t *lunatik_toobject(lua_State *L, int i);\n```\nReturns the Lunatik object at stack position `i` without type checking. Returns `NULL` if\nthe value is not a userdata. Defined as a macro.\n\n### LUNATIK\\_OBJECTCHECKER\n```C\n#define LUNATIK_OBJECTCHECKER(checker, T)\n```\nGenerates a `static inline` function `T checker(lua_State *L, int ix)` that returns\n`object->private` cast to `T`. Performs a full object check; raises a Lua error if the\nvalue at `ix` is not a valid Lunatik object.\n\n### LUNATIK\\_PRIVATECHECKER\n```C\n#define LUNATIK_PRIVATECHECKER(checker, T, ...)\n```\nLike `LUNATIK_OBJECTCHECKER`, but also guards against use-after-free by checking that\n`private != NULL` before returning. The optional `...` may include additional validation\nstatements (e.g., checking a secondary field) that are executed before `return private`.\n\n---\n\n## Registry and Attach/Detach\n\nThe registry pattern keeps pre-allocated objects alive across `lunatik_run` calls without\nexposing them to the GC:\n\n```\n// Registration (once, at hook setup):\nlunatik_attach(L, obj, field, luafoo_new)   // creates object, stores in registry, sets obj->field\n\n// Use (on each callback):\nlunatik_getregistry(L, obj->field)          // pushes userdata\nlunatik_object_t *o = lunatik_toobject(L, -1);\nluafoo_reset(o, ...);                       // update the wrapped pointer\n\n// Teardown (on unregister):\nlunatik_detach(runtime, obj, field)         // unregisters and nulls obj->field\n```\n\n### lunatik\\_registerobject\n```C\nvoid lunatik_registerobject(lua_State *L, int ix, lunatik_object_t *object);\n```\nPins `object` and its `private` pointer in `LUA_REGISTRYINDEX`, preventing garbage\ncollection. `ix` is typically the index of the opts table passed to the registration function.\n\n### lunatik\\_unregisterobject\n```C\nvoid lunatik_unregisterobject(lua_State *L, lunatik_object_t *object);\n```\nRemoves `object` and its `private` pointer from the registry, allowing GC to collect them.\n\n### lunatik\\_getregistry\n```C\nint lunatik_getregistry(lua_State *L, void *key);\n```\nPushes the value stored in `LUA_REGISTRYINDEX` at `key` onto the Lua stack and returns\nits type. Defined as a macro wrapping `lua_rawgetp`.\n\n### lunatik\\_attach\n```C\nvoid lunatik_attach(lua_State *L, obj, field, new_fn, ...);\n```\nCreates a new object by calling `new_fn(L, ...)`, stores it in `LUA_REGISTRYINDEX` keyed by\nthe returned pointer, sets `obj->field` to the result, and pops the userdata from the stack.\nDefined as a macro.\n\n### lunatik\\_detach\n```C\nvoid lunatik_detach(lunatik_object_t *runtime, obj, field);\n```\nUnregisters `obj->field` from the registry and sets `obj->field = NULL`. Safe to call when\nthe Lua state may already be closed (e.g., from `release` after `lunatik_stop`). Defined as a macro.\n\n---\n\n## Error Handling\n\n### lunatik\\_throw\n```C\nvoid lunatik_throw(lua_State *L, int ret);\n```\nPushes the POSIX error name for `-ret` (e.g., `\"ENOMEM\"`) as a string and calls `lua_error`.\nUsed to convert negative kernel error codes into Lua errors.\n\n### lunatik\\_try\n```C\nvoid lunatik_try(lua_State *L, op, ...);\n```\nCalls `op(...)`. If the return value is negative, calls `lunatik_throw`. Defined as a macro.\n\n### lunatik\\_tryret\n```C\nvoid lunatik_tryret(lua_State *L, ret, op, ...);\n```\nLike `lunatik_try`, but stores the return value in `ret` before checking. Defined as a macro.\n\n---\n\n## Table Fields\n\nThese macros read fields from a Lua table at stack index `idx` into a C struct.\n\n### lunatik\\_setinteger\n```C\nvoid lunatik_setinteger(lua_State *L, int idx, hook, field);\n```\nReads a required integer field named `field` from the table at `idx` into `hook->field`.\nRaises a Lua error if the field is missing or not a number.\n\n### lunatik\\_optinteger\n```C\nvoid lunatik_optinteger(lua_State *L, int idx, priv, field, opt);\n```\nReads an optional integer field named `field` from the table at `idx` into `priv->field`.\nFalls back to `opt` if the field is absent or nil.\n\n### lunatik\\_setstring\n```C\nvoid lunatik_setstring(lua_State *L, int idx, hook, field, maxlen);\n```\nReads a required string field named `field` from the table at `idx` into `hook->field`,\ntruncated to `maxlen` bytes. Raises a Lua error if the field is missing or not a string.\n\n---\n\n## Module Definition\n\n### LUNATIK\\_CLASSES\n```C\n#define LUNATIK_CLASSES(name, ...)\n```\nDeclares a NULL-terminated `const lunatik_class_t *` array named `lua<name>_classes`,\nto be passed as the `classes` argument of `LUNATIK_NEWLIB`.\n\n- `name`: module name suffix (same token used in `LUNATIK_NEWLIB`).\n- `...`: one or more `lunatik_class_t *` pointers.\n\n### LUNATIK\\_NEWLIB\n```C\n#define LUNATIK_NEWLIB(libname, funcs, classes, namespaces)\n```\nDefines and exports the `luaopen_<libname>` entry point using `EXPORT_SYMBOL_GPL`.\n\n- `funcs`: `luaL_Reg[]` of Lua-callable functions (the module table).\n- `classes`: NULL-terminated `const lunatik_class_t **` array declared with\n  `LUNATIK_CLASSES`, or `NULL` if the module defines no object type.\n- `namespaces`: `lunatik_namespace_t[]` of constant sub-tables, or `NULL`.\n\nWhen `classes != NULL`, `LUNATIK_NEWLIB` registers the metatable(s) for every\nclass in the array. When `namespaces != NULL`, it creates constant sub-tables\ninside the module table.\n\n#### Example — single class\n```C\nstatic const luaL_Reg luafoo_lib[] = {\n\t{\"new\", luafoo_new},\n\t{NULL, NULL},\n};\n\nLUNATIK_CLASSES(foo, &luafoo_class);\nLUNATIK_NEWLIB(foo, luafoo_lib, luafoo_classes, NULL);\n```\n\n#### Example — multiple classes\n```C\nLUNATIK_CLASSES(foo, &luafoo_class, &luafoo_bar_class);\nLUNATIK_NEWLIB(foo, luafoo_lib, luafoo_classes, NULL);\n```\n\n---\n\n## Memory\n\n### lunatik\\_malloc\n```C\nvoid *lunatik_malloc(lua_State *L, size_t size);\n```\nAllocates `size` bytes using the Lua allocator. Returns `NULL` on failure.\n\n### lunatik\\_realloc\n```C\nvoid *lunatik_realloc(lua_State *L, void *ptr, size_t size);\n```\nReallocates `ptr` to `size` bytes using the Lua allocator.\n\n### lunatik\\_free\n```C\nvoid lunatik_free(void *ptr);\n```\nFrees memory allocated by `lunatik_malloc` or `lunatik_realloc`. Equivalent to `kfree`.\n\n"
  },
  {
    "path": "driver.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal device = require(\"device\")\n\nlocal driver = {name = \"lunatik\"}\n\nfunction driver:read()\n\tlocal result = self.result\n\tself.result = nil\n\treturn result\nend\n\nlocal function result(_, ...)\n\tlocal n = select('#', ...)\n\tlocal t = {}\n\tfor i = 1, n do\n\t\tt[i] = tostring(select(i, ...))\n\tend\n\treturn table.concat(t, '\\t')\nend\n\nfunction driver:write(buf)\n\tlocal ok, err = load(buf)\n\tif ok then\n\t\terr = result(pcall(ok))\n\tend\n\tself.result = err\nend\n\ndevice.new(driver)\n\n"
  },
  {
    "path": "examples/cpuexporter.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025-2026 Enderson Maia <endersonmaia@gmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal lunatik = require(\"lunatik\")\nlocal thread  = require(\"thread\")\nlocal unix    = require(\"socket.unix\")\nlocal linux   = require(\"linux\")\nlocal cpu     = require(\"cpu\")\nlocal socket  = require(\"socket\")\n\nlocal shouldstop = thread.shouldstop\nlocal NONBLOCK   = socket.sock.NONBLOCK\n\nlocal server = unix.stream(\"/tmp/cpuexporter.sock\")\nserver:bind()\nserver:listen()\n\nlocal last_stats = {}\nlocal last_total_stats = {}\n\n-- Scale integer to decimal with high precision\n-- Converts a ratio (metric/total) to percentage format with 16 decimal places\nlocal function format_percentage(metric, total)\n\tif total == 0 then\n\t\treturn \"0.0000000000000000\"\n\tend\n\n\tlocal int_part = (metric * 100) // total\n\tlocal remainder = (metric * 100) % total\n\tlocal frac_high = (remainder * 100000000) // total\n\tlocal remainder2 = (remainder * 100000000) % total\n\tlocal frac_low = (remainder2 * 100000000) // total\n\n\treturn string.format(\"%d.%08d%08d\", int_part, frac_high, frac_low)\nend\n\n-- Helper function to sum all stats values in a table\n-- Note: guest and guest_nice are already included in user and nice respectively\n-- according to the Linux kernel documentation, so we must exclude them from the total\nlocal function sum_stats(stats)\n\tlocal sum = 0\n\tstats[\"guest\"] = 0\n\tstats[\"guest_nice\"] = 0\n\tfor key, value in pairs(stats) do\n\t\tsum = sum + value\n\tend\n\treturn sum\nend\n\n-- returns per cpu stats, total_stats tables\nlocal function cpu_stats()\n\t--TODO: add cpu-total with accumulated values for all cpus\n\tlocal stats = {}\n\tlocal total_stats = {}\n\tcpu.foreach_online(function(id)\n\t\tstats[id] = {}\n\t\tstats[id] = cpu.stats(id)\n\t\ttotal_stats[id] = sum_stats(stats[id])\n\tend)\n\treturn stats, total_stats\nend\n\n-- returns cpu_usage (%) table\nlocal function cpu_usage()\n\tlocal usage = {}\n\tlocal current_stats, current_total_stats = cpu_stats()\n\n\tfor cpu_id, _ in pairs(current_stats) do\n\t\tlocal total_delta = current_total_stats[cpu_id] - (last_total_stats[cpu_id] or 0)\n\t\tlocal guest_delta = current_stats[cpu_id].guest - (last_stats[cpu_id].guest or 0)\n\t\tlocal guest_nice_delta = current_stats[cpu_id].guest_nice - (last_stats[cpu_id].guest_nice or 0)\n\n\t\tusage[cpu_id] = {}\n\t\tfor metric, value in pairs(current_stats[cpu_id]) do\n\t\t\tlocal metric_delta = value - (last_stats[cpu_id][metric] or 0)\n\n\t\t\tif metric == \"user\" then\n\t\t\t\tmetric_delta = metric_delta - guest_delta\n\t\t\telseif metric == \"nice\" then\n\t\t\t\tmetric_delta = metric_delta - guest_nice_delta\n\t\t\tend\n\t\t\tusage[cpu_id][metric] = format_percentage(metric_delta, total_delta)\n\t\tend\n\tend\n\n\tlast_stats = current_stats\n\tlast_total_stats = current_total_stats\n\treturn usage\nend\n\nlocal function cpu_metrics()\n\tlocal metrics = \"\"\n\tlocal ts_ms = linux.time() // 1000  -- convert nanoseconds to milliseconds\n\tlocal usage_data = cpu_usage()  -- Call once and store the result\n\n\t-- Collect all unique metric names from the first available CPU\n\tlocal cpu_metric_names = {}\n\tfor _, cpu_metrics in pairs(usage_data) do\n\t\tfor key, _ in pairs(cpu_metrics) do\n\t\t\tcpu_metric_names[key] = true\n\t\tend\n\t\tbreak  -- Only need one CPU to get all metric names\n\tend\n\n\t-- Output grouped by metric name\n\tfor metric, _ in pairs(cpu_metric_names) do\n\t\tmetrics = metrics .. string.format('# TYPE cpu_usage_%s gauge\\n', metric)\n\t\tfor cpu_id, cpu_metrics in pairs(usage_data) do\n\t\t\tlocal value = cpu_metrics[metric] or \"0\"\n\t\t\tmetrics = metrics .. string.format('cpu_usage_%s{cpu=\"cpu%d\"} %s %d\\n',\n\t\t\t\tmetric, cpu_id, value, ts_ms)\n\t\tend\n\tend\n\n\treturn metrics\nend\n\nlocal function handle_client(session)\n\t-- Read the request\n\tlocal request, err = session:receive(1024)\n\tif not request then\n\t\terror(err)\n\tend\n\n\t-- Check if this is an HTTP request\n\tlocal method, path, http_version = string.match(request, \"^(%w+)%s+([^%s]+)%s+(HTTP/%d%.%d)\")\n\n\tif http_version then\n\t\t-- This is an HTTP request, validate it\n\t\tif method ~= \"GET\" then\n\t\t\tsession:send(\"HTTP/1.1 405 Method Not Allowed\\r\\n\\r\\n\")\n\t\t\terror(\"Method not allowed: \" .. tostring(method))\n\t\tend\n\n\t\tif path ~= \"/metrics\" then\n\t\t\tsession:send(\"HTTP/1.1 404 Not Found\\r\\n\\r\\n\")\n\t\t\terror(\"Path not found: \" .. tostring(path))\n\t\tend\n\n\t\t-- Send HTTP response headers\n\t\tsession:send(\"HTTP/1.1 200 OK\\r\\n\")\n\t\tsession:send(\"Content-Type: text/plain; version=0.0.4\\r\\n\")\n\t\tsession:send(\"\\r\\n\")\n\tend\n\n\t-- Send metrics (works for both HTTP and plain connections like socat)\n\tsession:send(cpu_metrics())\nend\n\n-- Initial sample\nlast_stats = cpu_stats()\n\nlocal function daemon()\n\tprint(\"cpud [daemon]: started\")\n\twhile (not shouldstop()) do\n\t\tlocal ok, session = pcall(server.accept, server, NONBLOCK)\n\t\tif ok then\n\t\t\tlocal ok, err = pcall(handle_client, session)\n\t\t\tif not ok then\n\t\t\t\tprint(\"cpud [daemon]: error handling client: \" .. tostring(err))\n\t\t\tend\n\t\t\tsession:close()\n\t\telseif session == \"EAGAIN\" then\n\t\t\tlinux.schedule(100)\n\t\tend\n\tend\n\tprint(\"cpud [daemon]: stopped\")\nend\n\nreturn daemon\n\n"
  },
  {
    "path": "examples/dnsblock/common.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n-- Common code for new netfilter framework and legacy iptables dnsblock example\n\nlocal linux = require(\"linux\")\nlocal byteorder = require(\"byteorder\")\nlocal string = require(\"string\")\n\nlocal common = {}\n\nlocal udp = 0x11\nlocal dns = 0x35\n\nlocal blacklist = {\n\t\"github.com\",\n\t\"gitlab.com\",\n}\n\nlocal function get_domain(skb, off)\n\tlocal _, _, name = skb:getstring(off):find(\"([^\\0]*)\")\n\treturn name\nend\n\nlocal function check_blacklist(name)\n\tfor _, v in ipairs(blacklist) do\n\t\tif string.find(name, v) ~= nil then\n\t\t\treturn true\n\t\tend\n\tend\n\treturn false\nend\n\nfunction common.hook(skb, thoff, proto)\n\tif proto == udp then\n\t\tlocal dstport = byteorder.ntoh16(skb:getuint16(thoff + 2))\n\t\tif dstport == dns then\n\t\t\tlocal qoff = thoff + 20\n\t\t\tlocal name = get_domain(skb, qoff)\n\t\t\tif check_blacklist(name) then\n\t\t\t\tprint(\"DNS query for \" .. name .. \" blocked\\n\")\n\t\t\t\treturn true\n\t\t\tend\n\t\tend\n\tend\n\n\treturn false\nend\n\nreturn common\n\n"
  },
  {
    "path": "examples/dnsblock/nf_dnsblock.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2024-2026 Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n-- Filter DNS packets based on a blocklist using new netfilter hooks\n\nlocal nf = require(\"netfilter\")\nlocal common = require(\"examples.dnsblock.common\")\nlocal family = nf.family\nlocal action = nf.action\nlocal hooks = nf.inet_hooks\nlocal priority = nf.ip_priority\n\nlocal function dnsblock_hook(skb)\n\tlocal pkt = skb:data(\"net\")\n\tlocal proto = pkt:getuint8(9)\n\tlocal ihl = (pkt:getuint8(0) & 0x0F)\n\tlocal thoff = ihl * 4\n\n\treturn common.hook(pkt, thoff, proto) and action.DROP or action.ACCEPT\nend\n\nnf.register{\n\thook = dnsblock_hook,\n\tpf = family.INET,\n\thooknum = hooks.LOCAL_OUT,\n\tpriority = priority.FILTER,\n}\n\n"
  },
  {
    "path": "examples/dnsdoctor/cleanup.sh",
    "content": "# SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com>\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n\n#!/bin/bash\n\nset -eux\n\nrm dnstest -rf\n\n# backup resolv config\nif [[ -f /etc/resolv.conf.lunatik ]]; then\n\techo \"Restoring dns config from resolv.conf.lunatik\"\n\tsudo rm /etc/resolv.conf\n\tsudo cp /etc/resolv.conf.lunatik /etc/resolv.conf\n\tsudo rm /etc/resolv.conf.lunatik\nfi\n\n# down the interfaces\nsudo ip -n ns1 link set veth1 down\nsudo ip -n ns2 link set veth3 down\nsudo ip link set veth2 down\nsudo ip link set veth4 down\n\nsudo ip addr delete 10.1.1.2/24 dev veth2\nsudo ip -n ns1 addr delete 10.1.1.3/24 dev veth1\nsudo ip addr delete 10.1.2.2/24 dev veth4\nsudo ip -n ns2 addr delete 10.1.2.3/24 dev veth3\n\n# delete link between host and the namespaces\nsudo ip -n ns1 link delete veth1\nsudo ip -n ns2 link delete veth3\n\n# delete namespaces ns1 for dns server ns2 for server\nsudo ip netns delete ns1\nsudo ip netns delete ns2\n\n"
  },
  {
    "path": "examples/dnsdoctor/common.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n-- Common code for new netfilter framework and legacy iptables dns doctoring example\n\nlocal nf = require(\"netfilter\")\nlocal linux = require(\"linux\")\nlocal byteorder = require(\"byteorder\")\nlocal action = nf.action\nlocal dns = 0x35\n\nlocal common = {}\n\nlocal function get_domain(skb, off)\n\tlocal _, nameoff, name = skb:getstring(off):find(\"([^\\0]*)\")\n\treturn name, nameoff + 1\nend\n\nfunction common.hook(skb, thoff, target_dns, target_ip, dst_ip, packet_dst)\n\tif packet_dst ~= byteorder.hton32(dst_ip) then\n\t\treturn action.ACCEPT\n\tend\n\n\tlocal srcport = byteorder.ntoh16(skb:getuint16(thoff))\n\tif srcport == dns then\n\t\tlocal dnsoff = thoff + 8\n\t\tlocal nanswers = byteorder.ntoh16(skb:getuint16(dnsoff + 6))\n\n\t\t-- check the domain name\n\t\tdnsoff = dnsoff + 12\n\t\tlocal domainname, nameoff = get_domain(skb, dnsoff)\n\t\tif domainname == target_dns then\n\t\t\tdnsoff = dnsoff + nameoff + 4 -- skip over type, label fields\n\t\t\t-- iterate over answers\n\t\t\tfor i = 1, nanswers do\n\t\t\t\tlocal atype = byteorder.hton16(skb:getuint16(dnsoff + 2))\n\t\t\t\tif atype == 1 then\n\t\t\t\t\tskb:setuint32(dnsoff + 12, byteorder.hton32(target_ip))\n\t\t\t\tend\n\t\t\t\tdnsoff = dnsoff + 16\n\t\t\tend\n\t\tend\n\tend\n\n\treturn action.ACCEPT\nend\n\nreturn common\n"
  },
  {
    "path": "examples/dnsdoctor/nf_dnsdoctor.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2024-2026 Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n-- DNS Doctoring using new netfilter API\n\nlocal nf = require(\"netfilter\")\nlocal string = require(\"string\")\nlocal common = require(\"examples.dnsdoctor.common\")\nlocal action = nf.action\nlocal family = nf.family\nlocal hooks = nf.inet_hooks\nlocal pri = nf.ip_priority\n\nlocal udp = 0x11\n\nlocal function dnsdoctor_hook(skb)\n\tlocal pkt = skb:data(\"mac\")\n\tlocal ihl = pkt:getuint8(0) & 0x0F\n\tlocal thoff = ihl * 4\n\tlocal proto = pkt:getuint8(9)\n\tlocal packet_dst = pkt:getuint32(16)\n\n\tif proto ~= udp then\n\t\treturn action.ACCEPT\n\tend\n\n\tlocal target_dns = string.pack(\"s1s1\", \"lunatik\", \"com\")\n\tlocal dns_ip = \"10.1.2.3\"\n\tlocal target_ip = 0\n\tdns_ip:gsub(\"%d+\", function(s) target_ip = target_ip * 256 + tonumber(s) end)\n\n\tlocal dst = \"10.1.1.2\"\n\tlocal dst_ip = 0\n\tdst:gsub(\"%d+\", function(s) dst_ip = dst_ip * 256 + tonumber(s) end)\n\n\treturn common.hook(pkt, thoff, target_dns, target_ip, dst_ip, packet_dst)\nend\n\nnf.register{\n\thook = dnsdoctor_hook,\n\tpf = family.INET,\n\thooknum = hooks.PRE_ROUTING,\n\tpriority = pri.MANGLE + 1,\n}\n\n"
  },
  {
    "path": "examples/dnsdoctor/setup.sh",
    "content": "# SPDX-FileCopyrightText: (c) 2024 Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com>\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n\n#!/bin/bash\n\nset -eux\n\n# add namespaces ns1 for dns server ns2 for server\nsudo ip netns add ns1\nsudo ip netns add ns2\n\n# add link between host and the namespaces\nsudo ip link add veth1 netns ns1 type veth peer name veth2\nsudo ip link add veth3 netns ns2 type veth peer name veth4\n\n# add ip address to the links\n# DNS IP : 10.1.1.3\n# Server IP : 10.1.2.3\nsudo ip addr add 10.1.1.2/24 dev veth2\nsudo ip -n ns1 addr add 10.1.1.3/24 dev veth1\nsudo ip addr add 10.1.2.2/24 dev veth4\nsudo ip -n ns2 addr add 10.1.2.3/24 dev veth3\n\n# up the interfaces\nsudo ip -n ns1 link set veth1 up\nsudo ip -n ns2 link set veth3 up\nsudo ip link set veth2 up\nsudo ip link set veth4 up\n\n# make a directory to setup dns server\nmkdir dnstest\ncd dnstest\npython -m venv .venv\nsource .venv/bin/activate\npip install dnserver\n\n# backup resolv config\necho \"Backing up resolver config to /etc/resolver.conf.lunatik\"\nsudo cp -f /etc/resolv.conf /etc/resolv.conf.lunatik && \\\nsudo sed -i 's/nameserver/#nameserver/g' /etc/resolv.conf && \\\necho \"nameserver 10.1.1.3\" | sudo tee -a /etc/resolv.conf && \\\n\n# add zone info and run dns server in ns1\necho \"\"\"\n[[zones]]\nhost = 'lunatik.com'\ntype = 'A'\nanswer = '192.168.10.1'\n\n[[zones]]\nhost = 'lunatik.com'\ntype = 'NS'\nanswer = 'ns1.lunatik.com.'\n\n[[zones]]\nhost = 'lunatik.com'\ntype = 'NS'\nanswer = 'ns2.lunatik.com.'\n\"\"\" > zones.toml\nsudo ip netns exec ns1 .venv/bin/dnserver --no-upstream zones.toml\n\n"
  },
  {
    "path": "examples/echod/daemon.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal lunatik = require(\"lunatik\")\nlocal thread  = require(\"thread\")\nlocal socket  = require(\"socket\")\nlocal inet    = require(\"socket.inet\")\nlocal linux   = require(\"linux\")\nlocal data    = require(\"data\")\n\nlocal shouldstop = thread.shouldstop\nlocal task = linux.task\nlocal sock = socket.sock\n\nlocal control = data.new(2)\ncontrol:setbyte(1, 1) -- alive\n\nlocal server = inet.tcp()\nserver:bind(inet.localhost, 1337)\nserver:listen()\n\nlocal n = 1\nlocal worker = \"echod/worker\"\n\nlocal function daemon()\n\tprint(\"echod [daemon]: started\")\n\twhile (not shouldstop()) do\n\t\tlocal ok, session = pcall(server.accept, server, sock.NONBLOCK)\n\t\tif ok then\n\t\t\tcontrol:setbyte(0, n) -- #workers\n\t\t\tlocal runtime = lunatik.runtime(\"examples/\" .. worker)\n\t\t\truntime:resume(control, session)\n\t\t\tthread.run(runtime, worker .. n)\n\t\t\tn = n + 1\n\t\telseif session == \"EAGAIN\" then\n\t\t\tlinux.schedule(100)\n\t\tend\n\tend\n\tcontrol:setbyte(1, 0) -- dead\n\tprint(\"echod [daemon]: stopped\")\nend\n\nreturn daemon\n\n"
  },
  {
    "path": "examples/echod/worker.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal thread = require(\"thread\")\n\nlocal shouldstop = thread.shouldstop\n\nlocal function info(id, message)\n\tlocal prefix = \"echod [worker #\" .. id .. \"]\"\n\tprint(string.format(\"%s: %s\", prefix, message))\nend\n\nlocal function alive(control)\n\treturn control:getbyte(1) ~= 0\nend\n\nlocal function echo(session)\n\tlocal message = session:receive(1024)\n\tsession:send(message)\n\treturn message == \"\"\nend\n\nlocal function worker(control, session)\n\treturn function ()\n\t\tlocal id = control:getbyte(0)\n\n\t\tinfo(id, \"started\")\n\t\trepeat\n\t\t\tlocal ok, err = pcall(echo, session)\n\t\t\t\tif not ok then\n\t\t\t\treturn info(id, \"aborted\")\n\t\t\tend\n\t\tuntil (not alive(control) or err or shouldstop())\n\t\tinfo(id, \"stopped\")\n\tend\nend\n\nreturn worker\n\n"
  },
  {
    "path": "examples/filter/Makefile",
    "content": "# SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n\nall: vmlinux https.o\n\nvmlinux:\n\tbpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h\n\nhttps.o: https.c\n\tclang -target bpf -Wall -O2 -c -g $<\n\nclean:\n\trm -f vmlinux.h https.o\n\n"
  },
  {
    "path": "examples/filter/https.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#include \"vmlinux.h\"\n#include <bpf/bpf_helpers.h>\n#include <bpf/bpf_endian.h>\n\nextern int bpf_luaxdp_run(char *key, size_t key__sz, struct xdp_md *xdp_ctx, void *arg, size_t arg__sz) __ksym;\n\nstatic char runtime[] = \"examples/filter/sni\";\n\nstruct bpf_luaxdp_arg {\n\t__u16 offset;\n} __attribute__((packed));\n\nSEC(\"xdp\")\nint filter_https(struct xdp_md *ctx)\n{\n\tstruct bpf_luaxdp_arg arg;\n\tvoid *data_end = (void *)(long)ctx->data_end;\n\tvoid *data = (void *)(long)ctx->data;\n\tstruct iphdr *ip = data + sizeof(struct ethhdr);\n\n\tif (ip + 1 > (struct iphdr *)data_end)\n\t\tgoto pass;\n\n\tif (ip->protocol != IPPROTO_TCP)\n\t\tgoto pass;\n\n\tstruct tcphdr *tcp = (void *)ip + (ip->ihl * 4);\n\tif (tcp + 1 > (struct tcphdr *)data_end)\n\t\tgoto pass;\n\n\tif (bpf_ntohs(tcp->dest) != 443 || !tcp->psh)\n\t\tgoto pass;\n\n\tvoid *payload = (void *)tcp + (tcp->doff * 4);\n\tif (payload > data_end)\n\t\tgoto pass;\n\n\targ.offset = bpf_htons((__u16)(payload - data));\n\n\tint action = bpf_luaxdp_run(runtime, sizeof(runtime), ctx, &arg, sizeof(arg));\n\treturn action < 0 ? XDP_PASS : action;\npass:\n\treturn XDP_PASS;\n}\n\nchar _license[] SEC(\"license\") = \"Dual MIT/GPL\";\n\n"
  },
  {
    "path": "examples/filter/sni.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2024 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal xdp  = require(\"xdp\")\n\nlocal action = xdp.action\n\nlocal function set(t)\n\tlocal s = {}\n\tfor _, key in ipairs(t) do s[key] = true end\n\treturn s\nend\n\nlocal blacklist = set{\n\t\"ebpf.io\",\n}\n\nlocal function log(sni, verdict)\n\tprint(string.format(\"filter_sni: %s %s\", sni, verdict))\nend\n\nlocal function unpacker(packet, base)\n\tlocal byte = function (offset)\n\t\treturn packet:getbyte(base + offset)\n\tend\n\n\tlocal short = function (offset)\n\t\tlocal offset = base + offset\n\t\treturn packet:getbyte(offset) << 8 | packet:getbyte(offset + 1)\n\tend\n\n\tlocal str = function (offset, length)\n\t\treturn packet:getstring(base + offset, length)\n\tend\n\n\treturn byte, short, str\nend\n\nlocal function offset(argument)\n\treturn select(2, unpacker(argument, 0))(0)\nend\n\nlocal client_hello = 0x01\nlocal handshake    = 0x16\nlocal server_name  = 0x00\n\nlocal session = 43\nlocal max_extensions = 17\n\nlocal function filter_sni(packet, argument)\n\tlocal byte, short, str = unpacker(packet, offset(argument))\n\n\tif byte(0) ~= handshake or byte(5) ~= client_hello then\n\t\treturn action.PASS\n\tend\n\n\tlocal cipher = (session + 1) + byte(session)\n\tlocal compression = cipher + 2 + short(cipher)\n\tlocal extension = compression + 3 + byte(compression)\n\n\tfor i = 1, max_extensions do\n\t\tlocal data = extension + 4\n\t\tif short(extension) == server_name then\n\t\t\tlocal length = short(data + 3)\n\t\t\tlocal sni = str(data + 5, length)\n\n\t\t\tverdict = blacklist[sni] and \"DROP\" or \"PASS\"\n\t\t\tlog(sni, verdict)\n\t\t\treturn action[verdict]\n\t\tend\n\t\textension = data + short(extension + 2)\n\tend\n\n\treturn action.PASS\nend\n\nxdp.attach(filter_sni)\n\n"
  },
  {
    "path": "examples/gesture.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025-2026 Jieming Zhou <qrsikno@gmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n-- Driver for QEMU's USB mouse with gesture (dragging),\n-- swiping right to lock the mouse and swiping left to unlock it.\n\nlocal hid = require(\"hid\")\n\nlocal driver = {name = \"gesture\", id_table = {{vendor = 0x0627, product = 0x0001}}}\n\nlocal threshold = 2\n\nlocal function debug(fmt, ...)\n\tprint(string.format(fmt, ...))\nend\n\nfunction driver:probe(id)\n\tself.state = {x = 0, count = 0, lock = false}\nend\n\nlocal function forward(x0, x1)\n\treturn (x0 >= 32767 and x1 <= 0) or (x1 > x0)\nend\n\nlocal function count(state, direction)\n\tif direction ~= \"neutral\" then\n\t\tif state.direction == direction then\n\t\t\tstate.count = state.count + 1\n\t\telse\n\t\t\tstate.count = 0\n\t\t\tstate.direction = direction\n\t\tend\n\tend\n\treturn state.count\nend\n\nfunction driver:raw_event(hdev, report, raw)\n\tlocal state = self.state\n\tlocal button = raw:getbyte(0)\n\n\tlocal left_down = (button & 1) == 1\n\tif left_down then\n\t\tlocal x0 = state.x\n\t\tlocal x1 = raw:getint16(1)\n\t\tlocal direction = x0 == x1 and \"neutral\" or\n\t\t\tforward(x0, x1) and \"right\" or \"left\"\n\n\t\tdebug(\"%s\\t%d %d\", direction, x0, x1)\n\t\tif count(state, direction) > threshold then\n\t\t\tif direction == \"right\" then\n\t\t\t\tstate.lock = true\n\t\t\telseif direction == \"left\" then\n\t\t\t\tstate.lock = false\n\t\t\tend\n\t\tend\n\t\tstate.x = x1\n\tend\n\n\tif state.lock then\n\t\tlocal last = #raw - 1\n\t\tfor i = 0, last do \n\t\t\traw:setbyte(i, 0)\n\t\tend\n\tend\nend\n\nhid.register(driver)\n\n"
  },
  {
    "path": "examples/keylocker.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal notifier = require(\"notifier\")\n\n-- <UP> <UP> <DOWN> <DOWN> <LEFT> <RIGHT> <LEFT> <RIGHT> <LCTRL> <LALT>\nlocal konami = {code = {103, 103, 108, 108, 105, 106, 105, 106, 29, 56}, ix = 1}\nfunction konami:completion(key)\n\tself.ix = key == self.code[self.ix] and (self.ix + 1) or 1\n\treturn self.ix == (#self.code + 1)\nend\n\nlocal notify = notifier.notify\nlocal kbd    = notifier.kbd\nlocal enable = true\nlocal function locker(event, down, shift, key)\n\tif not down and event == kbd.KEYCODE and konami:completion(key) then\n\t\tenable = not enable\n\tend\n\treturn enable and notify.OK or notify.STOP\nend\n\nnotifier.keyboard(locker)\n\n"
  },
  {
    "path": "examples/lldpd.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025-2026 Ashwani Kumar Kamal <ashwanikamal.im421@gmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n-- Daemon to send LLDP frames on given interface\nlocal raw    = require(\"socket.raw\")\nlocal linux  = require(\"linux\")\nlocal thread = require(\"thread\")\nlocal eth    = require(\"linux.eth\")\n\nlocal shouldstop = thread.shouldstop\n\n-- LLDP multicast destination\nlocal ETH_DST_MAC = string.char(0x01,0x80,0xc2,0x00,0x00,0x0e)\n-- System Capabilities: station only\nlocal LLDP_CAP_STATION_ONLY = 0x0080\n\nlocal config = {\n\tinterface = \"veth0\",\n\tport_description = \"ethernet interface\",\n\t-- default transmission interval used in lldpd implementation (30s)\n\t-- https://lldpd.github.io/usage.html\n\ttx_interval_ms = 30000,\n\tsystem = {\n\t\tname = \"lunatik-lldpd\",\n\t\tdescription = \"LLDP daemon implemented in Lunatik\",\n\t\tttl = 120,\n\t\tcapabilities = LLDP_CAP_STATION_ONLY,\n\t\tcapabilities_enabled = LLDP_CAP_STATION_ONLY,\n\t},\n}\n\nlocal ethertype = string.pack(\">I2\", eth.LLDP)\n\nlocal function tlv(t, payload, subtype)\n\tif subtype then\n\t\tpayload = string.char(subtype) .. payload\n\tend\n\treturn string.pack(\">I2\", (t << 9) | #payload) .. payload\nend\n\nlocal function build_lldp_frame(chassis_id)\n\tlocal port_id = config.interface\n\tlocal ttl = string.pack(\">I2\", config.system.ttl)\n\tlocal capabilities = string.pack(\">I2I2\", config.system.capabilities, config.system.capabilities_enabled)\n\n\tlocal pdu = {\n\t\t-- Ethernet header\n\t\tETH_DST_MAC,\n\t\tchassis_id,\n\t\tethertype,\n\t\t-- LLDP TLVs\n\t\ttlv(1, chassis_id, 4),\n\t\ttlv(2, port_id, 5),\n\t\ttlv(3, ttl),\n\t\ttlv(4, config.port_description),\n\t\ttlv(5, config.system.name),\n\t\ttlv(6, config.system.description),\n\t\ttlv(7, capabilities),\n\t\t-- End of LLDPDU\n\t\ttlv(0, \"\"),\n\t}\n\n\treturn table.concat(pdu)\nend\n\nlocal ifindex = linux.ifindex(config.interface)\nlocal src_mac = linux.ifaddr(ifindex)\nlocal lldp_frame = build_lldp_frame(src_mac)\n\nlocal function worker()\n\tlocal tx <close> = raw.bind(eth.LLDP, ifindex)\n\n\twhile (not shouldstop()) do\n\t\ttx:send(lldp_frame)\n\t\tprint(string.format(\"[lldpd] frame sent on ifindex=%d (%d bytes)\", ifindex, #lldp_frame))\n\t\tlinux.schedule(config.tx_interval_ms)\n\tend\n\n\tprint(\"[lldpd] worker stopped\")\nend\n\nreturn worker\n\n"
  },
  {
    "path": "examples/shared.lua",
    "content": "--\n-- Copyright (c) 2023-2024 ring-0 Ltda.\n--\n-- Permission is hereby granted, free of charge, to any person obtaining\n-- a copy of this software and associated documentation files (the\n-- \"Software\"), to deal in the Software without restriction, including\n-- without limitation the rights to use, copy, modify, merge, publish,\n-- distribute, sublicense, and/or sell copies of the Software, and to\n-- permit persons to whom the Software is furnished to do so, subject to\n-- the following conditions:\n--\n-- The above copyright notice and this permission notice shall be\n-- included in all copies or substantial portions of the Software.\n--\n-- THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n--\n\nlocal thread = require(\"thread\")\nlocal socket = require(\"socket\")\nlocal inet   = require(\"socket.inet\")\nlocal rcu    = require(\"rcu\")\nlocal data   = require(\"data\")\nlocal linux  = require(\"linux\")\n\nlocal shared = rcu.table()\n\nlocal server = inet.tcp()\nserver:bind(inet.localhost, 90)\nserver:listen()\n\nlocal shouldstop = thread.shouldstop\nlocal task = linux.task\nlocal sock = socket.sock\n\nlocal size = 1024\n\nlocal function handle(session)\n\trepeat\n\t\tlocal request = session:receive(size)\n\t\tlocal key, assign, value = string.match(request, \"(%w+)(=*)(%w*)\\n\")\n\t\tif key then\n\t\t\tif assign ~= \"\" then\n\t\t\t\tlocal slot\n\t\t\t\tif value ~= \"\" then\n\t\t\t\t\tslot = shared[key] or data.new(size)\n\t\t\t\t\tslot:setstring(0, value)\n\t\t\t\tend\n\n\t\t\t\tshared[key] = slot\n\t\t\telse\n\t\t\t\tlocal value = shared[key]:getstring(0, size)\n\t\t\t\tsession:send(value .. \"\\n\")\n\t\t\tend\n\t\tend\n\tuntil (not key or shouldstop())\nend\n\nlocal function daemon()\n\tprint(\"starting shared...\")\n\twhile (not shouldstop()) do\n\t\tlocal ok, session = pcall(server.accept, server, sock.NONBLOCK)\n\t\tif ok then\n\t\t\thandle(session)\n\t\telseif session == \"EAGAIN\" then\n\t\t\tlinux.schedule(100)\n\t\tend\n\tend\n\tprint(\"stopping shared...\")\nend\n\nreturn daemon\n\n"
  },
  {
    "path": "examples/spyglass.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal notifier = require(\"notifier\")\nlocal device = require(\"device\")\nlocal inet = require(\"socket.inet\")\n\nlocal function info(...)\n\tprint(\"spyglass: \" .. string.format(...))\nend\n\nlocal control = {\n\t [0] = \"nul\",  [1] = \"soh\",  [2] = \"stx\",  [3] = \"etx\",  [4] = \"eot\",  [5] = \"enq\",\n\t [6] = \"ack\",  [7] = \"bel\",  [8] = \"bs\",   [9] = \"ht\",  [10] = \"nl\",  [11] = \"vt\",\n\t[12] = \"np\",  [13] = \"cr\",  [14] = \"so\",  [15] = \"si\",  [16] = \"dle\", [17] = \"dc1\",\n\t[18] = \"dc2\", [19] = \"dc3\", [20] = \"dc4\", [21] = \"nak\", [22] = \"syn\", [23] = \"etb\",\n\t[24] = \"can\", [25] = \"em\",  [26] = \"sub\", [27] = \"esc\", [28] = \"fs\",  [29] = \"gs\",\n\t[30] = \"rs\",  [31] = \"us\", [127] = \"del\"\n}\n\nlocal function printable(keysym)\n\treturn keysym >= 32 and keysym <= 126\nend\n\nlocal spyglass = {name = \"spyglass\", log = \"\"}\n\nfunction spyglass:read()\n\tlocal log = self.log\n\tself.log = \"\"\n\tlocal socket = self.socket\n\tif socket and #log > 0 then\n\t\tpcall(socket.send, socket, log, ip, port)\n\t\treturn \"\"\n\tend\n\treturn log\nend\n\nlocal settings = {\n\t['enable'] = function (self, enable)\n\t\tlocal enable = enable ~= \"false\"\n\t\tif enable and not self.notifier then\n\t\t\tself.notifier = notifier.keyboard(self.callback)\n\t\telseif not enable and self.notifier then\n\t\t\tself.notifier:delete()\n\t\t\tself.notifier = nil\n\t\tend\n\tend,\n\t['net'] = function (self, net)\n\t\tlocal ip, port = string.match(net, \"(%g+):(%d+)\")\n\t\tif ip then\n\t\t\tinfo(\"enabling network support %s:%d\", ip, port)\n\t\t\tself.socket = inet.udp()\n\t\t\tself.socket:connect(ip, port)\n\t\telseif self.socket then\n\t\t\tinfo(\"disabling network support\")\n\t\t\tself.socket:close()\n\t\t\tself.socket = nil\n\t\tend\n\tend\n}\n\nfunction spyglass:write(buf)\n\tfor k, v in string.gmatch(buf, \"(%w+)=(%g+)\") do\n\t\tlocal setter = settings[k]\n\t\tif setter then\n\t\t\tsetter(self, v)\n\t\tend\n\tend\nend\n\nlocal notify = notifier.notify\nlocal kbd    = notifier.kbd\nfunction spyglass.callback(event, down, shift, key)\n\tif not down and event == kbd.KEYSYM then\n\t\tlocal keysym = key & 0xFF\n\t\tlocal log = printable(keysym) and string.char(keysym) or\n\t\t\tstring.format(\"<%s>\", control[keysym])\n\t\tspyglass.log = spyglass.log .. log\n\tend\n\treturn notify.OK\nend\n\nspyglass.notifier = notifier.keyboard(spyglass.callback)\nspyglass.device = device.new(spyglass)\n\n"
  },
  {
    "path": "examples/systrack.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal linux  = require(\"linux\")\nlocal probe  = require(\"probe\")\nlocal device = require(\"device\")\nlocal systab = require(\"syscall.table\")\n\nlocal syscalls = {\"openat\", \"read\", \"write\", \"readv\", \"writev\", \"close\"}\n\nlocal s = linux.stat\nlocal driver = {name = \"systrack\", mode = s.IRUGO}\n\nlocal track = {}\nlocal toggle = true\nfunction driver:read()\n\tlocal log = \"\"\n\tif toggle then\n\t\tfor symbol, counter in pairs(track) do\n\t\t\tlog = log .. string.format(\"%s: %d\\n\", symbol, counter)\n\t\tend\n\tend\n\ttoggle = not toggle\n\treturn log\nend\n\nfor _, symbol in ipairs(syscalls) do\n\tlocal address = systab[symbol]\n\ttrack[symbol] = 0\n\n\tlocal function handler()\n\t\ttrack[symbol] = track[symbol] + 1\n\tend\n\n\tprobe.new(address, {pre = handler})\nend\n\ndevice.new(driver)\n\n"
  },
  {
    "path": "examples/tap.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal device = require(\"device\")\nlocal raw    = require(\"socket.raw\")\nlocal linux  = require(\"linux\")\n\nlocal MTU       = 1500\n\nlocal s = linux.stat\nlocal tap = {name = \"tap\", mode = s.IRUGO}\n\nlocal socket = raw.bind()\n\nfunction tap:read()\n\tlocal frame = socket:receive(MTU)\n\tlocal dst, src, ethtype = string.unpack(\">I6I6I2\", frame)\n\treturn string.format(\"%X\\t%X\\t%X\\t%d\\n\", dst, src, ethtype, #frame)\nend\n\ndevice.new(tap)\n\n"
  },
  {
    "path": "examples/tcpreject/cleanup.sh",
    "content": "#!/bin/bash\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n\nset -eux\n\nlunatik stop examples/tcpreject/nf_tcpreject\nnft delete table ip tcpreject\nnft delete table ip6 tcpreject\nip link del veth-tcpreject\nip netns del tcpreject\necho 0 > /proc/sys/net/ipv4/ip_forward\necho 0 > /proc/sys/net/ipv6/conf/all/forwarding\n\n"
  },
  {
    "path": "examples/tcpreject/nf_tcpreject.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n-- Inject a TCP RST toward the origin for forwarded packets matching a mark.\n-- Use nft to mark packets before this hook runs (priority mangle < filter).\n\nlocal netfilter = require(\"netfilter\")\nlocal byteorder = require(\"byteorder\")\nlocal family    = netfilter.family\nlocal action    = netfilter.action\nlocal hooks     = netfilter.inet_hooks\nlocal priority  = netfilter.ip_priority\n\nlocal IP_TOTLEN  = 2\nlocal IP_SADDR   = 12\nlocal IP_DADDR   = 16\n\nlocal IP6_PAYLEN = 4\nlocal IP6_SADDR  = 8\nlocal IP6_DADDR  = 24\nlocal IP6_HDRLEN = 40\n\nlocal TCP_SPORT  = 0\nlocal TCP_DPORT  = 2\nlocal TCP_SEQ    = 4\nlocal TCP_ACK    = 8\nlocal TCP_DOFF   = 12\nlocal TCP_FLAGS  = 13\nlocal TCP_URGENT = 18\nlocal TCP_HDRLEN = 20\n\nlocal RST   = 0x04\nlocal ACK   = 0x10\nlocal DOFF5 = 0x50  -- data offset = 5 (20 bytes, no options)\n\nlocal function swap_addrs(npkt, nframe, ihl)\n\tlocal ver = npkt:getuint8(0) >> 4\n\tif ver == 4 then\n\t\tlocal saddr = npkt:getuint32(IP_SADDR)\n\t\tlocal daddr = npkt:getuint32(IP_DADDR)\n\t\tnpkt:setuint32(IP_SADDR, daddr)\n\t\tnpkt:setuint32(IP_DADDR, saddr)\n\telse\n\t\tfor i = 0, 3 do\n\t\t\tlocal s = npkt:getuint32(IP6_SADDR + i * 4)\n\t\t\tlocal d = npkt:getuint32(IP6_DADDR + i * 4)\n\t\t\tnpkt:setuint32(IP6_SADDR + i * 4, d)\n\t\t\tnpkt:setuint32(IP6_DADDR + i * 4, s)\n\t\tend\n\tend\n\n\tlocal sport  = npkt:getuint16(ihl + TCP_SPORT)\n\tlocal dportn = npkt:getuint16(ihl + TCP_DPORT)\n\tnpkt:setuint16(ihl + TCP_SPORT, dportn)\n\tnpkt:setuint16(ihl + TCP_DPORT, sport)\n\n\tlocal dst_hi = nframe:getuint32(0)\n\tlocal dst_lo = nframe:getuint16(4)\n\tlocal src_hi = nframe:getuint32(6)\n\tlocal src_lo = nframe:getuint16(10)\n\tnframe:setuint32(0, src_hi)\n\tnframe:setuint16(4, src_lo)\n\tnframe:setuint32(6, dst_hi)\n\tnframe:setuint16(10, dst_lo)\nend\n\nlocal function set_rst(npkt, ihl)\n\tlocal seq  = byteorder.ntoh32(npkt:getuint32(ihl + TCP_SEQ))\n\tlocal ackn = npkt:getuint32(ihl + TCP_ACK)\n\tnpkt:setuint32(ihl + TCP_SEQ, ackn)\n\tnpkt:setuint32(ihl + TCP_ACK, byteorder.hton32(seq + 1))\n\tnpkt:setuint8(ihl + TCP_DOFF, DOFF5)\n\tnpkt:setuint8(ihl + TCP_FLAGS, RST | ACK)\n\tnpkt:setuint16(ihl + TCP_URGENT, 0)\nend\n\nlocal function tcpreject(skb)\n\tlocal pkt = skb:data(\"net\")\n\tlocal ver = pkt:getuint8(0) >> 4\n\tlocal ihl = ver == 4 and (pkt:getuint8(0) & 0x0F) * 4 or IP6_HDRLEN\n\n\tlocal nskb   = skb:copy()\n\tlocal nframe = nskb:data(\"mac\")\n\tlocal npkt   = nskb:data(\"net\")\n\n\tswap_addrs(npkt, nframe, ihl)\n\tset_rst(npkt, ihl)\n\n\tlocal rst_len = ihl + TCP_HDRLEN\n\tnskb:resize(rst_len)\n\tif ver == 4 then\n\t\tnpkt:setuint16(IP_TOTLEN, byteorder.hton16(rst_len))\n\telse\n\t\tnpkt:setuint16(IP6_PAYLEN, byteorder.hton16(TCP_HDRLEN))\n\tend\n\n\tnskb:checksum()\n\tnskb:forward()\n\treturn action.DROP\nend\n\nnetfilter.register{\n\thook     = tcpreject,\n\tpf       = family.INET,\n\thooknum  = hooks.FORWARD,\n\tpriority = priority.FILTER,\n\tmark     = 0x403,\n}\n\nnetfilter.register{\n\thook     = tcpreject,\n\tpf       = family.IPV6,\n\thooknum  = hooks.FORWARD,\n\tpriority = priority.FILTER,\n\tmark     = 0x403,\n}\n\n"
  },
  {
    "path": "examples/tcpreject/setup.sh",
    "content": "#!/bin/bash\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n\nset -eux\n\nNETNS=tcpreject\nVETH_HOST=veth-tcpreject\nVETH_NS=veth-ns\nHOST_ADDR=10.99.0.1\nNS_ADDR=10.99.0.2\nPREFIX=10.99.0.0/24\nHOST_ADDR6=fd99::1\nNS_ADDR6=fd99::2\nPREFIX6=fd99::/64\nDNS4=8.8.8.8\nDNS6=2001:4860:4860::8888\nMARK=0x403\n\n# namespace + veth pair\nip netns add $NETNS\nip link add $VETH_HOST type veth peer name $VETH_NS\nip link set $VETH_NS netns $NETNS\nip addr add $HOST_ADDR/24 dev $VETH_HOST\nip -6 addr add $HOST_ADDR6/64 dev $VETH_HOST\nip link set $VETH_HOST up\nip -n $NETNS addr add $NS_ADDR/24 dev $VETH_NS\nip -n $NETNS -6 addr add $NS_ADDR6/64 dev $VETH_NS\nip -n $NETNS link set $VETH_NS up\nip -n $NETNS link set lo up\nip -n $NETNS route add default via $HOST_ADDR\nip -n $NETNS -6 route add default via $HOST_ADDR6\n\n# forwarding + masquerade so the namespace can reach the internet\necho 1 > /proc/sys/net/ipv4/ip_forward\necho 1 > /proc/sys/net/ipv6/conf/all/forwarding\nnft add table ip tcpreject\nnft add chain ip tcpreject postrouting \\\n\t{ type nat hook postrouting priority srcnat \\; }\nnft add rule ip tcpreject postrouting \\\n\tip saddr $PREFIX masquerade\n\n# mark TCP/443 at mangle priority (before the Lua FILTER hook)\nnft add chain ip tcpreject forward \\\n\t{ type filter hook forward priority mangle \\; }\nnft add rule ip tcpreject forward \\\n\tip daddr $DNS4 tcp dport 443 mark set $MARK\n\nnft add table ip6 tcpreject\nnft add chain ip6 tcpreject forward \\\n\t{ type filter hook forward priority mangle \\; }\nnft add rule ip6 tcpreject forward \\\n\tip6 daddr $DNS6 tcp dport 443 mark set $MARK\n\n# load the Lua hook\nlunatik run examples/tcpreject/nf_tcpreject softirq\n\necho \"setup done\"\necho \"test IPv4: ip netns exec $NETNS curl --connect-timeout 2 https://$DNS4\"\necho \"test IPv6: ip netns exec $NETNS curl --connect-timeout 2 https://[$DNS6]\"\n\n"
  },
  {
    "path": "examples/xiaomi.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025-2026 Jieming Zhou <qrsikno@gmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n-- Porting Xiaomi Silent Mouse's Kernel driver to work with luahid.\n-- Link: https://elixir.bootlin.com/linux/v6.16.3/source/drivers/hid/hid-xiaomi.c\n\nlocal hid = require(\"hid\")\n\nlocal driver = {name = \"luahid_xiaomi\", id_table = {{bus = 0x05, vendor = 0x2717, product = 0x5014}}}\n\nlocal mi_silent_mouse_orig_rdesc_length = 87\nlocal mi_silent_mouse_rdesc_fixed = {\n\t0x05, 0x01, --  Usage Page (Desktop),\n\t0x09, 0x02, --  Usage (Mouse),\n\t0xA1, 0x01, --  Collection (Application),\n\t0x85, 0x03, --      Report ID (3),\n\t0x09, 0x01, --      Usage (Pointer),\n\t0xA1, 0x00, --      Collection (Physical),\n\t0x05, 0x09, --          Usage Page (Button),\n\t0x19, 0x01, --          Usage Minimum (01h),\n\t0x29, 0x05, -- X  --    Usage Maximum (05h),\n\t0x15, 0x00, --          Logical Minimum (0),\n\t0x25, 0x01, --          Logical Maximum (1),\n\t0x75, 0x01, --          Report Size (1),\n\t0x95, 0x05, --          Report Count (5),\n\t0x81, 0x02, --          Input (Variable),\n\t0x75, 0x03, --          Report Size (3),\n\t0x95, 0x01, --          Report Count (1),\n\t0x81, 0x01, --          Input (Constant),\n\t0x05, 0x01, --          Usage Page (Desktop),\n\t0x09, 0x30, --          Usage (X),\n\t0x09, 0x31, --          Usage (Y),\n\t0x15, 0x81, --          Logical Minimum (-127),\n\t0x25, 0x7F, --          Logical Maximum (127),\n\t0x75, 0x08, --          Report Size (8),\n\t0x95, 0x02, --          Report Count (2),\n\t0x81, 0x06, --          Input (Variable, Relative),\n\t0x09, 0x38, --          Usage (Wheel),\n\t0x15, 0x81, --          Logical Minimum (-127),\n\t0x25, 0x7F, --          Logical Maximum (127),\n\t0x75, 0x08, --          Report Size (8),\n\t0x95, 0x01, --          Report Count (1),\n\t0x81, 0x06, --          Input (Variable, Relative),\n\t0xC0,      --      End Collection,\n\t0xC0,      --  End Collection,\n\t0x06, 0x01, 0xFF, --  Usage Page (FF01h),\n\t0x09, 0x01, --  Usage (01h),\n\t0xA1, 0x01, --  Collection (Application),\n\t0x85, 0x05, --      Report ID (5),\n\t0x09, 0x05, --      Usage (05h),\n\t0x15, 0x00, --      Logical Minimum (0),\n\t0x26, 0xFF, 0x00, --      Logical Maximum (255),\n\t0x75, 0x08, --      Report Size (8),\n\t0x95, 0x04, --      Report Count (4),\n\t0xB1, 0x02, --      Feature (Variable),\n\t0xC0       --  End Collection\n}\n\nfunction driver:report_fixup(hdev, report, data)\n\tif hdev.product == 0x5014 and #report == mi_silent_mouse_orig_rdesc_length then\n\t\tprint(\"Fixing Xiaomi Silent Mouse report descriptor\")\n\t\tfor i = 1, #mi_silent_mouse_rdesc_fixed do\n\t\t\treport:setbyte(i - 1, mi_silent_mouse_rdesc_fixed[i])\n\t\tend\n\tend\nend\n\nhid.register(driver)\n\n"
  },
  {
    "path": "gensymbols.sh",
    "content": "#!/bin/sh\n# SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n\nsed \"s/.*luaconf.h.*//g\" \"$@\" | \\\n        ${CC} -Ilua/ -D_KERNEL -DLUNATIK_GENSYMBOLS -E - | \\\n\tgrep API | sed \"s/.*(\\(\\w*\\))\\s*(.*/EXPORT_SYMBOL(\\1);/g\"\n\n"
  },
  {
    "path": "include/assert.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_assert_h\n#define lunatik_assert_h\n\n/* placeholder */\n\n#endif\n\n"
  },
  {
    "path": "include/ctype.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_ctype_h\n#define lunatik_ctype_h\n\n#include <linux/ctype.h>\n\n#endif\n\n"
  },
  {
    "path": "include/errno.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_errno_h\n#define lunatik_errno_h\n\nstatic __maybe_unused int errno;\n\n#endif\n\n"
  },
  {
    "path": "include/float.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_float_h\n#define lunatik_float_h\n\n/* placeholder */\n\n#endif\n\n"
  },
  {
    "path": "include/klibc/diverr.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_klibc_diverr_h\n#define lunatik_klibc_diverr_h\n\n#include <linux/types.h>\n\nstatic __inline__ void __divide_error(void) {}\n\n#endif\n\n"
  },
  {
    "path": "include/limits.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_limits_h\n#define lunatik_limits_h\n\n#include <linux/limits.h>\n\n#define UCHAR_MAX\t(255)\n#define CHAR_BIT\t(8)\n\n/**\n * vdso/limits.h defines INT_MAX as ((int)(~0U >> 1)) which breaks\n * \"#if MAX_CNST/(MAXARG_vC + 1) > MAXARG_Ax\" on lparser.c\n * see https://gcc.gnu.org/onlinedocs/cpp/If.html\n **/\n#undef INT_MAX\n#define INT_MAX\t(2147483647)\n\n#endif\n\n"
  },
  {
    "path": "include/locale.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_locale_h\n#define lunatik_locale_h\n\n/* placeholder */\n\n#endif\n\n"
  },
  {
    "path": "include/math.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_math_h\n#define lunatik_math_h\n\n/* placeholder */\n\n#endif\n\n"
  },
  {
    "path": "include/setjmp.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_setjmp_h\n#define lunatik_setjmp_h\n\n#include <linux/types.h>\n\n#include <klibc/archconfig.h>\n#include <klibc/archsetjmp.h>\n\nextern int setjmp(jmp_buf);\nextern void __attribute__((noreturn)) longjmp(jmp_buf, int);\n\n#endif\n\n"
  },
  {
    "path": "include/stdarg.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_stdarg_h\n#define lunatik_stdarg_h\n\n#include <linux/stdarg.h>\n\n#endif\n\n"
  },
  {
    "path": "include/stddef.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_stddef_h\n#define lunatik_stddef_h\n\n#include <linux/stddef.h>\n\n#endif\n\n"
  },
  {
    "path": "include/stdint.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_stdint_h\n#define lunatik_stdint_h\n\n#include <linux/types.h>\n\n#if BITS_PER_LONG == 32\nuint64_t __udivmoddi4(uint64_t num, uint64_t den, uint64_t *rem);\nint64_t __divdi3(int64_t num, int64_t den);\nuint64_t __udivdi3(uint64_t num, uint64_t den);\nint64_t __moddi3(int64_t num, int64_t den);\nuint64_t __umoddi3(uint64_t num, uint64_t den);\n#endif /* BITS_PER_LONG = 32 */\n#endif\n\n"
  },
  {
    "path": "include/stdio.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/* Kernel-space FILE implementation for lua/liolib.c, backed by VFS */\n\n#ifndef lunatik_stdio_h\n#define lunatik_stdio_h\n\n#include <errno.h>\n\n#include <linux/printk.h>\n#include <linux/fs.h>\n#include <linux/slab.h>\n\n#define BUFSIZE\t\t\t(512)\n#define EOF\t\t\t(-1)\n#define feof(f)\t\t\t((f)->eof)\n#define ferror(f)\t\t((f)->err)\n#define clearerr(f)\t\t((f)->eof = 0, (f)->err = 0)\n#define fflush(f)\t\t(0)\n#define bufempty(f)\t\t((f)->bufpos >= (f)->buflen && unlikely(fill(f) == 0))\n\n#define l_getc(f)\t\tgetc(f)\n#define l_lockfile(f)\t\t((void)(f))\n#define l_unlockfile(f)\t\t((void)(f))\n#define l_fseek(f, o, w)\tseek((f), (o), (w))\n#define l_ftell(f)\t\t((f)->pos)\n#define l_seeknum\t\tloff_t\n\ntypedef struct {\n\tstruct file *filp;\n\tloff_t pos;\n\tint bufpos;\n\tint buflen;\n\tint err;\n\tint eof;\n\tint unget;\t/* -1 = empty, 0-255 = pushed-back char */\n\tchar buf[BUFSIZE];\n} FILE;\n\nstatic inline FILE *fopen(const char *path, const char *mode)\n{\n\tif (unlikely(!mode))\n\t\treturn NULL;\n\n\tint flags;\n\tswitch (mode[0]) {\n\tcase 'r':\n\t\tflags = (mode[1] == '+') ? O_RDWR : O_RDONLY;\n\t\tbreak;\n\tcase 'w':\n\t\tflags = (mode[1] == '+') ? O_RDWR | O_CREAT | O_TRUNC\n\t\t\t\t : O_WRONLY | O_CREAT | O_TRUNC;\n\t\tbreak;\n\tcase 'a':\n\t\tflags = (mode[1] == '+') ? O_RDWR | O_CREAT | O_APPEND\n\t\t\t\t : O_WRONLY | O_CREAT | O_APPEND;\n\t\tbreak;\n\tdefault:\n\t\treturn NULL;\n\t}\n\n\tFILE *f = kzalloc(sizeof(FILE), GFP_KERNEL);\n\tif (unlikely(!f))\n\t\treturn NULL;\n\n\tf->filp = filp_open(path, flags, 0666);\n\tif (unlikely(IS_ERR(f->filp))) {\n\t\tkfree(f);\n\t\treturn NULL;\n\t}\n\tf->unget = -1;\n\treturn f;\n}\n\nstatic inline int fclose(FILE *f)\n{\n\tint ret = filp_close(f->filp, NULL);\n\tkfree(f);\n\treturn ret ? EOF : 0;\n}\n\nstatic inline int fill(FILE *f)\n{\n\tssize_t n = kernel_read(f->filp, f->buf, BUFSIZE, &f->pos);\n\n\tf->bufpos = 0;\n\tif (unlikely(n <= 0)) {\n\t\tf->eof = (n == 0);\n\t\tf->err = (n < 0);\n\t\tf->buflen = 0;\n\t\treturn 0;\n\t}\n\n\tf->buflen = (int)n;\n\treturn (int)n;\n}\n\nstatic inline int getc(FILE *f)\n{\n\tif (unlikely(f->unget >= 0)) {\n\t\tint c = f->unget;\n\t\tf->unget = -1;\n\t\treturn c;\n\t}\n\n\treturn bufempty(f) ? EOF : (unsigned char)f->buf[f->bufpos++];\n}\n\nstatic inline int ungetc(int c, FILE *f)\n{\n\tif (unlikely(c == EOF))\n\t\treturn EOF;\n\tf->eof = 0;\n\tf->unget = c;\n\treturn c;\n}\n\nstatic inline size_t fread(void *ptr, size_t sz, size_t nmemb, FILE *f)\n{\n\tsize_t total;\n\tif (unlikely(sz == 0 || (total = sz * nmemb) == 0))\n\t\treturn 0;\n\n\tsize_t done = 0;\n\tchar *dst = (char *)ptr;\n\tif (unlikely(f->unget >= 0)) {\n\t\tdst[done++] = (char)f->unget;\n\t\tf->unget = -1;\n\t}\n\n\twhile (done < total) {\n\t\tint avail = f->buflen - f->bufpos;\n\t\tif (unlikely(!(avail || (avail = fill(f)))))\n\t\t\tbreak;\n\n\t\tint n = (int)min_t(size_t, avail, total - done);\n\t\tmemcpy(dst + done, f->buf + f->bufpos, n);\n\t\tf->bufpos += n;\n\t\tdone += n;\n\t}\n\treturn done / sz;\n}\n\nstatic inline size_t fwrite(const void *ptr, size_t sz, size_t nmemb, FILE *f)\n{\n\tsize_t total;\n\tif (unlikely(sz == 0 || (total = sz * nmemb) == 0))\n\t\treturn 0;\n\n\tssize_t n = kernel_write(f->filp, ptr, total, &f->pos);\n\tif (unlikely(n < 0)) {\n\t\tf->err = 1;\n\t\treturn 0;\n\t}\n\n\treturn (size_t)n / sz;\n}\n\nstatic inline int seek(FILE *f, loff_t off, int whence)\n{\n\tloff_t ret = vfs_llseek(f->filp, off, whence);\n\n\tif (unlikely(ret < 0)) {\n\t\tf->err = 1;\n\t\treturn -1;\n\t}\n\n\tf->pos = ret;\n\tf->bufpos = 0;\n\tf->buflen = 0;\n\tf->unget = -1;\n\treturn 0;\n}\n\n#endif\n\n"
  },
  {
    "path": "include/stdlib.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_stdlib_h\n#define lunatik_stdlib_h\n\n#include <linux/slab.h>\n#define abort()\t\tBUG()\n#define free(a)\t\tkfree((a))\n#define realloc(a,b)\tkrealloc((a),(b),GFP_KERNEL)\nstatic inline char *getenv(const char *name)\n{\n\t(void)name;\n\treturn NULL;\n}\n\n#endif\n\n"
  },
  {
    "path": "include/string.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_string_h\n#define lunatik_string_h\n\n#include <linux/string.h>\n#define strcoll(l,r)\tstrcmp((l),(r))\n#define strerror(n)\t\"I/O error\"\n\n#if defined(CONFIG_FORTIFY_SOURCE) && !defined(unsafe_memcpy)\n#define unsafe_memcpy(dst, src, bytes, justification)\t__builtin_memcpy(dst, src, bytes)\n#endif\n\n#endif\n\n"
  },
  {
    "path": "include/time.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_time_h\n#define lunatik_time_h\n\n/* placeholder */\n\n#endif\n\n"
  },
  {
    "path": "lib/Kbuild",
    "content": "# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n\nobj-$(CONFIG_LUNATIK_DEVICE) += luadevice.o\nobj-$(CONFIG_LUNATIK_LINUX) += lualinux.o\nobj-$(CONFIG_LUNATIK_NOTIFIER) += luanotifier.o\nobj-$(CONFIG_LUNATIK_SOCKET) += luasocket.o\nobj-$(CONFIG_LUNATIK_RCU) += luarcu.o\nobj-$(CONFIG_LUNATIK_THREAD) += luathread.o\nobj-$(CONFIG_LUNATIK_FIB) += luafib.o\nobj-$(CONFIG_LUNATIK_DATA) += luadata.o\nobj-$(CONFIG_LUNATIK_PROBE) += luaprobe.o\nobj-$(CONFIG_LUNATIK_SYSCALL) += luasyscall.o\nobj-$(CONFIG_LUNATIK_XDP) += luaxdp.o\nobj-$(CONFIG_LUNATIK_FIFO) += luafifo.o\nobj-$(CONFIG_LUNATIK_NETFILTER) += luanetfilter.o\nobj-$(CONFIG_LUNATIK_COMPLETION) += luacompletion.o\nobj-$(CONFIG_LUNATIK_CRYPTO) += luacrypto.o\nluacrypto-objs := luacrypto_shash.o luacrypto_skcipher.o luacrypto_aead.o \\\n                  luacrypto_rng.o luacrypto_comp.o luacrypto_core.o\nobj-$(CONFIG_LUNATIK_CPU) += luacpu.o\nobj-$(CONFIG_LUNATIK_HID) += luahid.o\nobj-$(CONFIG_LUNATIK_SIGNAL) += luasignal.o\nobj-$(CONFIG_LUNATIK_BYTEORDER) += luabyteorder.o\nobj-$(CONFIG_LUNATIK_DARKEN) += luadarken.o\nobj-$(CONFIG_LUNATIK_SKB) += luaskb.o\n\n"
  },
  {
    "path": "lib/crypto/hkdf.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025-2026 jperon <cataclop@hotmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n--- HMAC-based Extract-and-Expand Key Derivation Function (HKDF) based on RFC 5869.\n-- This module provides functions to perform HKDF operations, utilizing the\n-- underlying `crypto` C module for HMAC calculations.\n-- @classmod crypto.hkdf\n\nlocal shash = require(\"crypto\").shash\nlocal char, rep, sub = string.char, string.rep, string.sub\n\n--- HKDF operations.\n-- This table provides the `new` method to create HKDF instances and also\n-- serves as the prototype for these instances.\nlocal HKDF = {}\n\n--- Closes the HKDF instance and releases the underlying HMAC transform.\n-- This method is also called by the garbage collector.\n-- @function HKDF:close\nfunction HKDF:close()\n\tself.tfm:__close()\nend\n\nHKDF.__close = HKDF.close\nHKDF.__index = HKDF\n\nfunction HKDF.__len(self)\n\treturn self.tfm:digestsize()\nend\n\n--- Creates a new HKDF instance for a given hash algorithm.\n-- @function HKDF.new\n-- @tparam string alg base hash algorithm name (e.g., \"sha256\", \"sha512\").\n-- The \"hmac(\" prefix will be added automatically.\n-- @treturn HKDF An HKDF instance table with methods for key derivation.\n-- @usage local hkdf_sha256 = require(\"crypto.hkdf\").new(\"sha256\")\nfunction HKDF.new(alg)\n\tlocal hmac = setmetatable({}, HKDF)\n\thmac.tfm = shash(\"hmac(\" .. alg .. \")\")\n\thmac.salt = rep(\"\\0\", #hmac)\n\n\treturn hmac\nend\n\n--- Performs an HMAC calculation using the instance's algorithm.\n-- @tparam string key HMAC key.\n-- @tparam string data data to hash.\n-- @treturn string HMAC digest.\nfunction HKDF:hmac(key, data)\n\tself.tfm:setkey(key)\n\treturn self.tfm:digest(data)\nend\n\n--- Performs the HKDF Extract step.\n-- @function HKDF:extract\n-- @tparam[opt] string salt Optional salt value. If nil or not provided, a salt of `hash_len` zeros is used.\n-- @tparam string ikm Input Keying Material.\n-- @treturn string Pseudorandom Key (PRK).\nfunction HKDF:extract(salt, ikm)\n\treturn self:hmac((salt or self.salt), ikm)\nend\n\n--- Performs the HKDF Expand step.\n-- @function HKDF:expand\n-- @tparam string prk Pseudorandom Key.\n-- @tparam[opt] string info Optional context and application-specific information. Defaults to an empty string if nil.\n-- @tparam number length desired length in bytes for the Output Keying Material (OKM).\n-- @treturn string Output Keying Material of the specified `length`.\nfunction HKDF:expand(prk, info, length)\n\tinfo = info or \"\"\n\tlocal hash_len = #self\n\tlocal n = length / hash_len\n\tn = (n * hash_len == length) and n or n + 1\n\n\tlocal okm, t = \"\", \"\"\n\tfor i = 1, n do\n\t\tt = self:hmac(prk, t .. info .. char(i))\n\t\tokm = okm .. t\n\tend\n\treturn sub(okm, 1, length)\nend\n\n--- Performs the full HKDF (Extract and Expand) operation.\n-- @function HKDF:hkdf\n-- @tparam[opt] string salt Optional salt value.\n-- @tparam string ikm Input Keying Material.\n-- @tparam[opt] string info Optional context and application-specific information.\n-- @tparam number length desired length in bytes for the Output Keying Material.\n-- @treturn string Output Keying Material.\nfunction HKDF:hkdf(salt, ikm, info, length)\n\treturn self:expand(self:extract(salt, ikm), info, length)\nend\n\nreturn HKDF\n\n"
  },
  {
    "path": "lib/lighten.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n--- Encrypted Lua script support (AES-256-CTR).\n-- This module provides functions to run encrypted Lua scripts\n-- using the `darken` C module.\n-- @module lighten\n\nlocal light = require(\"light\")\nlocal darken = require(\"darken\")\nlocal hex2bin = require(\"util\").hex2bin\n\nlocal lighten = {}\n\n--- Decrypts and executes an encrypted Lua script.\n-- @tparam string ct Hex-encoded ciphertext.\n-- @tparam string iv Hex-encoded 16-byte IV.\n-- @return The return values of the decrypted script.\nfunction lighten.run(ct, iv)\n\treturn darken.run(hex2bin(ct), hex2bin(iv), hex2bin(light))\nend\n\nreturn lighten\n\n"
  },
  {
    "path": "lib/luabyteorder.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2024-2026 Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com>\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#include <linux/module.h>\n#include <linux/byteorder/generic.h>\n\n#include <lunatik.h>\n\n/***\n* Byte Order Conversion\n* @module byteorder\n*/\n#define LUABYTEORDER_BYTESWAPPER(swapper, T)\t\t\\\nstatic int luabyteorder_##swapper(lua_State *L)\t\t\\\n{\t\t\t\t\t\t\t\\\n\tT x = (T)luaL_checkinteger(L, 1);\t\t\\\n\tlua_pushinteger(L, (lua_Integer)swapper(x));\t\\\n\treturn 1;\t\t\t\t\t\\\n}\n\n/***\n* Converts a 16-bit integer from host byte order to big-endian byte order.\n* @function htobe16\n* @tparam integer num16-bit integer in host byte order.\n* @treturn integer value in big-endian byte order.\n*/\nLUABYTEORDER_BYTESWAPPER(cpu_to_be16, u16);\n\n/***\n* Converts a 32-bit integer from host byte order to big-endian byte order.\n* @function htobe32\n* @tparam integer num32-bit integer in host byte order.\n* @treturn integer value in big-endian byte order.\n*/\nLUABYTEORDER_BYTESWAPPER(cpu_to_be32, u32);\n\n/***\n* Converts a 16-bit integer from host byte order to little-endian byte order.\n* @function htole16\n* @tparam integer num16-bit integer in host byte order.\n* @treturn integer value in little-endian byte order.\n*/\nLUABYTEORDER_BYTESWAPPER(cpu_to_le16, u16);\n\n/***\n* Converts a 32-bit integer from host byte order to little-endian byte order.\n* @function htole32\n* @tparam integer num32-bit integer in host byte order.\n* @treturn integer value in little-endian byte order.\n*/\nLUABYTEORDER_BYTESWAPPER(cpu_to_le32, u32);\n\n/***\n* Converts a 16-bit integer from big-endian byte order to host byte order.\n* @function be16toh\n* @tparam integer num16-bit integer in big-endian byte order.\n* @treturn integer value in host byte order.\n*/\nLUABYTEORDER_BYTESWAPPER(be16_to_cpu, u16);\n\n/***\n* Converts a 32-bit integer from big-endian byte order to host byte order.\n* @function be32toh\n* @tparam integer num32-bit integer in big-endian byte order.\n* @treturn integer value in host byte order.\n*/\nLUABYTEORDER_BYTESWAPPER(be32_to_cpu, u32);\n\n/***\n* Converts a 16-bit integer from little-endian byte order to host byte order.\n* @function le16toh\n* @tparam integer num16-bit integer in little-endian byte order.\n* @treturn integer value in host byte order.\n*/\nLUABYTEORDER_BYTESWAPPER(le16_to_cpu, u16);\n\n/***\n* Converts a 32-bit integer from little-endian byte order to host byte order.\n* @function le32toh\n* @tparam integer num32-bit integer in little-endian byte order.\n* @treturn integer value in host byte order.\n*/\nLUABYTEORDER_BYTESWAPPER(le32_to_cpu, u32);\n\n/***\n* Converts a 64-bit integer from host byte order to big-endian byte order.\n* @function htobe64\n* @tparam integer num64-bit integer in host byte order.\n* @treturn integer value in big-endian byte order.\n*/\nLUABYTEORDER_BYTESWAPPER(cpu_to_be64, u64);\n\n/***\n* Converts a 64-bit integer from host byte order to little-endian byte order.\n* @function htole64\n* @tparam integer num64-bit integer in host byte order.\n* @treturn integer value in little-endian byte order.\n*/\nLUABYTEORDER_BYTESWAPPER(cpu_to_le64, u64);\n\n/***\n* Converts a 64-bit integer from big-endian byte order to host byte order.\n* @function be64toh\n* @tparam integer num64-bit integer in big-endian byte order.\n* @treturn integer value in host byte order.\n*/\nLUABYTEORDER_BYTESWAPPER(be64_to_cpu, u64);\n\n/***\n* Converts a 64-bit integer from little-endian byte order to host byte order.\n* @function le64toh\n* @tparam integer num64-bit integer in little-endian byte order.\n* @treturn integer value in host byte order.\n*/\nLUABYTEORDER_BYTESWAPPER(le64_to_cpu, u64);\n\nstatic const luaL_Reg luabyteorder_lib[] = {\n/***\n* Converts a 16-bit integer from network (big-endian) byte order to host byte order.\n* @function ntoh16\n* @tparam integer num16-bit integer in network byte order.\n* @treturn integer value in host byte order.\n*/\n\t{\"ntoh16\", luabyteorder_be16_to_cpu},\n/***\n* Converts a 32-bit integer from network (big-endian) byte order to host byte order.\n* @function ntoh32\n* @tparam integer num32-bit integer in network byte order.\n* @treturn integer value in host byte order.\n*/\n\t{\"ntoh32\", luabyteorder_be32_to_cpu},\n/***\n* Converts a 16-bit integer from host byte order to network (big-endian) byte order.\n* @function hton16\n* @tparam integer num16-bit integer in host byte order.\n* @treturn integer value in network byte order.\n*/\n\t{\"hton16\", luabyteorder_cpu_to_be16},\n/***\n* Converts a 32-bit integer from host byte order to network (big-endian) byte order.\n* @function hton32\n* @tparam integer num32-bit integer in host byte order.\n* @treturn integer value in network byte order.\n*/\n\t{\"hton32\", luabyteorder_cpu_to_be32},\n\t{\"htobe16\", luabyteorder_cpu_to_be16},\n\t{\"htobe32\", luabyteorder_cpu_to_be32},\n\t{\"htole16\", luabyteorder_cpu_to_le16},\n\t{\"htole32\", luabyteorder_cpu_to_le32},\n\t{\"be16toh\", luabyteorder_be16_to_cpu},\n\t{\"be32toh\", luabyteorder_be32_to_cpu},\n\t{\"le16toh\", luabyteorder_le16_to_cpu},\n\t{\"le32toh\", luabyteorder_le32_to_cpu},\n/***\n* Converts a 64-bit integer from network (big-endian) byte order to host byte order.\n* @function ntoh64\n* @tparam integer num64-bit integer in network byte order.\n* @treturn integer value in host byte order.\n*/\n\t{\"ntoh64\", luabyteorder_be64_to_cpu},\n/***\n* Converts a 64-bit integer from host byte order to network (big-endian) byte order.\n* @function hton64\n* @tparam integer num64-bit integer in host byte order.\n* @treturn integer value in network byte order.\n*/\n\t{\"hton64\", luabyteorder_cpu_to_be64},\n\t{\"htobe64\", luabyteorder_cpu_to_be64},\n\t{\"htole64\", luabyteorder_cpu_to_le64},\n\t{\"be64toh\", luabyteorder_be64_to_cpu},\n\t{\"le64toh\", luabyteorder_le64_to_cpu},\n\t{NULL, NULL}\n};\n\nLUNATIK_NEWLIB(byteorder, luabyteorder_lib, NULL, NULL);\n\nstatic int __init luabyteorder_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luabyteorder_exit(void)\n{\n}\n\nmodule_init(luabyteorder_init);\nmodule_exit(luabyteorder_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com>\");\n\n"
  },
  {
    "path": "lib/luacompletion.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2025 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Lua bindings for kernel completion mechanisms.\n* This library allows Lua scripts to create, signal, and wait on\n* kernel completion objects.\n*\n* Task completion is a synchronization mechanism used to coordinate the\n* execution of multiple threads. It allows threads to wait for a specific\n* event to occur before proceeding, ensuring certain tasks are complete.\n*\n* @module completion\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/completion.h>\n#include <linux/sched.h>\n\n#include <lunatik.h>\n\nLUNATIK_OBJECTCHECKER(luacompletion_check, struct completion *);\n\n/***\n* Signals a completion.\n* This wakes up one task waiting on this completion object.\n* Corresponds to the kernel's `complete()` function.\n* @function complete\n* @treturn nil\n* @usage\n*   -- Assuming 'c' is a completion object returned by completion.new()\n*   c:complete()\n* @see completion.new\n*/\nstatic int luacompletion_complete(lua_State *L)\n{\n\tstruct completion *completion = luacompletion_check(L, 1);\n\n\tcomplete(completion);\n\treturn 0;\n}\n\n/***\n* Waits for a completion to be signaled.\n* This function will block the current Lua runtime until the completion\n* is signaled, an optional timeout occurs, or the wait is interrupted.\n* The Lunatik runtime invoking this method must be sleepable.\n* Corresponds to the kernel's `wait_for_completion_interruptible_timeout()`.\n*\n* @function wait\n* @tparam[opt] integer timeout Optional timeout in milliseconds. If omitted or set to `MAX_SCHEDULE_TIMEOUT` (a large kernel-defined constant), waits indefinitely.\n* @treturn boolean `true` if the completion was signaled successfully before timeout or interruption.\n* @treturn nil,string `nil` and an error message if the wait did not complete successfully. The message will be one of:\n*   - `\"timeout\"`: The specified timeout elapsed.\n*   - `\"interrupt\"`: The waiting task was interrupted by a signal (e.g., `thread.stop()`).\n*   If the wait fails for other kernel internal reasons, the C code might push `\"unknown\"`, though typical documented returns are for timeout and interrupt.\n*   - `\"unknown\"`: An unexpected error occurred during the wait.\n* @usage\n*   -- Assuming 'c' is a completion object\n*   local success, err_msg = c:wait(1000) -- Wait for up to 1 second\n*   if success then\n*     print(\"Completion received!\")\n*   else\n*     print(\"Wait failed: \" .. err_msg)\n*   end\n* @see completion.new\n*/\nstatic int luacompletion_wait(lua_State *L)\n{\n\tstruct completion *completion = luacompletion_check(L, 1);\n\tlua_Integer timeout = luaL_optinteger(L, 2, MAX_SCHEDULE_TIMEOUT);\n\tunsigned long timeout_jiffies = msecs_to_jiffies((unsigned long)timeout);\n\tlong ret;\n\n\tlunatik_checkruntime(L, LUNATIK_OPT_NONE);\n\tret = wait_for_completion_interruptible_timeout(completion, timeout_jiffies);\n\tif (ret > 0) {\n\t\tlua_pushboolean(L, true);\n\t\treturn 1;\n\t}\n\n\tlua_pushnil(L);\n\tswitch (ret) {\n\tcase 0:\n\t\tlua_pushliteral(L, \"timeout\");\n\t\tbreak;\n\tcase -ERESTARTSYS:\n\t\tlua_pushliteral(L, \"interrupt\");\n\t\tbreak;\n\tdefault:\n\t\tlua_pushliteral(L, \"unknown\");\n\t\tbreak;\n\t}\n\treturn 2;\n}\n\nstatic int luacompletion_new(lua_State *L);\n\nstatic const luaL_Reg luacompletion_lib[] = {\n\t{\"new\", luacompletion_new},\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg luacompletion_mt[] = {\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"complete\", luacompletion_complete},\n\t{\"wait\", luacompletion_wait},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luacompletion_class = {\n\t.name = \"completion\",\n\t.methods = luacompletion_mt,\n\t.opt = LUNATIK_OPT_SOFTIRQ,\n};\n\n/***\n* Creates a new kernel completion object.\n* Initializes a `struct completion` and returns it wrapped as a Lua userdata\n* object of type `completion`.\n* @function new\n* @treturn completion A new completion object.\n* @usage\n*   local c = completion.new()\n* @within completion\n*/\nstatic int luacompletion_new(lua_State *L)\n{\n\tlunatik_object_t *object = lunatik_newobject(L, &luacompletion_class, sizeof(struct completion), LUNATIK_OPT_NONE);\n\tstruct completion *completion = (struct completion *)object->private;\n\n\tinit_completion(completion);\n\treturn 1;\n}\n\nLUNATIK_CLASSES(completion, &luacompletion_class);\nLUNATIK_NEWLIB(completion, luacompletion_lib, luacompletion_classes, NULL);\n\nstatic int __init luacompletion_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luacompletion_exit(void)\n{\n}\n\nmodule_init(luacompletion_init);\nmodule_exit(luacompletion_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Savio Sena <savio.sena@gmail.com>\");\n\n"
  },
  {
    "path": "lib/luacpu.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2025 Enderson Maia <endersonmaia@gmail.com\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Lua interface to Linux CPU abstractions.\n* @module cpu\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/cpumask.h>\n#include <linux/kernel_stat.h>\n\n#include <lunatik.h>\n\n#define LUACPU_NUM(name)\t\t\t\t\t\\\nstatic int luacpu_num_##name(lua_State *L)\t\t\t\\\n{\t\t\t\t\t\t\t\t\\\n\tlua_pushinteger(L, (lua_Integer)num_##name##_cpus());\t\\\n\treturn 1;\t\t\t\t\t\t\\\n}\n\n/***\n* @function num_possible\n* @treturn integer number of possible CPUs\n*/\nLUACPU_NUM(possible)\n\n/***\n* @function num_present\n* @treturn integer number of present CPUs\n*/\nLUACPU_NUM(present)\n\n/***\n* @function num_online\n* @treturn integer number of online CPUs\n*/\nLUACPU_NUM(online)\n\n#define luacpu_setstat(L, idx, kcs, name, NAME)\t\t\t\t\\\ndo {\t\t\t\t\t\t\t\t\t\\\n\tlua_pushinteger(L, (lua_Integer)kcs.cpustat[CPUTIME_##NAME]);\t\\\n\tlua_setfield(L, idx - 1, #name);\t\t\t\t\\\n} while (0)\n\n/***\n* Returns CPU time statistics for a given CPU.\n* @function stats\n* @tparam integer cpu CPU number (0-based)\n* @treturn table fields: `user`, `nice`, `system`, `idle`, `iowait`, `irq`,\n*   `softirq`, `steal`, `guest`, `guest_nice`, `forceidle` (if CONFIG_SCHED_CORE)\n* @raise if CPU is offline\n*/\nstatic int luacpu_stats(lua_State *L)\n{\n\tunsigned int cpu = luaL_checkinteger(L, 1);\n\tstruct kernel_cpustat kcs;\n\n\tluaL_argcheck(L, cpu_online(cpu), 1, \"CPU is offline\");\n\n\tkcpustat_cpu_fetch(&kcs, cpu);\n\n\tlua_createtable(L, 0, NR_STATS);\n\n\tluacpu_setstat(L, -1, kcs, user, USER);\n\tluacpu_setstat(L, -1, kcs, nice, NICE);\n\tluacpu_setstat(L, -1, kcs, system, SYSTEM);\n\tluacpu_setstat(L, -1, kcs, idle, IDLE);\n\tluacpu_setstat(L, -1, kcs, iowait, IOWAIT);\n\tluacpu_setstat(L, -1, kcs, irq, IRQ);\n\tluacpu_setstat(L, -1, kcs, softirq, SOFTIRQ);\n\tluacpu_setstat(L, -1, kcs, steal, STEAL);\n\tluacpu_setstat(L, -1, kcs, guest, GUEST);\n\tluacpu_setstat(L, -1, kcs, guest_nice, GUEST_NICE);\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)) && defined(CONFIG_SCHED_CORE)\n\tluacpu_setstat(L, -1, kcs, forceidle, FORCEIDLE);\n#endif\n\treturn 1;\n}\n\n#define LUACPU_FOREACH(name)\t\t\t\t\\\nstatic int luacpu_foreach_##name(lua_State *L)\t\t\\\n{\t\t\t\t\t\t\t\\\n\tunsigned int cpu;\t\t\t\t\\\n\tluaL_checktype(L, 1, LUA_TFUNCTION);\t\t\\\n\tfor_each_##name##_cpu(cpu) {\t\t\t\\\n\t\tlua_pushvalue(L, 1);\t\t\t\\\n\t\tlua_pushinteger(L, cpu);\t\t\\\n\t\tlua_call(L, 1, 0);\t\t\t\\\n\t}\t\t\t\t\t\t\\\n\treturn 0;\t\t\t\t\t\\\n}\n\n/***\n* Calls a function for each possible CPU.\n* @function foreach_possible\n* @tparam function callback called with the CPU number\n*/\nLUACPU_FOREACH(possible)\n\n/***\n* Calls a function for each present CPU.\n* @function foreach_present\n* @tparam function callback called with the CPU number\n*/\nLUACPU_FOREACH(present)\n\n/***\n* Calls a function for each online CPU.\n* @function foreach_online\n* @tparam function callback called with the CPU number\n*/\nLUACPU_FOREACH(online)\n\nstatic const luaL_Reg luacpu_lib[] = {\n\t{\"num_possible\", luacpu_num_possible},\n\t{\"num_present\", luacpu_num_present},\n\t{\"num_online\", luacpu_num_online},\n\t{\"stats\", luacpu_stats},\n\t{\"foreach_possible\", luacpu_foreach_possible},\n\t{\"foreach_present\", luacpu_foreach_present},\n\t{\"foreach_online\", luacpu_foreach_online},\n\t{NULL, NULL}\n};\n\nLUNATIK_NEWLIB(cpu, luacpu_lib, NULL, NULL);\n\nstatic int __init luacpu_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luacpu_exit(void)\n{\n}\n\nmodule_init(luacpu_init);\nmodule_exit(luacpu_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Enderson Maia <endersonmaia@gmail.com>\");\nMODULE_DESCRIPTION(\"Lunatik interface to Linux's CPU abstractions.\");\n\n"
  },
  {
    "path": "lib/luacrypto.h",
    "content": "/*\n * SPDX-FileCopyrightText: (c) 2025-2026 jperon <cataclop@hotmail.com>\n * SPDX-License-Identifier: MIT OR GPL-2.0-only\n */\n\n#ifndef _LUACRYPTO_H\n#define _LUACRYPTO_H\n\n#include <linux/err.h>\n#include <linux/version.h>\n#include <lunatik.h>\n\n#define LUACRYPTO_NEW(name, T, alloc, class)\t\t\t\t\t\t\t\\\nint luacrypto_##name##_new(lua_State *L)\t\t\t\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tconst char *algname = luaL_checkstring(L, 1);\t\t\t\t\t\t\\\n\tT *tfm = alloc(algname, 0, 0);\t\t\t\t\t\t\t\t\\\n\tif (IS_ERR(tfm))\t\t\t\t\t\t\t\t\t\\\n\t\tlunatik_throw(L, PTR_ERR(tfm));\t\t\t\t\t\t\t\\\n\tlunatik_object_t *object = lunatik_newobject(L, &class, 0, LUNATIK_OPT_NONE);\t\t\\\n\tobject->private = tfm;\t\t\t\t\t\t\t\t\t\\\n\treturn 1;\t\t\t\t\t\t\t\t\t\t\\\n}\n\n#define LUACRYPTO_RELEASER(name, T, obj_free)\t\t\t\\\nstatic void luacrypto_##name##_release(void *private)\t\t\\\n{\t\t\t\t\t\t\t\t\\\n\tT *obj = (T *)private;\t\t\t\t\t\\\n\tif (obj)\t\t\t\t\t\t\\\n\t\tobj_free(obj);\t\t\t\t\t\\\n}\n\nextern const lunatik_class_t luacrypto_shash_class;\nextern const lunatik_class_t luacrypto_skcipher_class;\nextern const lunatik_class_t luacrypto_aead_class;\nextern const lunatik_class_t luacrypto_rng_class;\n\nint luacrypto_shash_new(lua_State *L);\nint luacrypto_skcipher_new(lua_State *L);\nint luacrypto_aead_new(lua_State *L);\nint luacrypto_rng_new(lua_State *L);\n\n#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 15, 0))\nextern const lunatik_class_t luacrypto_comp_class;\nint luacrypto_comp_new(lua_State *L);\n#endif\n\n#endif /* _LUACRYPTO_H */\n\n"
  },
  {
    "path": "lib/luacrypto_aead.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2025-2026 jperon <cataclop@hotmail.com>\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Lua interface to AEAD (Authenticated Encryption with Associated Data) ciphers.\n* @classmod crypto_aead\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n\n#include <crypto/aead.h>\n#include <linux/err.h>\n#include <linux/scatterlist.h>\n#include <linux/slab.h>\n\n#include \"luacrypto.h\"\n\nLUNATIK_PRIVATECHECKER(luacrypto_aead_check, struct crypto_aead *);\n\nLUACRYPTO_RELEASER(aead, struct crypto_aead, crypto_free_aead);\n\n/***\n* Sets the cipher key.\n* @function setkey\n* @tparam string key\n* @raise on invalid key length or algorithm error\n*/\nstatic int luacrypto_aead_setkey(lua_State *L)\n{\n\tstruct crypto_aead *tfm = luacrypto_aead_check(L, 1);\n\tsize_t keylen;\n\tconst char *key = luaL_checklstring(L, 2, &keylen);\n\tlunatik_try(L, crypto_aead_setkey, tfm, key, keylen);\n\treturn 0;\n}\n\n/***\n* Sets the authentication tag size.\n* @function setauthsize\n* @tparam integer tagsize tag size in bytes\n* @raise on unsupported size\n*/\nstatic int luacrypto_aead_setauthsize(lua_State *L)\n{\n\tstruct crypto_aead *tfm = luacrypto_aead_check(L, 1);\n\tunsigned int tagsize = (unsigned int)lunatik_checkinteger(L, 2, 1, UINT_MAX);\n\tlunatik_try(L, crypto_aead_setauthsize, tfm, tagsize);\n\treturn 0;\n}\n\n/***\n* Returns the required IV size in bytes.\n* @function ivsize\n* @treturn integer\n*/\nstatic int luacrypto_aead_ivsize(lua_State *L)\n{\n\tstruct crypto_aead *tfm = luacrypto_aead_check(L, 1);\n\tlua_pushinteger(L, crypto_aead_ivsize(tfm));\n\treturn 1;\n}\n\n/***\n* Returns the authentication tag size in bytes.\n* @function authsize\n* @treturn integer\n*/\nstatic int luacrypto_aead_authsize(lua_State *L)\n{\n\tstruct crypto_aead *tfm = luacrypto_aead_check(L, 1);\n\tlua_pushinteger(L, crypto_aead_authsize(tfm));\n\treturn 1;\n}\n\ntypedef struct luacrypto_aead_request_s {\n\tstruct scatterlist sg;\n\tstruct aead_request *aead;\n\tconst char *data;\n\tconst char *aad;\n\tu8 *iv;\n\tsize_t aad_len;\n\tsize_t crypt_len;\n\tsize_t authsize;\n} luacrypto_aead_request_t;\n\nstatic inline void luacrypto_aead_newrequest(lua_State *L, luacrypto_aead_request_t *request)\n{\n\tmemset(request, 0, sizeof(luacrypto_aead_request_t));\n\tstruct crypto_aead *tfm = luacrypto_aead_check(L, 1);\n\n\tsize_t iv_len;\n\tconst char *iv = luaL_checklstring(L, 2, &iv_len);\n\tif (iv_len != crypto_aead_ivsize(tfm))\n\t\tlunatik_throw(L, -EINVAL);\n\n\trequest->data = luaL_checklstring(L, 3, &request->crypt_len);\n\trequest->aad = luaL_optlstring(L, 4, \"\", &request->aad_len);\n\trequest->authsize = (size_t)crypto_aead_authsize(tfm);\n\n\trequest->iv = (u8 *)lunatik_checkalloc(L, iv_len);\n\tmemcpy(request->iv, iv, iv_len);\n\n\tgfp_t gfp = lunatik_gfp(lunatik_toruntime(L));\n\trequest->aead = aead_request_alloc(tfm, gfp);\n\tif (request->aead == NULL) {\n\t\tlunatik_free(request->iv);\n\t\tlunatik_enomem(L);\n\t}\n}\n\nstatic inline void luacrypto_aead_setrequest(luacrypto_aead_request_t *request, char *buffer, size_t buffer_len)\n{\n\tstruct aead_request *aead = request->aead;\n\tstruct scatterlist *sg = &request->sg;\n\n\t/* Build combined = aad || data in buffer */\n\tmemcpy(buffer, request->aad, request->aad_len);\n\tmemcpy(buffer + request->aad_len, request->data, request->crypt_len);\n\n\tsg_init_one(sg, buffer, buffer_len);\n\n\taead_request_set_ad(aead, request->aad_len);\n\taead_request_set_crypt(aead, sg, sg, request->crypt_len, request->iv);\n\taead_request_set_callback(aead, 0, NULL, NULL);\n}\n\nstatic inline void luacrypto_aead_request_free(luacrypto_aead_request_t *request)\n{\n\taead_request_free(request->aead);\n\tlunatik_free(request->iv);\n}\n\nstatic inline char *luacrypto_aead_prepare(lua_State *L, luacrypto_aead_request_t *request, size_t buffer_len)\n{\n\tchar *buffer = (char *)lunatik_malloc(L, buffer_len);\n\tif (buffer == NULL) {\n\t\tluacrypto_aead_request_free(request);\n\t\tlunatik_enomem(L);\n\t}\n\tluacrypto_aead_setrequest(request, buffer, buffer_len);\n\treturn buffer;\n}\n\nstatic inline int luacrypto_aead_finish(lua_State *L, luacrypto_aead_request_t *request,\n\tchar *buffer, int ret, size_t output_len)\n{\n\tluacrypto_aead_request_free(request);\n\tif (ret < 0) {\n\t\tlunatik_free(buffer);\n\t\tlunatik_throw(L, ret);\n\t}\n\tlua_pushlstring(L, buffer + request->aad_len, output_len);\n\tlunatik_free(buffer);\n\treturn 1;\n}\n\n/***\n* Encrypts plaintext with authentication.\n* IV length must match `ivsize()`.\n* @function encrypt\n* @tparam string iv initialization vector\n* @tparam string plaintext data to encrypt\n* @tparam[opt] string aad additional authenticated data (default: empty string)\n* @treturn string ciphertext concatenated with authentication tag\n* @raise on encryption failure or incorrect IV length\n*/\nstatic int luacrypto_aead_encrypt(lua_State *L)\n{\n\tluacrypto_aead_request_t request;\n\tluacrypto_aead_newrequest(L, &request);\n\tsize_t buffer_len = request.aad_len + request.crypt_len + request.authsize;\n\tchar *buffer = luacrypto_aead_prepare(L, &request, buffer_len);\n\tint ret = crypto_aead_encrypt(request.aead);\n\treturn luacrypto_aead_finish(L, &request, buffer, ret, request.crypt_len + request.authsize);\n}\n\n/***\n* Decrypts and authenticates ciphertext.\n* IV length must match `ivsize()`. Raises EBADMSG on authentication failure.\n* @function decrypt\n* @tparam string iv initialization vector\n* @tparam string ciphertext_with_tag ciphertext concatenated with authentication tag\n* @tparam[opt] string aad additional authenticated data (default: empty string)\n* @treturn string decrypted plaintext\n* @raise on authentication failure (EBADMSG), incorrect IV length, or input too short\n*/\nstatic int luacrypto_aead_decrypt(lua_State *L)\n{\n\tluacrypto_aead_request_t request;\n\tluacrypto_aead_newrequest(L, &request);\n\tif (request.crypt_len < request.authsize) {\n\t\tluacrypto_aead_request_free(&request);\n\t\tlunatik_throw(L, -EBADMSG);\n\t}\n\tsize_t buffer_len = request.aad_len + request.crypt_len;\n\tchar *buffer = luacrypto_aead_prepare(L, &request, buffer_len);\n\tint ret = crypto_aead_decrypt(request.aead);\n\treturn luacrypto_aead_finish(L, &request, buffer, ret, request.crypt_len - request.authsize);\n}\n\nstatic int luacrypto_aead_algname(lua_State *L)\n{\n\tstruct crypto_aead *tfm = luacrypto_aead_check(L, 1);\n\tlua_pushstring(L, crypto_tfm_alg_name(crypto_aead_tfm(tfm)));\n\treturn 1;\n}\n\nstatic const luaL_Reg luacrypto_aead_mt[] = {\n\t{\"algname\", luacrypto_aead_algname},\n\t{\"setkey\", luacrypto_aead_setkey},\n\t{\"setauthsize\", luacrypto_aead_setauthsize},\n\t{\"ivsize\", luacrypto_aead_ivsize},\n\t{\"authsize\", luacrypto_aead_authsize},\n\t{\"encrypt\", luacrypto_aead_encrypt},\n\t{\"decrypt\", luacrypto_aead_decrypt},\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"__close\", lunatik_closeobject},\n\t{NULL, NULL}\n};\n\nconst lunatik_class_t luacrypto_aead_class = {\n\t.name = \"crypto_aead\",\n\t.methods = luacrypto_aead_mt,\n\t.release = luacrypto_aead_release,\n\t.opt = LUNATIK_OPT_MONITOR | LUNATIK_OPT_EXTERNAL,\n};\n\n/***\n* Creates a new AEAD transform object.\n* @function new\n* @tparam string algname algorithm name (e.g., \"gcm(aes)\", \"ccm(aes)\")\n* @treturn crypto_aead\n* @raise on allocation failure\n* @usage\n*   local aead = require(\"crypto\").aead\n*   local cipher = aead(\"gcm(aes)\")\n*/\nLUACRYPTO_NEW(aead, struct crypto_aead, crypto_alloc_aead, luacrypto_aead_class);\n\n"
  },
  {
    "path": "lib/luacrypto_comp.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2025-2026 jperon <cataclop@hotmail.com>\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Lua interface to synchronous compression algorithms.\n* @classmod crypto_comp\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n\n#include <linux/crypto.h>\n#include <linux/err.h>\n#include <linux/slab.h>\n#include <linux/limits.h>\n\n#include \"luacrypto.h\"\n\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0))\n#warning \"crypto_comp API was removed in Linux 6.15, skip COMP module\"\n#else\nLUNATIK_PRIVATECHECKER(luacrypto_comp_check, struct crypto_comp *);\n\nLUACRYPTO_RELEASER(comp, struct crypto_comp, crypto_free_comp);\n\n#define LUACRYPTO_COMP_OPERATION(name)\t\t\t\t\t\t\t\t\t\\\nstatic int luacrypto_comp_##name(lua_State *L)\t\t\t\t\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tstruct crypto_comp *tfm = luacrypto_comp_check(L, 1);\t\t\t\t\t\t\\\n\tsize_t datalen;\t\t\t\t\t\t\t\t\t\t\t\\\n\tconst u8 *data = (const u8 *)luaL_checklstring(L, 2, &datalen);\t\t\t\t\\\n\tlunatik_checkbounds(L, 2, datalen, 1, UINT_MAX);\t\t\t\t\t\t\\\n\tunsigned int max_len = (unsigned int)lunatik_checkinteger(L, 3, 1, UINT_MAX);\t\\\n\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tluaL_Buffer B;\t\t\t\t\t\t\t\t\t\t\t\\\n\tu8 *output_buf = luaL_buffinitsize(L, &B, max_len);\t\t\t\t\t\t\\\n\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tlunatik_try(L, crypto_comp_##name, tfm, data, (unsigned int)datalen, output_buf, &max_len);\t\\\n\tluaL_pushresultsize(&B, max_len);\t\t\t\t\t\t\t\t\\\n\treturn 1;\t\t\t\t\t\t\t\t\t\t\t\\\n}\n\n/***\n* Compresses data.\n* @function compress\n* @tparam string data input data\n* @tparam integer max_len maximum size of compressed output\n* @treturn string compressed data\n* @raise on compression failure\n*/\nLUACRYPTO_COMP_OPERATION(compress);\n\n/***\n* Decompresses data.\n* @function decompress\n* @tparam string data compressed input\n* @tparam integer max_len maximum size of decompressed output\n* @treturn string decompressed data\n* @raise on decompression failure\n*/\nLUACRYPTO_COMP_OPERATION(decompress);\n\nstatic const luaL_Reg luacrypto_comp_mt[] = {\n\t{\"compress\", luacrypto_comp_compress},\n\t{\"decompress\", luacrypto_comp_decompress},\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"__close\", lunatik_closeobject},\n\t{NULL, NULL}\n};\n\nconst lunatik_class_t luacrypto_comp_class = {\n\t.name = \"crypto_comp\",\n\t.methods = luacrypto_comp_mt,\n\t.release = luacrypto_comp_release,\n\t.opt = LUNATIK_OPT_MONITOR | LUNATIK_OPT_EXTERNAL,\n};\n\n/***\n* Creates a new COMP transform object.\n* @function new\n* @tparam string algname algorithm name (e.g., \"lz4\", \"deflate\")\n* @treturn crypto_comp\n* @raise on allocation failure\n* @usage\n*   local comp = require(\"crypto\").comp\n*   local c = comp(\"lz4\")\n*/\nLUACRYPTO_NEW(comp, struct crypto_comp, crypto_alloc_comp, luacrypto_comp_class);\n\n#endif\n\n"
  },
  {
    "path": "lib/luacrypto_core.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2025-2026 jperon <cataclop@hotmail.com>\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Lua interface to the Linux Crypto API.\n* @module crypto\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n\n#include \"luacrypto.h\"\n\nstatic const luaL_Reg luacrypto_lib[] = {\n\t{\"shash\", luacrypto_shash_new},\n\t{\"skcipher\", luacrypto_skcipher_new},\n\t{\"aead\", luacrypto_aead_new},\n\t{\"rng\", luacrypto_rng_new},\n#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 15, 0))\n\t{\"comp\", luacrypto_comp_new},\n#endif\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t *luacrypto_classes[] = {\n\t&luacrypto_shash_class,\n\t&luacrypto_skcipher_class,\n\t&luacrypto_aead_class,\n\t&luacrypto_rng_class,\n#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 15, 0))\n\t&luacrypto_comp_class,\n#endif\n\tNULL\n};\n\nLUNATIK_NEWLIB(crypto, luacrypto_lib, luacrypto_classes, NULL);\n\nstatic int __init luacrypto_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luacrypto_exit(void)\n{\n}\n\nmodule_init(luacrypto_init);\nmodule_exit(luacrypto_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"jperon <cataclop@hotmail.com>\");\nMODULE_DESCRIPTION(\"Lunatik Linux Crypto API interface\");\n\n"
  },
  {
    "path": "lib/luacrypto_rng.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2025-2026 jperon <cataclop@hotmail.com>\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Lua interface to synchronous Random Number Generators (RNG).\n* @classmod crypto_rng\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n\n#include <crypto/rng.h>\n#include <linux/err.h>\n#include <linux/limits.h>\n#include <linux/slab.h>\n\n#include \"luacrypto.h\"\n\nLUNATIK_PRIVATECHECKER(luacrypto_rng_check, struct crypto_rng *);\n\nLUACRYPTO_RELEASER(rng, struct crypto_rng, crypto_free_rng);\n\n/***\n* Generates random bytes, optionally reseeding first.\n* @function generate\n* @tparam integer n number of bytes to generate\n* @tparam[opt] string seed optional seed data\n* @treturn string random bytes\n* @raise on generation failure\n*/\nstatic int luacrypto_rng_generate(lua_State *L)\n{\n\tstruct crypto_rng *tfm = luacrypto_rng_check(L, 1);\n\tunsigned int num_bytes = (unsigned int)lunatik_checkinteger(L, 2, 1, UINT_MAX);\n\n\tsize_t seed_len = 0;\n\tconst char *seed_data = lua_tolstring(L, 3, &seed_len);\n\n\tluaL_Buffer B;\n\tchar *buffer = luaL_buffinitsize(L, &B, num_bytes);\n\n\tlunatik_try(L, crypto_rng_generate, tfm, seed_data, (unsigned int)seed_len, (u8 *)buffer, num_bytes);\n\tluaL_pushresultsize(&B, num_bytes);\n\treturn 1;\n}\n\n/***\n* Reseeds the RNG.\n* @function reset\n* @tparam string seed\n* @raise on reseed failure\n*/\nstatic int luacrypto_rng_reset(lua_State *L)\n{\n\tstruct crypto_rng *tfm = luacrypto_rng_check(L, 1);\n\tsize_t seed_len = 0;\n\tconst char *seed_data = lua_tolstring(L, 2, &seed_len);\n\tlunatik_try(L, crypto_rng_reset, tfm, (const u8 *)seed_data, (unsigned int)seed_len);\n\treturn 0;\n}\n\n/***\n* Generates random bytes without reseeding.\n* @function getbytes\n* @tparam integer n number of bytes to generate\n* @treturn string random bytes\n* @raise on generation failure\n*/\nstatic int luacrypto_rng_getbytes(lua_State *L)\n{\n\tstruct crypto_rng *tfm = luacrypto_rng_check(L, 1);\n\tunsigned int num_bytes = (unsigned int)lunatik_checkinteger(L, 2, 1, UINT_MAX);\n\n\tluaL_Buffer B;\n\tu8 *buffer = (u8 *)luaL_buffinitsize(L, &B, num_bytes);\n\n\tlunatik_try(L, crypto_rng_get_bytes, tfm, (u8 *)buffer, num_bytes);\n\tluaL_pushresultsize(&B, num_bytes);\n\treturn 1;\n}\n\n/***\n* Returns algorithm information.\n* @function info\n* @treturn table with fields `driver_name` (string) and `seedsize` (integer)\n*/\nstatic int luacrypto_rng_info(lua_State *L)\n{\n\tstruct crypto_rng *tfm = luacrypto_rng_check(L, 1);\n\tconst struct rng_alg *alg = crypto_rng_alg(tfm);\n\n\tlua_createtable(L, 0, 2);\n\n\tlua_pushstring(L, alg->base.cra_driver_name);\n\tlua_setfield(L, -2, \"driver_name\");\n\n\tlua_pushinteger(L, alg->seedsize);\n\tlua_setfield(L, -2, \"seedsize\");\n\treturn 1;\n}\n\nstatic int luacrypto_rng_algname(lua_State *L)\n{\n\tstruct crypto_rng *tfm = luacrypto_rng_check(L, 1);\n\tlua_pushstring(L, crypto_tfm_alg_name(crypto_rng_tfm(tfm)));\n\treturn 1;\n}\n\nstatic const luaL_Reg luacrypto_rng_mt[] = {\n\t{\"algname\", luacrypto_rng_algname},\n\t{\"generate\", luacrypto_rng_generate},\n\t{\"reset\", luacrypto_rng_reset},\n\t{\"getbytes\", luacrypto_rng_getbytes},\n\t{\"info\", luacrypto_rng_info},\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"__close\", lunatik_closeobject},\n\t{NULL, NULL}\n};\n\nconst lunatik_class_t luacrypto_rng_class = {\n\t.name = \"crypto_rng\",\n\t.methods = luacrypto_rng_mt,\n\t.release = luacrypto_rng_release,\n\t.opt = LUNATIK_OPT_MONITOR | LUNATIK_OPT_EXTERNAL,\n};\n\n/***\n* Creates a new RNG object.\n* @function new\n* @tparam[opt] string algname algorithm name; defaults to \"stdrng\"\n* @treturn crypto_rng\n* @raise on allocation or initialization failure\n* @usage\n*   local rng = require(\"crypto\").rng\n*   local r = rng()  -- uses \"stdrng\"\n*/\nint luacrypto_rng_new(lua_State *L)\n{\n\tconst char *algname = luaL_optstring(L, 1, \"stdrng\");\n\tlunatik_object_t *object = lunatik_newobject(L, &luacrypto_rng_class, 0, LUNATIK_OPT_NONE);\n\tstruct crypto_rng *tfm = crypto_alloc_rng(algname, 0, 0);\n\n\tif (IS_ERR(tfm))\n\t\tlunatik_throw(L, PTR_ERR(tfm));\n\n\tobject->private = tfm;\n\tlunatik_try(L, crypto_rng_reset, tfm, NULL, 0);\n\treturn 1;\n}\n\n"
  },
  {
    "path": "lib/luacrypto_shash.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2025-2026 jperon <cataclop@hotmail.com>\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Lua interface to synchronous message digest (hash) algorithms, including HMAC.\n* @classmod crypto_shash\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n\n#include <crypto/hash.h>\n#include <linux/err.h>\n#include <linux/slab.h>\n\n#include \"luacrypto.h\"\n\nLUNATIK_PRIVATECHECKER(luacrypto_shash_check, struct shash_desc *);\n\nstatic void luacrypto_shash_release(void *private)\n{\n\tstruct shash_desc *obj = (struct shash_desc *)private;\n\tif (obj) {\n\t\tif (obj->tfm)\n\t\t\tcrypto_free_shash(obj->tfm);\n\t\tlunatik_free(obj);\n\t}\n}\n\n/***\n* Returns the digest size in bytes.\n* @function digestsize\n* @treturn integer\n*/\nstatic int luacrypto_shash_digestsize(lua_State *L)\n{\n\tstruct shash_desc *sdesc = luacrypto_shash_check(L, 1);\n\tlua_pushinteger(L, crypto_shash_digestsize(sdesc->tfm));\n\treturn 1;\n}\n\n/***\n* Sets the key (required for HMAC algorithms).\n* @function setkey\n* @tparam string key\n* @raise on invalid key or algorithm error\n*/\nstatic int luacrypto_shash_setkey(lua_State *L)\n{\n\tstruct shash_desc *sdesc = luacrypto_shash_check(L, 1);\n\tsize_t keylen;\n\tconst char *key = luaL_checklstring(L, 2, &keylen);\n\tlunatik_try(L, crypto_shash_setkey, sdesc->tfm, key, keylen);\n\treturn 0;\n}\n\n/***\n* Computes the digest of data in a single call.\n* @function digest\n* @tparam string data\n* @treturn string digest bytes\n* @raise on hash failure\n*/\nstatic int luacrypto_shash_digest(lua_State *L)\n{\n\tstruct shash_desc *sdesc = luacrypto_shash_check(L, 1);\n\tsize_t datalen;\n\tconst char *data = luaL_checklstring(L, 2, &datalen);\n\tunsigned int digestsize = crypto_shash_digestsize(sdesc->tfm);\n\tluaL_Buffer B;\n\tu8 *digest_buf = luaL_buffinitsize(L, &B, digestsize);\n\n\tlunatik_try(L, crypto_shash_digest, sdesc, data, datalen, digest_buf);\n\tluaL_pushresultsize(&B, digestsize);\n\treturn 1;\n}\n\n/***\n* Initializes the hash state for incremental hashing.\n* @function init\n* @raise on initialization failure\n*/\nstatic int luacrypto_shash_init_mt(lua_State *L)\n{\n\tstruct shash_desc *sdesc = luacrypto_shash_check(L, 1);\n\n\tlunatik_try(L, crypto_shash_init, sdesc);\n\treturn 0;\n}\n\n/***\n* Feeds data into the running hash.\n* @function update\n* @tparam string data\n* @raise on hash failure\n*/\nstatic int luacrypto_shash_update(lua_State *L)\n{\n\tstruct shash_desc *sdesc = luacrypto_shash_check(L, 1);\n\tsize_t datalen;\n\tconst char *data = luaL_checklstring(L, 2, &datalen);\n\n\tlunatik_try(L, crypto_shash_update, sdesc, data, datalen);\n\treturn 0;\n}\n\n/***\n* Finalizes and returns the digest.\n* @function final\n* @treturn string digest bytes\n* @raise on hash failure\n*/\nstatic int luacrypto_shash_final(lua_State *L)\n{\n\tstruct shash_desc *sdesc = luacrypto_shash_check(L, 1);\n\tunsigned int digestsize = crypto_shash_digestsize(sdesc->tfm);\n\tluaL_Buffer B;\n\tu8 *digest_buf = luaL_buffinitsize(L, &B, digestsize);\n\n\tlunatik_try(L, crypto_shash_final, sdesc, digest_buf);\n\tluaL_pushresultsize(&B, digestsize);\n\treturn 1;\n}\n\n/***\n* Feeds final data and returns the digest (update + final in one call).\n* @function finup\n* @tparam string data\n* @treturn string digest bytes\n* @raise on hash failure\n*/\nstatic int luacrypto_shash_finup(lua_State *L)\n{\n\tstruct shash_desc *sdesc = luacrypto_shash_check(L, 1);\n\tsize_t datalen;\n\tconst char *data = luaL_checklstring(L, 2, &datalen);\n\tunsigned int digestsize = crypto_shash_digestsize(sdesc->tfm);\n\tluaL_Buffer B;\n\tu8 *digest_buf = luaL_buffinitsize(L, &B, digestsize);\n\n\tlunatik_try(L, crypto_shash_finup, sdesc, data, datalen, digest_buf);\n\tluaL_pushresultsize(&B, digestsize);\n\treturn 1;\n}\n\n/***\n* Exports the current internal hash state.\n* @function export\n* @treturn string opaque state blob\n*/\nstatic int luacrypto_shash_export(lua_State *L)\n{\n\tstruct shash_desc *sdesc = luacrypto_shash_check(L, 1);\n\tunsigned int statesize = crypto_shash_statesize(sdesc->tfm);\n\tluaL_Buffer B;\n\tvoid *state_buf = luaL_buffinitsize(L, &B, statesize);\n\tcrypto_shash_export(sdesc, state_buf);\n\tluaL_pushresultsize(&B, statesize);\n\treturn 1;\n}\n\n/***\n* Restores a previously exported hash state.\n* @function import\n* @tparam string state blob returned by `export()`\n* @raise on length mismatch\n*/\nstatic int luacrypto_shash_import(lua_State *L)\n{\n\tstruct shash_desc *sdesc = luacrypto_shash_check(L, 1);\n\tsize_t statelen;\n\tconst char *state = luaL_checklstring(L, 2, &statelen);\n\tunsigned int expected_statesize = crypto_shash_statesize(sdesc->tfm);\n\tif (statelen != expected_statesize)\n\t\tlunatik_throw(L, -EINVAL);\n\tlunatik_try(L, crypto_shash_import, sdesc, state);\n\treturn 0;\n}\n\nstatic int luacrypto_shash_algname(lua_State *L)\n{\n\tstruct shash_desc *sdesc = luacrypto_shash_check(L, 1);\n\tlua_pushstring(L, crypto_tfm_alg_name(crypto_shash_tfm(sdesc->tfm)));\n\treturn 1;\n}\n\nstatic const luaL_Reg luacrypto_shash_mt[] = {\n\t{\"algname\", luacrypto_shash_algname},\n\t{\"digestsize\", luacrypto_shash_digestsize},\n\t{\"setkey\", luacrypto_shash_setkey},\n\t{\"digest\", luacrypto_shash_digest},\n\t{\"init\", luacrypto_shash_init_mt},\n\t{\"update\", luacrypto_shash_update},\n\t{\"final\", luacrypto_shash_final},\n\t{\"finup\", luacrypto_shash_finup},\n\t{\"export\", luacrypto_shash_export},\n\t{\"import\", luacrypto_shash_import},\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"__close\", lunatik_closeobject},\n\t{NULL, NULL}\n};\n\nconst lunatik_class_t luacrypto_shash_class = {\n\t.name = \"crypto_shash\",\n\t.methods = luacrypto_shash_mt,\n\t.release = luacrypto_shash_release,\n\t.opt = LUNATIK_OPT_MONITOR | LUNATIK_OPT_EXTERNAL,\n};\n\n/***\n* Creates a new SHASH object.\n* @function new\n* @tparam string algname algorithm name (e.g., \"sha256\", \"hmac(sha256)\")\n* @treturn crypto_shash\n* @raise on allocation failure\n* @usage\n*   local shash = require(\"crypto\").shash\n*   local h = shash(\"sha256\")\n*/\nint luacrypto_shash_new(lua_State *L)\n{\n\tconst char *algname = luaL_checkstring(L, 1);\n\tstruct crypto_shash *tfm = crypto_alloc_shash(algname, 0, 0);\n\tsize_t desc_size;\n\tstruct shash_desc *sdesc;\n\tlunatik_object_t *object;\n\n\tif (IS_ERR(tfm))\n\t\tlunatik_throw(L, PTR_ERR(tfm));\n\n\tdesc_size = sizeof(struct shash_desc) + crypto_shash_descsize(tfm);\n\tsdesc = lunatik_malloc(L, desc_size);\n\tif (!sdesc) {\n\t\tcrypto_free_shash(tfm);\n\t\tlunatik_enomem(L);\n\t}\n\tsdesc->tfm = tfm;\n\n\tobject = lunatik_newobject(L, &luacrypto_shash_class, 0, LUNATIK_OPT_NONE);\n\tobject->private = sdesc;\n\treturn 1;\n}\n\n"
  },
  {
    "path": "lib/luacrypto_skcipher.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2025-2026 jperon <cataclop@hotmail.com>\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Lua interface to symmetric-key ciphers (SKCIPHER).\n* @classmod crypto_skcipher\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n\n#include <crypto/skcipher.h>\n#include <linux/err.h>\n#include <linux/scatterlist.h>\n#include <linux/slab.h>\n\n#include \"luacrypto.h\"\n\nLUNATIK_PRIVATECHECKER(luacrypto_skcipher_check, struct crypto_skcipher *);\n\nLUACRYPTO_RELEASER(skcipher, struct crypto_skcipher, crypto_free_skcipher);\n\n/***\n* Sets the cipher key.\n* @function setkey\n* @tparam string key\n* @raise on invalid key length or algorithm error\n*/\nstatic int luacrypto_skcipher_setkey(lua_State *L)\n{\n\tstruct crypto_skcipher *tfm = luacrypto_skcipher_check(L, 1);\n\tsize_t keylen;\n\tconst char *key = luaL_checklstring(L, 2, &keylen);\n\tlunatik_try(L, crypto_skcipher_setkey, tfm, key, keylen);\n\treturn 0;\n}\n\n/***\n* Returns the required IV size in bytes.\n* @function ivsize\n* @treturn integer\n*/\nstatic int luacrypto_skcipher_ivsize(lua_State *L)\n{\n\tstruct crypto_skcipher *tfm = luacrypto_skcipher_check(L, 1);\n\tlua_pushinteger(L, crypto_skcipher_ivsize(tfm));\n\treturn 1;\n}\n\n/***\n* Returns the cipher block size in bytes.\n* @function blocksize\n* @treturn integer\n*/\nstatic int luacrypto_skcipher_blocksize(lua_State *L)\n{\n\tstruct crypto_skcipher *tfm = luacrypto_skcipher_check(L, 1);\n\tlua_pushinteger(L, crypto_skcipher_blocksize(tfm));\n\treturn 1;\n}\n\ntypedef struct luacrypto_skcipher_request_s {\n\tstruct scatterlist sg;\n\tstruct skcipher_request *skcipher;\n\tconst char *data;\n\tsize_t data_len;\n\tu8 *iv;\n} luacrypto_skcipher_request_t;\n\nstatic inline void luacrypto_skcipher_newrequest(lua_State *L, luacrypto_skcipher_request_t *request)\n{\n\tmemset(request, 0, sizeof(luacrypto_skcipher_request_t));\n\tstruct crypto_skcipher *tfm = luacrypto_skcipher_check(L, 1);\n\n\tsize_t iv_len;\n\tconst char *iv = luaL_checklstring(L, 2, &iv_len);\n\tif (iv_len != crypto_skcipher_ivsize(tfm))\n\t\tlunatik_throw(L, -EINVAL);\n\n\trequest->data = luaL_checklstring(L, 3, &request->data_len);\n\n\trequest->iv = (u8 *)lunatik_checkalloc(L, iv_len);\n\tmemcpy(request->iv, iv, iv_len);\n\n\tgfp_t gfp = lunatik_gfp(lunatik_toruntime(L));\n\trequest->skcipher = skcipher_request_alloc(tfm, gfp);\n\tif (request->skcipher == NULL) {\n\t\tlunatik_free(request->iv);\n\t\tlunatik_enomem(L);\n\t}\n}\n\nstatic inline void luacrypto_skcipher_setrequest(luacrypto_skcipher_request_t *request, char *buffer)\n{\n\tstruct skcipher_request *skcipher = request->skcipher;\n\tstruct scatterlist *sg = &request->sg;\n\tsize_t data_len = request->data_len;\n\n\tmemcpy(buffer, request->data, data_len);\n\n\tsg_init_one(sg, buffer, data_len);\n\n\tskcipher_request_set_crypt(skcipher, sg, sg, data_len, request->iv);\n\tskcipher_request_set_callback(skcipher, 0, NULL, NULL);\n}\n\nstatic int luacrypto_skcipher_crypt(lua_State *L, int (*crypt)(struct skcipher_request *))\n{\n\tluacrypto_skcipher_request_t request;\n\tluacrypto_skcipher_newrequest(L, &request);\n\n\tchar *buffer = (char *)lunatik_malloc(L, request.data_len);\n\tif (buffer == NULL) {\n\t\tskcipher_request_free(request.skcipher);\n\t\tlunatik_free(request.iv);\n\t\tlunatik_enomem(L);\n\t}\n\n\tluacrypto_skcipher_setrequest(&request, buffer);\n\tint ret = crypt(request.skcipher);\n\tskcipher_request_free(request.skcipher);\n\tlunatik_free(request.iv);\n\tif (ret < 0) {\n\t\tlunatik_free(buffer);\n\t\tlunatik_throw(L, ret);\n\t}\n\n\tlunatik_pushstring(L, buffer, request.data_len);\n\treturn 1;\n}\n\n/***\n* Encrypts data. IV length must match `ivsize()`.\n* @function encrypt\n* @tparam string iv initialization vector\n* @tparam string data plaintext\n* @treturn string ciphertext (same length as input)\n* @raise on encryption failure or incorrect IV length\n*/\nstatic int luacrypto_skcipher_encrypt(lua_State *L)\n{\n\treturn luacrypto_skcipher_crypt(L, crypto_skcipher_encrypt);\n}\n\n/***\n* Decrypts data. IV length must match `ivsize()`.\n* @function decrypt\n* @tparam string iv initialization vector\n* @tparam string data ciphertext\n* @treturn string plaintext (same length as input)\n* @raise on decryption failure or incorrect IV length\n*/\nstatic int luacrypto_skcipher_decrypt(lua_State *L)\n{\n\treturn luacrypto_skcipher_crypt(L, crypto_skcipher_decrypt);\n}\n\nstatic int luacrypto_skcipher_algname(lua_State *L)\n{\n\tstruct crypto_skcipher *tfm = luacrypto_skcipher_check(L, 1);\n\tlua_pushstring(L, crypto_tfm_alg_name(crypto_skcipher_tfm(tfm)));\n\treturn 1;\n}\n\nstatic const luaL_Reg luacrypto_skcipher_mt[] = {\n\t{\"algname\", luacrypto_skcipher_algname},\n\t{\"setkey\", luacrypto_skcipher_setkey},\n\t{\"ivsize\", luacrypto_skcipher_ivsize},\n\t{\"blocksize\", luacrypto_skcipher_blocksize},\n\t{\"encrypt\", luacrypto_skcipher_encrypt},\n\t{\"decrypt\", luacrypto_skcipher_decrypt},\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"__close\", lunatik_closeobject},\n\t{NULL, NULL}\n};\n\nconst lunatik_class_t luacrypto_skcipher_class = {\n\t.name = \"crypto_skcipher\",\n\t.methods = luacrypto_skcipher_mt,\n\t.release = luacrypto_skcipher_release,\n\t.opt = LUNATIK_OPT_MONITOR | LUNATIK_OPT_EXTERNAL,\n};\n\n/***\n* Creates a new SKCIPHER transform object.\n* @function new\n* @tparam string algname algorithm name (e.g., \"cbc(aes)\", \"ctr(aes)\")\n* @treturn crypto_skcipher\n* @raise on allocation failure\n* @usage\n*   local skcipher = require(\"crypto\").skcipher\n*   local cipher = skcipher(\"cbc(aes)\")\n*/\nLUACRYPTO_NEW(skcipher, struct crypto_skcipher, crypto_alloc_skcipher, luacrypto_skcipher_class);\n\n"
  },
  {
    "path": "lib/luadarken.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Encrypted Lua script execution using AES-256-CTR.\n*\n* This module provides functionality to decrypt and execute Lua scripts\n* encrypted with AES-256 in CTR mode. Scripts are decrypted in-kernel\n* and executed immediately, with the decrypted plaintext never written\n* to persistent storage.\n*\n* @module darken\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n\n#include <crypto/skcipher.h>\n#include <linux/scatterlist.h>\n\n#include <lunatik.h>\n\n#define LUADARKEN_KEYLEN\t32\n#define LUADARKEN_IVLEN\t\t16\n#define LUADARKEN_ALG\t\t\"ctr(aes)\"\n\ntypedef struct luadarken_request_s {\n\tstruct crypto_skcipher *tfm;\n\tstruct skcipher_request *req;\n\tu8 *iv;\n} luadarken_request_t;\n\nstatic void luadarken_freerequest(luadarken_request_t *r)\n{\n\tkfree(r->iv);\n\tif (r->req)\n\t\tskcipher_request_free(r->req);\n\tif (r->tfm)\n\t\tcrypto_free_skcipher(r->tfm);\n}\n\nstatic struct crypto_skcipher *luadarken_setkey(lua_State *L, const char *key)\n{\n\tstruct crypto_skcipher *tfm = crypto_alloc_skcipher(LUADARKEN_ALG, 0, 0);\n\tif (IS_ERR(tfm))\n\t\tlunatik_throw(L, PTR_ERR(tfm));\n\n\tint ret = crypto_skcipher_setkey(tfm, key, LUADARKEN_KEYLEN);\n\tif (ret < 0) {\n\t\tcrypto_free_skcipher(tfm);\n\t\tlunatik_throw(L, ret);\n\t}\n\treturn tfm;\n}\n\nstatic char *luadarken_setrequest(lua_State *L, luadarken_request_t *r,\n\tconst char *ct, size_t ct_len, const char *iv, const char *key)\n{\n\tr->tfm = luadarken_setkey(L, key);\n\n\tgfp_t gfp = lunatik_gfp(lunatik_toruntime(L));\n\n\tr->req = skcipher_request_alloc(r->tfm, gfp);\n\tif (r->req == NULL)\n\t\tgoto err;\n\n\tr->iv = kmemdup(iv, LUADARKEN_IVLEN, gfp);\n\tif (r->iv == NULL)\n\t\tgoto err;\n\n\tchar *buf = kmemdup(ct, ct_len, gfp);\n\tif (buf == NULL)\n\t\tgoto err;\n\n\tstruct scatterlist sg;\n\tsg_init_one(&sg, buf, ct_len);\n\tskcipher_request_set_crypt(r->req, &sg, &sg, ct_len, r->iv);\n\tskcipher_request_set_callback(r->req, 0, NULL, NULL);\n\n\treturn buf;\nerr:\n\tluadarken_freerequest(r);\n\tlunatik_enomem(L);\n\treturn NULL; /* unreachable */\n}\n\nstatic void luadarken_decrypt(lua_State *L, luadarken_request_t *r)\n{\n\tint ret = crypto_skcipher_decrypt(r->req);\n\tluadarken_freerequest(r);\n\n\tif (ret < 0)\n\t\tlunatik_throw(L, ret);\n}\n\n/***\n* Decrypts and executes an encrypted Lua script.\n* @function run\n* @tparam string ciphertext encrypted Lua script (binary).\n* @tparam string iv 16-byte initialization vector (binary).\n* @tparam string key 32-byte AES-256 key (binary).\n* @return The return values from the executed script.\n* @raise Error if decryption fails or IV/key length is invalid.\n*/\nstatic int luadarken_run(lua_State *L)\n{\n\tsize_t ct_len, iv_len, key_len;\n\tconst char *ct = luaL_checklstring(L, 1, &ct_len);\n\tconst char *iv = luaL_checklstring(L, 2, &iv_len);\n\tconst char *key = luaL_checklstring(L, 3, &key_len);\n\n\tluaL_argcheck(L, iv_len == LUADARKEN_IVLEN, 2, \"IV must be 16 bytes\");\n\tluaL_argcheck(L, key_len == LUADARKEN_KEYLEN, 3, \"key must be 32 bytes\");\n\n\tluadarken_request_t r = {0};\n\tchar *buf = luadarken_setrequest(L, &r, ct, ct_len, iv, key);\n\tluadarken_decrypt(L, &r);\n\n\tint ret = luaL_loadbufferx(L, buf, ct_len, \"=darken\", \"t\");\n\tkfree(buf);\n\n\tif (ret != LUA_OK)\n\t\tlua_error(L);\n\n\tint base = lua_gettop(L);\n\tlua_call(L, 0, LUA_MULTRET);\n\treturn lua_gettop(L) - base + 1;\n}\n\nstatic const luaL_Reg luadarken_lib[] = {\n\t{\"run\", luadarken_run},\n\t{NULL, NULL}\n};\n\nLUNATIK_NEWLIB(darken, luadarken_lib, NULL, NULL);\n\nstatic int __init luadarken_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luadarken_exit(void)\n{\n}\n\nmodule_init(luadarken_init);\nmodule_exit(luadarken_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ringzero.com.br>\");\nMODULE_DESCRIPTION(\"Lunatik darken — AES-256-CTR script decryption\");\n\n"
  },
  {
    "path": "lib/luadata.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Direct memory access and manipulation.\n* @module data\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/bitops.h>\n#include <net/checksum.h>\n\n#include <lunatik.h>\n\n#include \"luadata.h\"\n\n\ntypedef struct luadata_s {\n\tvoid *ptr;\n\tsize_t size;\n\tuint8_t opt;\n} luadata_t;\n\n#define LUADATA_NUMBER_SZ\t(sizeof(lua_Integer))\n\nstatic int luadata_lnew(lua_State *L);\n\nLUNATIK_PRIVATECHECKER(luadata_check, luadata_t *);\n\nstatic inline void *luadata_checkbounds(lua_State *L, int ix, luadata_t *data, lua_Integer offset, lua_Integer length)\n{\n\tint bounds = offset >= 0 && length > 0 && offset + length <= data->size;\n\tluaL_argcheck(L, bounds, ix, \"out of bounds\");\n\treturn (data->ptr + offset);\n}\n\n#define luadata_checkwritable(L, data)\tluaL_argcheck((L), !((data)->opt & LUADATA_OPT_READONLY), 1, \"read only\")\n\n#define LUADATA_NEWINT_GETTER(T)\t\t\t\t\t\t\t\\\nstatic int luadata_get##T(lua_State *L) \t\t\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\t\t\\\n\tluadata_t *data = luadata_check(L, 1);\t\t\t\t\t\t\\\n\tlua_Integer offset = luaL_checkinteger(L, 2);\t\t\t\t\t\\\n\tT##_t value = *(T##_t *)luadata_checkbounds(L, 2, data, offset, sizeof(T##_t));\t\\\n\tlua_pushinteger(L, (lua_Integer)value);\t\t\t\t\t\t\\\n\treturn 1;\t\t\t\t\t\t\t\t\t\\\n}\n\n#define LUADATA_NEWINT_SETTER(T)\t\t\t\t\t\t\\\nstatic int luadata_set##T(lua_State *L)\t\t\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\t\\\n\tluadata_t *data = luadata_check(L, 1);\t\t\t\t\t\\\n\tlua_Integer offset = luaL_checkinteger(L, 2);\t\t\t\t\\\n\tT##_t *ptr = luadata_checkbounds(L, 2, data, offset, sizeof(T##_t));\t\\\n\tluadata_checkwritable(L, data);\t\t\t\t\t\t\\\n\t*ptr = (T##_t)luaL_checkinteger(L, 3);\t\t\t\t\t\\\n\treturn 0;\t\t\t\t\t\t\t\t\\\n}\n\n#define LUADATA_NEWINT(T)\t\t\\\n\tLUADATA_NEWINT_GETTER(T); \t\\\n\tLUADATA_NEWINT_SETTER(T);\n\nLUADATA_NEWINT(int8);\nLUADATA_NEWINT(uint8);\nLUADATA_NEWINT(int16);\nLUADATA_NEWINT(uint16);\nLUADATA_NEWINT(int32);\nLUADATA_NEWINT(uint32);\nLUADATA_NEWINT(int64);\n\n/***\n* @function getstring\n* @tparam integer offset\n* @tparam[opt] integer length number of bytes; default: from offset to end\n* @treturn string\n* @raise if out of bounds\n*/\nstatic int luadata_getstring(lua_State *L)\n{\n\tluadata_t *data = luadata_check(L, 1);\n\tlua_Integer offset = luaL_checkinteger(L, 2);\n\tlua_Integer length = luaL_optinteger(L, 3, data->size - offset);\n\tchar *str = (char *)luadata_checkbounds(L, 2, data, offset, length);\n\n\tlua_pushlstring(L, str, length);\n\treturn 1;\n}\n\n/***\n* @function setstring\n* @tparam integer offset\n* @tparam string s\n* @raise if out of bounds or read-only\n*/\nstatic int luadata_setstring(lua_State *L)\n{\n\tsize_t length;\n\tluadata_t *data = luadata_check(L, 1);\n\tlua_Integer offset = luaL_checkinteger(L, 2);\n\tconst char *str = luaL_checklstring(L, 3, &length);\n\tvoid *ptr = luadata_checkbounds(L, 2, data, offset, length);\n\n\tluadata_checkwritable(L, data);\n\tmemcpy(ptr, str, length);\n\treturn 0;\n}\n\n/***\n* @function checksum\n* @tparam[opt] integer offset\n* @tparam[opt] integer length\n* @treturn integer\n* @raise if out of bounds\n*/\nstatic int luadata_checksum(lua_State *L)\n{\n\tluadata_t *data = luadata_check(L, 1);\n\tlua_Integer offset = luaL_optinteger(L, 2, 0);\n\tlua_Integer length = luaL_optinteger(L, 3, data->size - offset);\n\tvoid *value = (void*)luadata_checkbounds(L, 2, data, offset, length);\n\n\t__wsum sum = csum_partial(value, length, 0);\n\tlua_pushinteger(L, csum_fold(sum));\n\treturn 1;\n}\n\n/***\n* @function resize\n* @tparam integer new_size\n* @raise if read-only or not owned\n*/\nstatic int luadata_resize(lua_State *L)\n{\n\tluadata_t *data = luadata_check(L, 1);\n\tsize_t new_size = (size_t)luaL_checkinteger(L, 2);\n\n\tluadata_checkwritable(L, data);\n\n\tif (data->opt & LUADATA_OPT_FREE)\n\t\tdata->ptr = lunatik_checknull(L, lunatik_realloc(L, data->ptr, new_size));\n\telse\n\t\tluaL_error(L, \"cannot resize external memory\");\n\n\tdata->size = new_size;\n\treturn 0;\n}\n\n/***\n* @function __len\n* @treturn integer data size in bytes\n*/\nstatic int luadata_length(lua_State *L)\n{\n\tluadata_t *data = luadata_check(L, 1);\n\tlua_pushinteger(L, (lua_Integer)data->size);\n\treturn 1;\n}\n\n/***\n* @function __tostring\n* @treturn string\n*/\nstatic int luadata_tostring(lua_State *L)\n{\n\tluadata_t *data = luadata_check(L, 1);\n\tlua_pushlstring(L, (char *)data->ptr, data->size);\n\treturn 1;\n}\n\nstatic void luadata_release(void *private)\n{\n\tluadata_t *data = (luadata_t *)private;\n\tif (data->opt & LUADATA_OPT_FREE)\n\t\tlunatik_free(data->ptr);\n}\n\n/***\n* @function new\n* @tparam integer size\n* @treturn data\n* @raise if allocation fails\n*/\nstatic const luaL_Reg luadata_lib[] = {\n\t{\"new\", luadata_lnew},\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg luadata_mt[] = {\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"__len\", luadata_length},\n\t{\"__tostring\", luadata_tostring},\n/***\n* @function getbyte\n* @tparam integer offset\n* @treturn integer\n* @raise if out of bounds\n*/\n\t{\"getbyte\", luadata_getuint8},\n/***\n* @function setbyte\n* @tparam integer offset\n* @tparam integer value\n* @raise if out of bounds or read-only\n*/\n\t{\"setbyte\", luadata_setuint8},\n/***\n* @function getint8\n* @tparam integer offset\n* @treturn integer\n* @raise if out of bounds\n*/\n\t{\"getint8\", luadata_getint8},\n/***\n* @function setint8\n* @tparam integer offset\n* @tparam integer value\n* @raise if out of bounds or read-only\n*/\n\t{\"setint8\", luadata_setint8},\n/***\n* @function getuint8\n* @tparam integer offset\n* @treturn integer\n* @raise if out of bounds\n*/\n\t{\"getuint8\", luadata_getuint8},\n/***\n* @function setuint8\n* @tparam integer offset\n* @tparam integer value\n* @raise if out of bounds or read-only\n*/\n\t{\"setuint8\", luadata_setuint8},\n/***\n* @function getint16\n* @tparam integer offset\n* @treturn integer\n* @raise if out of bounds\n*/\n\t{\"getint16\", luadata_getint16},\n/***\n* @function setint16\n* @tparam integer offset\n* @tparam integer value\n* @raise if out of bounds or read-only\n*/\n\t{\"setint16\", luadata_setint16},\n/***\n* @function getuint16\n* @tparam integer offset\n* @treturn integer\n* @raise if out of bounds\n*/\n\t{\"getuint16\", luadata_getuint16},\n/***\n* @function setuint16\n* @tparam integer offset\n* @tparam integer value\n* @raise if out of bounds or read-only\n*/\n\t{\"setuint16\", luadata_setuint16},\n/***\n* @function getint32\n* @tparam integer offset\n* @treturn integer\n* @raise if out of bounds\n*/\n\t{\"getint32\", luadata_getint32},\n/***\n* @function setint32\n* @tparam integer offset\n* @tparam integer value\n* @raise if out of bounds or read-only\n*/\n\t{\"setint32\", luadata_setint32},\n/***\n* @function getuint32\n* @tparam integer offset\n* @treturn integer\n* @raise if out of bounds\n*/\n\t{\"getuint32\", luadata_getuint32},\n/***\n* @function setuint32\n* @tparam integer offset\n* @tparam integer value\n* @raise if out of bounds or read-only\n*/\n\t{\"setuint32\", luadata_setuint32},\n/***\n* @function getint64\n* @tparam integer offset\n* @treturn integer\n* @raise if out of bounds\n*/\n\t{\"getint64\", luadata_getint64},\n/***\n* @function setint64\n* @tparam integer offset\n* @tparam integer value\n* @raise if out of bounds or read-only\n*/\n\t{\"setint64\", luadata_setint64},\n/***\n* @function getnumber\n* @tparam integer offset\n* @treturn integer\n* @raise if out of bounds\n*/\n\t{\"getnumber\", luadata_getint64},\n/***\n* @function setnumber\n* @tparam integer offset\n* @tparam integer value\n* @raise if out of bounds or read-only\n*/\n\t{\"setnumber\", luadata_setint64},\n\t{\"getstring\", luadata_getstring},\n\t{\"setstring\", luadata_setstring},\n\t{\"resize\", luadata_resize},\n\t{\"checksum\", luadata_checksum},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luadata_class = {\n\t.name = \"data\",\n\t.methods = luadata_mt,\n\t.release = luadata_release,\n\t.opt = LUNATIK_OPT_SOFTIRQ | LUNATIK_OPT_MONITOR,\n};\n\nstatic inline void luadata_set(luadata_t *data, void *ptr, size_t size, uint8_t opt)\n{\n\tdata->ptr = ptr;\n\tdata->size = size;\n\tdata->opt = opt;\n}\n\nstatic int luadata_lnew(lua_State *L)\n{\n\tsize_t size = (size_t)luaL_checkinteger(L, 1);\n\tstatic const char *const modes[] = {\"shared\", \"single\", NULL};\n\tint mode = luaL_checkoption(L, 2, \"shared\", modes);\n\tlunatik_opt_t opt = mode == 1 ? LUNATIK_OPT_SINGLE : LUNATIK_OPT_MONITOR;\n\tlunatik_object_t *object = lunatik_newobject(L, &luadata_class, sizeof(luadata_t), opt);\n\tluadata_t *data = (luadata_t *)object->private;\n\n\tluadata_set(data, lunatik_checkalloc(L, size), size, LUADATA_OPT_FREE);\n\treturn 1; /* object */\n}\n\nLUNATIK_CLASSES(data, &luadata_class);\nLUNATIK_NEWLIB(data, luadata_lib, luadata_classes, NULL);\n\nlunatik_object_t *luadata_new(lua_State *L, lunatik_opt_t opt)\n{\n\tlunatik_require(L, \"data\");\n\tlunatik_object_t *object = lunatik_newobject(L, &luadata_class, sizeof(luadata_t), opt);\n\treturn object;\n}\nEXPORT_SYMBOL(luadata_new);\n\nint luadata_reset(lunatik_object_t *object, void *ptr, size_t size, uint8_t opt)\n{\n\tluadata_t *data;\n\n\tlunatik_lock(object);\n\tdata = (luadata_t *)object->private;\n\n\tif (data->opt & LUADATA_OPT_FREE) {\n\t\tlunatik_unlock(object);\n\t\treturn -1;\n\t}\n\n\topt = opt & LUADATA_OPT_KEEP ? data->opt : opt;\n\tluadata_set(data, ptr, size, opt);\n\n\tlunatik_unlock(object);\n\treturn 0;\n}\nEXPORT_SYMBOL(luadata_reset);\n\nstatic int __init luadata_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luadata_exit(void)\n{\n}\n\nmodule_init(luadata_init);\nmodule_exit(luadata_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ringzero.com.br>\");\n\n"
  },
  {
    "path": "lib/luadata.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2024-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef luadata_h\n#define luadata_h\n\n#include <lunatik.h>\n\n#define\tLUADATA_OPT_NONE\t0x00\n#define\tLUADATA_OPT_READONLY\t0x01\n#define\tLUADATA_OPT_FREE\t0x02\n#define\tLUADATA_OPT_KEEP  \t0x80\n\n#define luadata_clear(o)\t(luadata_reset((o), NULL, 0, LUADATA_OPT_KEEP))\n\nlunatik_object_t *luadata_new(lua_State *L, lunatik_opt_t opt);\nint luadata_reset(lunatik_object_t *object, void *ptr, size_t size, uint8_t opt);\n\nstatic inline void luadata_close(lunatik_object_t *object)\n{\n\tluadata_clear(object);\n\tlunatik_putobject(object);\n}\n\n#define luadata_attach(L, obj, field, opt)\tlunatik_attach(L, obj, field, luadata_new, opt)\n\n#endif\n\n"
  },
  {
    "path": "lib/luadevice.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2025 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Low-level Lua interface for creating Linux character device drivers.\n*\n* This module allows Lua scripts to implement character device drivers\n* by providing callback functions for standard file operations like\n* `open`, `read`, `write`, and `release`.\n*\n* @module device\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/fs.h>\n#include <linux/printk.h>\n#include <linux/string.h>\n#include <linux/device.h>\n#include <linux/cdev.h>\n#include <linux/uaccess.h>\n#include <linux/list.h>\n#include <linux/mutex.h>\n#include <linux/kref.h>\n\n#include <lunatik.h>\n\nstatic struct class *luadevice_devclass;\n\n/***\n* Represents a character device implemented in Lua.\n* This is a userdata object returned by `device.new()`. It encapsulates\n* the necessary kernel structures (`struct cdev`, `dev_t`) to manage a\n* character device, linking file operations to Lua callback functions.\n* @type device\n*/\ntypedef struct luadevice_s {\n\tstruct list_head entry;\n\tlunatik_object_t *runtime;\n\tstruct cdev *cdev;\n\tdev_t devt;\n} luadevice_t;\n\n\n\nstatic DEFINE_MUTEX(luadevice_mutex);\nstatic LIST_HEAD(luadevice_list);\n\n#define luadevice_lock()\t\tmutex_lock(&luadevice_mutex)\n#define luadevice_unlock()\t\tmutex_unlock(&luadevice_mutex)\n#define luadevice_foreach(luadev)\tlist_for_each_entry((luadev), &luadevice_list, entry)\n\nstatic inline void luadevice_listadd(luadevice_t *luadev)\n{\n\tluadevice_lock();\n\tlist_add_tail(&luadev->entry, &luadevice_list);\n\tluadevice_unlock();\n}\n\nstatic inline void luadevice_listdel(luadevice_t *luadev)\n{\n\tluadevice_lock();\n\tlist_del(&luadev->entry);\n\tluadevice_unlock();\n}\n\nstatic inline luadevice_t *luadevice_find(dev_t devt)\n{\n\tluadevice_t *luadev = NULL;\n\tluadevice_lock();\n\tluadevice_foreach(luadev) {\n\t\tif (luadev->devt == devt)\n\t\t\tbreak;\n\t}\n\tluadevice_unlock();\n\treturn luadev;\n}\n\nstatic int luadevice_new(lua_State *L);\n\nstatic int luadevice_fop(lua_State *L, luadevice_t *luadev, const char *fop, int nargs, int nresults)\n{\n\tint base = lua_gettop(L) - nargs;\n\tint ret = -ENXIO;\n\n\tif (lunatik_getregistry(L, luadev) != LUA_TTABLE) {\n\t\tpr_err(\"%s: couldn't find driver\\n\", fop);\n\t\tgoto err;\n\t}\n\n\tlunatik_optcfunction(L, -1, fop, lunatik_nop);\n\n\tlua_insert(L, base + 1); /* fop */\n\tlua_insert(L, base + 2); /* driver */\n\n\tif (lua_pcall(L, nargs + 1, nresults, 0) != LUA_OK) { /* fop(driver, arg1, ...) */\n\t\tpr_err(\"%s: %s\\n\", lua_tostring(L, -1), fop);\n\t\tret = -ECANCELED;\n\t\tgoto err;\n\t}\n\treturn 0;\nerr:\n\tlua_settop(L, base); /* pop everything, including args */\n\treturn ret;\n}\n\nstatic int luadevice_doopen(lua_State *L, luadevice_t *luadev)\n{\n\treturn luadevice_fop(L, luadev, \"open\", 0, 0);\n}\n\nstatic ssize_t luadevice_doread(lua_State *L, luadevice_t *luadev, char *buf, size_t len, loff_t *off)\n{\n\tssize_t ret;\n\tsize_t llen;\n\tconst char *lbuf;\n\n\tlua_pushinteger(L, len);\n\tlua_pushinteger(L, *off);\n\tif ((ret = luadevice_fop(L, luadev, \"read\", 2, 2)) != 0)\n\t\treturn ret;\n\n\tlbuf = lua_tolstring(L, -2, &llen);\n\tllen = min(len, llen);\n\tif (copy_to_user(buf, lbuf, llen) != 0)\n\t\treturn -EFAULT;\n\n\t*off = (loff_t)luaL_optinteger(L, -1, *off + llen);\n\treturn (ssize_t)llen;\n}\n\nstatic ssize_t luadevice_dowrite(lua_State *L, luadevice_t *luadev, const char *buf, size_t len, loff_t *off)\n{\n\tssize_t ret;\n\tluaL_Buffer B;\n\tsize_t llen;\n\tchar *lbuf;\n\n\tlbuf = luaL_buffinitsize(L, &B, len);\n\n\tif (copy_from_user(lbuf, buf, len) != 0) {\n\t\tluaL_pushresultsize(&B, 0);\n\t\treturn -EFAULT;\n\t}\n\n\tluaL_pushresultsize(&B, len);\n\tlua_pushinteger(L, *off);\n\tif ((ret = luadevice_fop(L, luadev, \"write\", 2, 2)) != 0)\n\t\treturn ret;\n\n\tllen = (size_t)luaL_optinteger(L, -2, len);\n\tllen = min(len, llen);\n\t*off = (loff_t)luaL_optinteger(L, -1, *off + llen);\n\treturn (ssize_t)llen;\n}\n\nstatic int luadevice_dorelease(lua_State *L, luadevice_t *luadev)\n{\n\treturn luadevice_fop(L, luadev, \"release\", 0, 0);\n}\n\n#define luadevice_fromfile(f)\t((luadevice_t *)(f)->private_data)\n#define luadevice_run(handler, ret, f, ...)\t\t\t\t\t\\\n\t\tlunatik_run(luadevice_fromfile(f)->runtime, (handler),\t\\\n\t\t\t(ret), luadevice_fromfile(f), ## __VA_ARGS__)\n\nstatic int luadevice_fop_open(struct inode *inode, struct file *f)\n{\n\tluadevice_t *luadev;\n\tint ret;\n\n\tif ((luadev = luadevice_find(inode->i_rdev)) == NULL)\n\t\treturn -ENXIO;\n\n\tlunatik_getobject(luadev->runtime);\n\tf->private_data = luadev;\n\tluadevice_run(luadevice_doopen, ret, f);\n\treturn ret;\n}\n\nstatic ssize_t luadevice_fop_read(struct file *f, char *buf, size_t len, loff_t *off)\n{\n\tssize_t ret;\n\tluadevice_run(luadevice_doread, ret, f, buf, len, off);\n\treturn ret;\n}\n\nstatic ssize_t luadevice_fop_write(struct file *f, const char *buf, size_t len, loff_t* off)\n{\n\tssize_t ret;\n\tluadevice_run(luadevice_dowrite, ret, f, buf, len, off);\n\treturn ret;\n}\n\nstatic int luadevice_fop_release(struct inode *inode, struct file *f)\n{\n\tint ret;\n\n\tluadevice_run(luadevice_dorelease, ret, f);\n\treturn ret;\n}\n\nstatic struct file_operations luadevice_fops =\n{\n\t.owner = THIS_MODULE,\n\t.open = luadevice_fop_open,\n\t.read = luadevice_fop_read,\n\t.write = luadevice_fop_write,\n\t.release = luadevice_fop_release\n};\n\nstatic void luadevice_delete(luadevice_t *luadev)\n{\n\tif (luadev->cdev != NULL) {\n\t\tcdev_del(luadev->cdev);\n\t\tluadev->cdev = NULL;\n\t}\n\n\tif (luadev->devt != 0) {\n\t\tluadevice_listdel(luadev);\n\t\tdevice_destroy(luadevice_devclass, luadev->devt);\n\t\tunregister_chrdev_region(luadev->devt, 1);\n\t\tluadev->devt = 0;\n\t}\n}\n\nstatic void luadevice_release(void *private)\n{\n\tluadevice_t *luadev = (luadevice_t *)private;\n\n\t/* device might have never been stopped */\n\tluadevice_delete(luadev);\n\tlunatik_putobject(luadev->runtime);\n}\n\n/***\n* Stops and releases a character device driver from the system.\n* This method is called on a device object returned by `device.new()`.\n* Once stopped, the device file (`/dev/<name>`) will be removed and\n* the associated resources released.\n*\n* This method provides an explicit way to release the device. The device\n* is also released when `close` is called (e.g., via Lua 5.4's to-be-closed\n* mechanism if `__close` is defined and triggered for the object, which typically\n* calls this same underlying function) or eventually when the device object is\n* garbage collected (via `__gc`). The `stop` and `close` methods offer more\n* deterministic cleanup than relying solely on garbage collection.\n* @function stop\n* @treturn nil Does not return any value to Lua.\n* @raise Error May raise an error if the underlying C function encounters a critical issue during cleanup and calls `lua_error`.\n* @usage\n*   -- Assuming 'dev' is a device object:\n*   dev:stop()\n* @see device.new\n*/\nstatic int luadevice_stop(lua_State *L)\n{\n\tlunatik_object_t *object = lunatik_checkobject(L, 1);\n\tluadevice_t *luadev = (luadevice_t *)object->private;\n\n\tlunatik_lock(object);\n\tluadevice_delete(luadev);\n\tlunatik_unlock(object);\n\n\tif (lunatik_toruntime(L) == luadev->runtime)\n\t\tlunatik_unregisterobject(L, object);\n\treturn 0;\n}\n\n/***\n* Creates and installs a new character device driver in the system.\n* This function binds a Lua table (the `driver` table) to a new\n* character device file (`/dev/<name>`), allowing Lua functions\n* to handle file operations on that device.\n*\n* @function new\n* @tparam table driver A table defining the device driver's properties and callbacks.\n*   It **must** contain the field:\n*\n*   - `name` (string): The name of the device. This name will be used to create\n*     the device file `/dev/<name>`.\n*\n*   It **might** optionally contain the following fields (callback functions):\n*\n*   - `open` (function): Callback for the `open(2)` system call.\n*     Signature: `function(driver_table)`. Expected to return nothing.\n*   - `read` (function): Callback for the `read(2)` system call.\n*     Signature: `function(driver_table, length, offset) -> string [, updated_offset]`.\n*     Receives the driver table, the requested read length (integer), and the current\n*     file offset (integer). Should return the data as a string and optionally the\n*     updated file offset (integer). If `updated_offset` is not returned, the offset\n*     is advanced by the length of the returned string (or the requested length if\n*     the string is longer).\n*   - `write` (function): Callback for the `write(2)` system call.\n*     Signature: `function(driver_table, buffer_string, offset) -> [written_length] [, updated_offset]`.\n*     Receives the driver table, the data to write as a string, and the current file\n*     offset (integer). May return the number of bytes successfully written (integer)\n*     and optionally the updated file offset (integer). If `written_length` is not\n*     returned, it's assumed all provided data was written. If `updated_offset` is\n*     not returned, the offset is advanced by the `written_length`.\n*   - `release` (function): Callback for the `release(2)` system call (called when the\n*     last file descriptor is closed). Signature: `function(driver_table)`.\n*     Expected to return nothing.\n*   - `mode` (integer): Optional file mode flags (e.g., permissions) for the device file.\n*     Use constants from the `linux.stat` table (e.g., `linux.stat.IRUGO`).\n* @treturn userdata A Lunatik object representing the newly created device.\n*   This object can be used to explicitly stop the device using the `:stop()` method.\n* @raise Error if the device cannot be allocated or registered in the kernel,\n*   or if the `name` field is missing or not a string.\n* @usage\n*   local device = require(\"device\")\n*   local linux = require(\"linux\")\n*\n*   local my_driver = {\n*     name = \"my_lua_device\",\n*     mode = linux.stat.IRUGO, -- Read-only for all\n*     read = function(drv, len, off)\n*       local data = \"Hello from \" .. drv.name .. \" at offset \" .. tostring(off) .. \"!\"\n*       return data:sub(1, len), off + #data\n*     end\n*   }\n*   local dev_obj = device.new(my_driver)\n*   -- To clean up: dev_obj:stop() or let it be garbage collected.\n* @see linux.stat\n*/\nstatic const luaL_Reg luadevice_lib[] = {\n\t{\"new\", luadevice_new},\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg luadevice_mt[] = {\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"stop\", luadevice_stop},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luadevice_class = {\n\t.name = \"device\",\n\t.methods = luadevice_mt,\n\t.release = luadevice_release,\n\t.opt = LUNATIK_OPT_SINGLE,\n};\n\nstatic int luadevice_new(lua_State *L)\n{\n\tlunatik_object_t *object;\n\tluadevice_t *luadev;\n\tstruct device *device;\n\tconst char *name;\n\tint ret;\n\n\tluaL_checktype(L, 1, LUA_TTABLE); /* driver */\n\n\tlunatik_checkfield(L, 1, \"name\", LUA_TSTRING);\n\tname = lua_tostring(L, -1);\n\n\tobject = lunatik_newobject(L, &luadevice_class, sizeof(luadevice_t), LUNATIK_OPT_NONE);\n\tluadev = (luadevice_t *)object->private;\n\n\tmemset(luadev, 0, sizeof(luadevice_t));\n\n\tlunatik_setruntime(L, device, luadev);\n\tlunatik_getobject(luadev->runtime);\n\n\tif ((ret = alloc_chrdev_region(&luadev->devt, 0, 1, name) != 0))\n\t\tluaL_error(L, \"failed to allocate char device region (%d)\", ret);\n\n\tif ((luadev->cdev = cdev_alloc()) == NULL)\n\t\tluaL_error(L, \"failed to allocate cdev\");\n\tluadev->cdev->ops = &luadevice_fops;\n\tif ((ret = cdev_add(luadev->cdev, luadev->devt, 1)) != 0)\n\t\tluaL_error(L, \"failed to add cdev (%d)\", ret);\n\n\tluadevice_listadd(luadev);\n\tlunatik_registerobject(L, 1, object); /* driver */\n\n\tdevice = device_create(luadevice_devclass, NULL, luadev->devt, luadev, name); /* calls devnode */\n\tif (IS_ERR(device)) {\n\t\tlunatik_unregisterobject(L, object);\n\t\tluaL_error(L, \"failed to create a new device (%d)\", PTR_ERR(device));\n\t}\n\tlua_remove(L, -2); /* remove name */\n\n\treturn 1; /* object */\n}\n\nLUNATIK_CLASSES(device, &luadevice_class);\nLUNATIK_NEWLIB(device, luadevice_lib, luadevice_classes, NULL);\n\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 2, 0)\nstatic char *luadevice_devnode(const struct device *dev, umode_t *mode)\n#else\nstatic char *luadevice_devnode(struct device *dev, umode_t *mode)\n#endif\n{\n\tlua_State *L;\n\tluadevice_t *luadev;\n\tint base;\n\n\tif (!mode)\n\t\tgoto out;\n\n\tluadev = (luadevice_t *)dev_get_drvdata(dev);\n\tL = lunatik_getstate(luadev->runtime);\n\tif (!L)\n\t\tgoto out;\n\n\tbase = lua_gettop(L);\n\tif (lunatik_getregistry(L, luadev) == LUA_TTABLE && lua_getfield(L, -1, \"mode\") == LUA_TNUMBER)\n\t\t*mode = (umode_t)lua_tointeger(L, -1);\n\tlua_settop(L, base);\nout:\n\treturn NULL;\n}\n\nstatic int __init luadevice_init(void)\n{\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)\n\tluadevice_devclass = class_create(\"luadevice\");\n#else\n\tluadevice_devclass = class_create(THIS_MODULE, \"luadevice\");\n#endif\n\tif (IS_ERR(luadevice_devclass)) {\n\t\tpr_err(\"failed to create luadevice class\\n\");\n\t\treturn PTR_ERR(luadevice_devclass);\n\t}\n\tluadevice_devclass->devnode = luadevice_devnode;\n\treturn 0;\n}\n\nstatic void __exit luadevice_exit(void)\n{\n\tclass_destroy(luadevice_devclass);\n}\n\nmodule_init(luadevice_init);\nmodule_exit(luadevice_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ring-0.io>\");\n\n"
  },
  {
    "path": "lib/luafib.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2025 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Forwarding Information Base (FIB) rules.\n* This library allows Lua scripts to add and delete FIB rules, similar to the\n* user-space `ip rule add` and `ip rule del` commands.\n* FIB rules are used to influence routing decisions by selecting different\n* routing tables based on various criteria.\n*\n* @module fib\n*/\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <net/fib_rules.h>\n\n#include <lunatik.h>\n\n#define luafib_nl_sizeof(t)\t(nla_total_size(sizeof(t)))\n\n#define LUAFIB_NL_SIZE\t(NLMSG_ALIGN(sizeof(struct fib_rule_hdr)) \t\\\n\t\t+ luafib_nl_sizeof(u8)\t\t/* FRA_PROTOCOL */\t\\\n\t\t+ luafib_nl_sizeof(u32))\t/* FRA_PRITORITY */\n\ntypedef struct luafib_rule_s {\n\tstruct net *net;\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0)\n\tint (*command)(struct net *, struct sk_buff *, struct nlmsghdr *, struct netlink_ext_ack *, bool);\n#else\n\tint (*command)(struct sk_buff *, struct nlmsghdr *, struct netlink_ext_ack *);\n#endif\n\tu32 table;\n\tu32 priority;\n} luafib_rule_t;\n\nstatic int luafib_nl_rule(luafib_rule_t *rule)\n{\n\tstruct fib_rule_hdr *frh;\n\tstruct nlmsghdr *nlh;\n\tstruct sk_buff *skb;\n\tint ret = -1;\n\n\tif (!(skb = nlmsg_new(LUAFIB_NL_SIZE, GFP_KERNEL)) ||\n\t    !(nlh = nlmsg_put(skb, 0, 0, 0, sizeof(*frh), 0)))\n\t\tgoto out;\n\n\tnlh->nlmsg_flags |= NLM_F_EXCL;\n\n\tfrh = nlmsg_data(nlh);\n\tmemset(frh, 0, sizeof(*frh));\n\tfrh->family = AF_INET;\n\tfrh->action = FR_ACT_TO_TBL;\n\tfrh->table = rule->table;\n\n\tif (nla_put_u8(skb, FRA_PROTOCOL, RTPROT_KERNEL) ||\n\t    nla_put_u32(skb, FRA_PRIORITY, rule->priority))\n\t\tgoto free;\n\tnlmsg_end(skb, nlh);\n\n\tskb->sk = rule->net->rtnl;\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0)\n\tret = rule->command(rule->net, skb, nlh, NULL, true);\n#else\n\tret = rule->command(skb, nlh, NULL);\n#endif\nfree:\n\tnlmsg_free(skb);\nout:\n\treturn ret;\n}\n\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0)\n#define LUAFIB_RULECOMMAND(op) fib_##op\n#else\n#define LUAFIB_RULECOMMAND(op) fib_nl_##op\n#endif\n\n#define LUAFIB_OPRULE(op) \t\t\t\t\\\nstatic int luafib_##op(lua_State *L)\t\t\t\\\n{\t\t\t\t\t\t\t\\\n\tluafib_rule_t rule;\t\t\t\t\\\n\t\t\t\t\t\t\t\\\n\trule.net = &init_net;\t\t\t\t\\\n\trule.command = LUAFIB_RULECOMMAND(op);\t\t\\\n\trule.table = (u32)luaL_checkinteger(L, 1);\t\\\n\trule.priority = (u32)luaL_checkinteger(L, 2);\t\\\n\t\t\t\t\t\t\t\\\n\tif (luafib_nl_rule(&rule) < 0)\t\t\t\\\n\t\tluaL_error(L, \"failed on \" #op);\t\\\n\t\t\t\t\t\t\t\\\n\treturn 0;\t\t\t\t\t\\\n}\n\n/***\n* Adds a new FIB rule.\n* This function binds the kernel `fib_nl_newrule` API. It creates a new FIB rule\n* that directs lookups to the specified routing `table` for packets matching\n* this rule's `priority`.\n*\n* Note: This function creates a relatively simple rule. The rule is always for\n* IPv4 (`AF_INET`), the action is always to look up the specified `table`\n* (`FR_ACT_TO_TBL`), and the protocol is set to `RTPROT_KERNEL`. It does not\n* support specifying other match conditions (e.g., source/destination IP, interface, fwmark).\n*\n* @function newrule\n* @tparam integer table routing table identifier (e.g., 254 for main, 255 for local).\n* @tparam integer priority rule priority. Lower numbers have higher precedence.\n* @treturn nil\n* @raise Error if the rule cannot be added (e.g., due to kernel error, invalid parameters).\n* @usage\n*   fib.newrule(100, 10000) -- Add a rule with priority 10000 to lookup table 100\n*/\nLUAFIB_OPRULE(newrule);\n\n/***\n* Deletes an existing FIB rule.\n* This function binds the kernel `fib_nl_delrule` API. It removes a FIB rule\n* that matches the specified routing `table` and `priority`.\n*\n* @function delrule\n* @tparam integer table routing table identifier of the rule to delete.\n* @tparam integer priority priority of the rule to delete.\n* @treturn nil\n* @raise Error if the rule cannot be deleted (e.g., rule not found, kernel error).\n* @usage\n*   fib.delrule(100, 10000) -- Delete the rule with priority 10000 that looks up table 100\n*/\nLUAFIB_OPRULE(delrule);\n\nstatic const luaL_Reg luafib_lib[] = {\n\t{\"newrule\", luafib_newrule},\n\t{\"delrule\", luafib_delrule},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luafib_class = {\n\t.opt = LUNATIK_OPT_NONE,\n};\n\nLUNATIK_CLASSES(fib, &luafib_class);\nLUNATIK_NEWLIB(fib, luafib_lib, luafib_classes, NULL);\n\nstatic int __init luafib_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luafib_exit(void)\n{\n}\n\nmodule_init(luafib_init);\nmodule_exit(luafib_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ring-0.io>\");\n\n"
  },
  {
    "path": "lib/luafifo.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* kfifo (kernel FIFO) implementation.\n* This library allows creating and managing fixed-size, lockless FIFO queues\n* for byte streams, suitable for producer-consumer scenarios within the kernel.\n*\n* @module fifo\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/kfifo.h>\n\n#include <lunatik.h>\n\nLUNATIK_PRIVATECHECKER(luafifo_check, struct kfifo *);\n\n/***\n* Pushes data into the FIFO.\n* Copies a string of bytes into the FIFO.\n* @function push\n* @tparam string data bytes to push into the FIFO.\n* @treturn nil\n* @raise Error if the provided data string is larger than the available space in the FIFO.\n* @usage\n*   -- Assuming 'myfifo' is a fifo object\n*   myfifo:push(\"hello\")\n* @see fifo.pop\n*/\nstatic int luafifo_push(lua_State *L)\n{\n\tstruct kfifo *fifo = luafifo_check(L, 1);\n\tsize_t size;\n\tconst char *buf = luaL_checklstring(L, 2, &size);\n\n\tluaL_argcheck(L, size <= kfifo_avail(fifo), 2, \"not enough space\");\n\tkfifo_in(fifo, buf, size);\n\treturn 0;\n}\n\n/***\n* Pops data from the FIFO.\n* Retrieves a specified number of bytes from the FIFO.\n* @function pop\n* @tparam integer size maximum number of bytes to retrieve from the FIFO.\n* @treturn string A string containing the bytes popped from the FIFO. The actual length of this string might be less than `size` if the FIFO contained fewer bytes.\n* @treturn integer actual number of bytes popped from the FIFO.\n* @usage\n*   -- Assuming 'myfifo' is a fifo object\n*   local data, len = myfifo:pop(10)\n*   if len > 0 then\n*     print(\"Popped \" .. len .. \" bytes: \" .. data)\n*   end\n* @see fifo.push\n*/\nstatic int luafifo_pop(lua_State *L)\n{\n\tstruct kfifo *fifo = luafifo_check(L, 1);\n\tsize_t size = luaL_checkinteger(L, 2);\n\tluaL_Buffer B;\n\tchar *lbuf = luaL_buffinitsize(L, &B, size);\n\n\tsize = kfifo_out(fifo, lbuf, size);\n\tluaL_pushresultsize(&B, size);\n\tlua_pushinteger(L, (lua_Integer)size);\n\treturn 2;\n}\n\nstatic void luafifo_release(void *private)\n{\n\tstruct kfifo *fifo = (struct kfifo *)private;\n\tkfifo_free(fifo);\n}\n\nstatic int luafifo_new(lua_State *L);\n\n/***\n* Represents a kernel FIFO (kfifo) object.\n* This is a userdata object returned by `fifo.new()`. It encapsulates\n* a `struct kfifo` from the Linux kernel, providing a first-in, first-out\n* byte queue.\n* @type fifo\n*/\n\n/***\n* Creates a new kernel FIFO (kfifo) object.\n* Allocates and initializes a kfifo of the specified size. The size should\n* ideally be a power of two for kfifo's internal optimizations, though kfifo\n* will handle non-power-of-two sizes by rounding up.\n* @function new\n* @tparam integer size desired capacity of the FIFO in bytes.\n* @treturn fifo A new fifo object.\n* @raise Error if kfifo allocation fails (e.g., due to insufficient memory).\n* @usage\n*   local myfifo = fifo.new(1024) -- Creates a FIFO with a capacity of 1024 bytes\n* @within fifo\n*/\nstatic const luaL_Reg luafifo_lib[] = {\n\t{\"new\", luafifo_new},\n\t{NULL, NULL}\n};\n\n/***\n* Closes and releases the FIFO object.\n* This is an alias for the `__close` and `__gc` metamethods.\n* @function close\n* @treturn nil\n*/\nstatic const luaL_Reg luafifo_mt[] = {\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"__close\", lunatik_closeobject},\n\t{\"close\", lunatik_closeobject},\n\t{\"push\", luafifo_push},\n\t{\"pop\", luafifo_pop},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luafifo_class = {\n\t.name = \"fifo\",\n\t.methods = luafifo_mt,\n\t.release = luafifo_release,\n\t.opt = LUNATIK_OPT_SOFTIRQ | LUNATIK_OPT_MONITOR,\n};\n\nstatic int luafifo_new(lua_State *L)\n{\n\tsize_t size = luaL_checkinteger(L, 1);\n\tlunatik_object_t *object = lunatik_newobject(L, &luafifo_class, sizeof(struct kfifo), LUNATIK_OPT_NONE);\n\tgfp_t gfp = lunatik_gfp(lunatik_toruntime(L));\n\tint ret;\n\n\tif ((ret = kfifo_alloc((struct kfifo *)object->private, size, gfp)) != 0)\n\t\tluaL_error(L, \"failed to allocate kfifo (%d)\", ret);\n\treturn 1; /* object */\n}\n\nLUNATIK_CLASSES(fifo, &luafifo_class);\nLUNATIK_NEWLIB(fifo, luafifo_lib, luafifo_classes, NULL);\n\nstatic int __init luafifo_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luafifo_exit(void)\n{\n}\n\nmodule_init(luafifo_init);\nmodule_exit(luafifo_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ring-0.io>\");\n\n"
  },
  {
    "path": "lib/luahid.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2025-2026 Jieming Zhou <qrsikno@gmail.com>\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Lua interface to the Linux HID subsystem.\n* @module hid\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/version.h>\n#include <linux/string.h>\n#include <linux/hid.h>\n\n#include <lunatik.h>\n\n#include \"luadata.h\"\n\n/***\n* Represents a registered HID driver.\n* @type hid_driver\n*/\ntypedef struct luahid_s {\n\tlunatik_object_t *runtime;\n\tlunatik_object_t *data;\n\tstruct hid_driver driver;\n\tbool registered;\n} luahid_t;\n\ntypedef struct luahid_ctx_s {\n\tconst char *cb;\n\tluahid_t *hid;\n\tconst struct hid_device *hdev;\n\tconst struct hid_report *report;\n\tconst struct hid_device_id *id;\n\tu8 *data;\n\tsize_t size;\n\tint ret;\n} luahid_ctx_t;\n\nstatic void luahid_release(void *private)\n{\n\tluahid_t *hid = (luahid_t *)private;\n\tif (hid->registered)\n\t\thid_unregister_driver(&hid->driver);\n\n\tlunatik_object_t *runtime = hid->runtime;\n\tif (runtime != NULL) {\n\t\tlunatik_detach(runtime, hid, data);\n\t\tlunatik_putobject(runtime);\n\t}\n\tif (hid->driver.id_table != NULL)\n\t\tlunatik_free(hid->driver.id_table);\n\tif (hid->driver.name != NULL)\n\t\tlunatik_free(hid->driver.name);\n}\n\nstatic int luahid_register(lua_State *L);\n\nstatic const luaL_Reg luahid_lib[] = {\n\t{\"register\", luahid_register},\n\t{NULL, NULL},\n};\n\nstatic const luaL_Reg luahid_mt[] = {\n\t{\"__gc\", lunatik_deleteobject},\n\t{NULL, NULL},\n};\n\nstatic const lunatik_class_t luahid_class = {\n\t.name = \"hid\",\n\t.methods = luahid_mt,\n\t.release = luahid_release,\n\t.opt = LUNATIK_OPT_SOFTIRQ | LUNATIK_OPT_SINGLE,\n};\n\nstatic const struct hid_device_id *luahid_setidtable(lua_State *L, int idx)\n{\n\tsize_t len = luaL_len(L, idx);\n\tstruct hid_device_id *user_table = lunatik_checkalloc(L, sizeof(struct hid_device_id) * (len + 1));\n\n\tstruct hid_device_id *cur_id = user_table;\n\tsize_t i;\n\tfor (i = 0; i < len; i++, cur_id++) {\n\t\tif (lua_geti(L, idx, i + 1) != LUA_TTABLE) { /* table entry */\n\t\t\tlua_pop(L, 1); /* table entry */\n\t\t\tlunatik_free(user_table);\n\t\t\tuser_table = NULL;\n\t\t\tgoto out;\n\t\t}\n\n\t\tlunatik_optinteger(L, -1, cur_id, bus, HID_BUS_ANY);\n\t\tlunatik_optinteger(L, -1, cur_id, group, HID_GROUP_ANY);\n\t\tlunatik_optinteger(L, -1, cur_id, vendor, HID_ANY_ID);\n\t\tlunatik_optinteger(L, -1, cur_id, product, HID_ANY_ID);\n\t\tlunatik_optinteger(L, -1, cur_id, driver_data, 0);\n\n\t\tlua_pop(L, 1); /* table entry */\n\t}\n\n\tmemset(cur_id, 0, sizeof(struct hid_device_id));\nout:\n\tlua_pop(L, 1); /* id_table */\n\treturn user_table;\n}\n\n#define luahid_setfield(L, idx, obj, field)\t\\\ndo {\t\t\t\t\t\t\\\n\tlua_pushinteger(L, obj->field);\t\t\\\n\tlua_setfield(L, idx - 1, #field);\t\\\n} while (0)\n\nstatic inline int luahid_pcall(lua_State *L, lua_CFunction op, luahid_ctx_t *ctx)\n{\n\tlua_pushcfunction(L, op);\n\tlua_pushlightuserdata(L, ctx);\n\n\tctx->ret = 0;\n\tif (lua_pcall(L, 1, 0, 0) != LUA_OK)\n\t\thid_err(ctx->hdev, \"%s: %s\\n\", ctx->cb, lua_tostring(L, -1));\n\treturn ctx->ret;\n}\n\n#define luahid_run(op, ctx, hid, hdev, ret)\t\t\t\t\t\\\ndo {\t\t\t\t\t\t\t\t\t\t\\\n\t(ctx)->cb = #op; (ctx)->hid = hid; (ctx)->hdev = hdev;\t\t\t\\\n\tlunatik_run(hid->runtime, luahid_pcall, ret, luahid_do##op, ctx);\t\\\n} while (0)\n\n#define luahid_pushid(L, id, extra)\t\t\\\ndo {\t\t\t\t\t\t\\\n\tlua_newtable(L); \t\t\t\\\n\tluahid_setfield(L, -1, id, bus); \t\\\n\tluahid_setfield(L, -1, id, group); \t\\\n\tluahid_setfield(L, -1, id, vendor); \t\\\n\tluahid_setfield(L, -1, id, product); \t\\\n\tluahid_setfield(L, -1, id, extra); \t\\\n} while (0)\n\nstatic inline void luahid_pushhdev(lua_State *L, const struct hid_device *hdev)\n{\n\tluahid_pushid(L, hdev, version);\n\tlua_pushstring(L, hdev->name);\n\tlua_setfield(L, -2, \"name\");\n}\n\nstatic inline void luahid_pushreport(lua_State *L, const struct hid_report *report)\n{\n\tlua_newtable(L);\n\tluahid_setfield(L, -1, report, id);\n\tluahid_setfield(L, -1, report, type);\n\tluahid_setfield(L, -1, report, size);\n\tluahid_setfield(L, -1, report, application);\n\tluahid_setfield(L, -1, report, maxfield);\n}\n\nstatic luahid_t *luahid_gethid(struct hid_device *hdev)\n{\n\tstruct hid_driver *driver = hdev->driver;\n\treturn container_of(driver, luahid_t, driver);\n}\n\n#define luahid_checkdriver(L, hid)\t(lunatik_getregistry(L, hid) != LUA_TTABLE)\n\nstatic inline lunatik_object_t *luahid_pushdata(lua_State *L, luahid_ctx_t *ctx)\n{\n\tlunatik_object_t *obj;\n\n\tif (lunatik_getregistry(L, ctx->hid->data) != LUA_TUSERDATA ||\n\t    unlikely((obj = lunatik_toobject(L, -1)) == NULL)) {\n\t\tctx->ret = -ENXIO;\n\t\tluaL_error(L, \"couldn't find data\");\n\t}\n\n\tluadata_reset(obj, ctx->data, ctx->size, LUADATA_OPT_NONE);\n\treturn obj;\n}\n\nstatic void luahid_op(lua_State *L, luahid_ctx_t *ctx, int nargs)\n{\n\tluahid_t *hid = ctx->hid;\n\tint base = lua_gettop(L) - nargs;\n\n\tif (luahid_checkdriver(L, hid)) { /* stack: args, hid */\n\t\tctx->ret = -ENXIO;\n\t\tluaL_error(L, \"couldn't find driver\");\n\t}\n\n\tlunatik_optcfunction(L, -1, ctx->cb, lunatik_nop); /* stack: args, hid, hid.cb */\n\n\tlua_insert(L, base + 1); /* hid.cb */\n\tlua_insert(L, base + 2); /* hid */\n\tlua_settop(L, base + 2 + nargs); /* stack: hid.cb, hid, args */\n\n\tif (lua_pcall(L, nargs + 1, 0, 0) != LUA_OK) { /* ops.cb(hid, args) */\n\t\tctx->ret = -ECANCELED;\n\t\tlua_error(L);\n\t}\n}\n\nstatic int luahid_doprobe(lua_State *L)\n{\n\tluahid_ctx_t *ctx = lua_touserdata(L, 1);\n\n\tluahid_pushid(L, ctx->id, driver_data);\n\tluahid_op(L, ctx, 1);\n\treturn 0;\n}\n\nstatic int luahid_probe(struct hid_device *hdev, const struct hid_device_id *id)\n{\n\tluahid_t *hid = luahid_gethid(hdev);\n\tluahid_ctx_t ctx = {.id = id};\n\tint ret;\n\n\tluahid_run(probe, &ctx, hid, hdev, ret);\n\tif (ret != 0 || (ret = hid_parse(hdev)) != 0)\n\t\treturn ret;\n\n\thid_set_drvdata(hdev, hid);\n\treturn hid_hw_start(hdev, HID_CONNECT_DEFAULT);\n}\n\nstatic int luahid_doreport_fixup(lua_State *L)\n{\n\tluahid_ctx_t *ctx = lua_touserdata(L, 1);\n\tconst struct hid_device *hdev = ctx->hdev;\n\n\tluahid_pushhdev(L, hdev);\n\tlunatik_object_t *data = luahid_pushdata(L, ctx);\n\tluahid_op(L, ctx, 2);\n\tluadata_clear(data);\n\treturn 0;\n}\n\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0))\ntypedef const __u8 * luahid_rdesc_t;\n#else\ntypedef __u8 * luahid_rdesc_t;\n#endif\n\nstatic luahid_rdesc_t luahid_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize)\n{\n\tluahid_t *hid = luahid_gethid(hdev);\n\tluahid_ctx_t ctx = {.data = rdesc, .size = (size_t)*rsize};\n\tint ret;\n\n\tluahid_run(report_fixup, &ctx, hid, hdev, ret);\n\treturn rdesc;\n}\n\nstatic int luahid_doraw_event(lua_State *L)\n{\n\tluahid_ctx_t *ctx = lua_touserdata(L, 1);\n\tconst struct hid_device *hdev = ctx->hdev;\n\n\tluahid_pushhdev(L, hdev);\n\tluahid_pushreport(L, ctx->report);\n\tlunatik_object_t *data = luahid_pushdata(L, ctx);\n\tluahid_op(L, ctx, 3);\n\tluadata_clear(data);\n\treturn 0;\n}\n\nstatic int luahid_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)\n{\n\tluahid_t *hid = luahid_gethid(hdev);\n\tluahid_ctx_t ctx = {.data = data, .size = size, .report = report};\n\tint ret;\n\n\tluahid_run(raw_event, &ctx, hid, hdev, ret);\n\treturn ret;\n}\n\n/***\n* Registers a new HID driver.\n* @function register\n* @tparam table opts driver options: `name` (string), `id_table` (array of device ID tables,\n*   each with optional integer fields `bus`, `group`, `vendor`, `product`, `driver_data`)\n* @treturn hid_driver\n* @raise if required fields are missing, `id_table` is invalid, or driver registration fails\n*/\nstatic int luahid_register(lua_State *L)\n{\n\tluaL_checktype(L, 1, LUA_TTABLE);\n\n\tlunatik_object_t *object = lunatik_newobject(L, &luahid_class, sizeof(luahid_t), LUNATIK_OPT_NONE);\n\tluahid_t *hid = (luahid_t *)object->private;\n\tstruct hid_driver *driver = &hid->driver;\n\tdriver->name = lunatik_checkalloc(L, NAME_MAX);\n\tlunatik_setstring(L, 1, driver, name, NAME_MAX);\n\n\tlunatik_checkfield(L, 1, \"id_table\", LUA_TTABLE);\n\tluaL_argcheck(L, (driver->id_table = luahid_setidtable(L, -1)) != NULL, 1, \"invalid id_table\");\n\n\tdriver->probe = luahid_probe;\n\tdriver->report_fixup = luahid_report_fixup;\n\tdriver->raw_event = luahid_raw_event;\n\n\tlunatik_setruntime(L, hid, hid);\n\tluadata_attach(L, hid, data, LUNATIK_OPT_SINGLE);\n\tlunatik_getobject(hid->runtime);\n\tlunatik_registerobject(L, 1, object);\n\n\tif (hid_register_driver(driver) != 0) {\n\t\tlunatik_unregisterobject(L, object);\n\t\tluaL_error(L, \"failed to register hid driver: %s\", driver->name);\n\t}\n\n\thid->registered = true;\n\treturn 1; /* object */\n}\n\nLUNATIK_CLASSES(hid, &luahid_class);\nLUNATIK_NEWLIB(hid, luahid_lib, luahid_classes, NULL);\n\nstatic int __init luahid_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luahid_exit(void)\n{\n}\n\nmodule_init(luahid_init);\nmodule_exit(luahid_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Jieming Zhou <qrsikno@gmail.com>\");\n\n"
  },
  {
    "path": "lib/lualinux.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Various Linux kernel facilities.\n* This library includes functions for random number generation, task scheduling,\n* time retrieval, kernel symbol lookup, network interface information,\n* access to kernel constants like file modes, task states, and error numbers.\n*\n* @module linux\n*/\n\n#include <linux/random.h>\n#include <linux/stat.h>\n#include <linux/sched.h>\n#include <linux/jiffies.h>\n#include <linux/ktime.h>\n#include <linux/netdevice.h>\n\n#include <lunatik.h>\n\n/***\n* Generates pseudo-random integers.\n* Mimics the behavior of Lua's `math.random` but uses kernel's random number\n* generation facilities (`get_random_u32` or `get_random_u64`).\n*\n* @function random\n* @tparam[opt] integer m Lower bound of the range. If only one argument `n` is provided, `m` defaults to 1.\n* @tparam[opt] integer n Upper bound of the range.\n* @treturn integer A pseudo-random integer.\n*   - If called without arguments, returns an integer with all bits pseudo-random.\n*   - If called with one integer `n`, returns a pseudo-random integer in the range `[1, n]`.\n*   - If called with two integers `m` and `n`, returns a pseudo-random integer in the range `[m, n]`.\n* @raise Error if `m > n` or if the interval is too large.\n* @usage\n*   local r1 = linux.random()       -- Full range random integer\n*   local r2 = linux.random(100)    -- Random integer between 1 and 100\n*   local r3 = linux.random(50, 60) -- Random integer between 50 and 60\n*/\n/* based on math_random() @ lua/lmathlib.c */\nstatic int lualinux_random(lua_State *L)\n{\n\tlua_Integer low, up, rand;\n\n\tswitch (lua_gettop(L)) {  /* check number of arguments */\n\tcase 0: {  /* no arguments */\n\t\tlua_pushinteger(L, (lua_Integer)get_random_u64());\n\t\treturn 1;\n\t}\n\tcase 1: {  /* only upper limit */\n\t\tlow = 1;\n\t\tup = luaL_checkinteger(L, 1);\n\t\tbreak;\n\t}\n\tcase 2: {  /* lower and upper limits */\n\t\tlow = luaL_checkinteger(L, 1);\n\t\tup = luaL_checkinteger(L, 2);\n\t\tbreak;\n\t}\n\tdefault:\n\t\treturn luaL_error(L, \"wrong number of arguments\");\n\t}\n\n\t/* random integer in the interval [low, up] */\n\tluaL_argcheck(L, low <= up, 1, \"interval is empty\");\n\tluaL_argcheck(L, low >= 0 || up <= LUA_MAXINTEGER + low, 1, \"interval too large\");\n\n\trand = low + ((lua_Integer)get_random_u64()) % (up - low + 1);\n\tlua_pushinteger(L, rand);\n\treturn 1;\n}\n\n/***\n* Puts the current task to sleep.\n* Sets the current task's state and schedules it out until a timeout occurs\n* or it is woken up.\n*\n* @function schedule\n* @tparam[opt] integer timeout Duration in milliseconds to sleep.\n* Defaults to `MAX_SCHEDULE_TIMEOUT` (effectively indefinite sleep until woken).\n* @tparam[opt] integer state task state to set before sleeping.\n* See `linux.task` for possible values. Defaults to `linux.task.INTERRUPTIBLE`.\n* @treturn integer remaining time in milliseconds\n* if the sleep was interrupted before the full timeout, or 0 if the full timeout elapsed.\n* @raise Error if an invalid task state is provided.\n* @see linux.task\n* @usage\n*   linux.schedule(1000) -- Sleep for 1 second (interruptible)\n*   linux.schedule(500, linux.task.UNINTERRUPTIBLE) -- Sleep for 0.5 seconds (uninterruptible)\n*/\nstatic int lualinux_schedule(lua_State *L)\n{\n\tlua_Integer timeout = luaL_optinteger(L, 1, MAX_SCHEDULE_TIMEOUT);\n\tlua_Integer state = luaL_optinteger(L, 2, TASK_INTERRUPTIBLE);\n\n\tif (timeout != MAX_SCHEDULE_TIMEOUT)\n\t\ttimeout = msecs_to_jiffies(timeout);\n\n\tluaL_argcheck(L, state == TASK_INTERRUPTIBLE || state == TASK_UNINTERRUPTIBLE ||\n\t\tstate == TASK_KILLABLE || state == TASK_IDLE, 2, \"invalid task state\");\n\t__set_current_state(state);\n\n\tlua_pushinteger(L, jiffies_to_msecs(schedule_timeout(timeout)));\n\treturn 1;\n}\n\n/***\n* Controls kernel tracing.\n* Turns kernel tracing on or off via `tracing_on()` and `tracing_off()`.\n*\n* @function tracing\n* @tparam[opt] boolean enable If `true`, turns tracing on. If `false`, turns tracing off.\n* If omitted, does not change the state.\n* @treturn boolean current kernel tracing state (`true` if on, `false` if off) *after* any change.\n* @usage\n*   local was_tracing = linux.tracing(true) -- Enable tracing\n*   if was_tracing then print(\"Tracing is now on\") end\n*   local current_state = linux.tracing()   -- Get current state\n*   linux.tracing(false)                    -- Disable tracing\n*/\nstatic int lualinux_tracing(lua_State *L)\n{\n\tif (lua_gettop(L) == 0)\n\t\tgoto out;\n\n\tif (lua_toboolean(L, 1))\n\t\ttracing_on();\n\telse\n\t\ttracing_off();\nout:\n\tlua_pushboolean(L, tracing_is_on());\n\treturn 1;\n}\n\n/***\n* Gets the current real time.\n*\n* @function time\n* @treturn integer current time in nanoseconds since the epoch (from `ktime_get_real_ns`).\n*/\nstatic int lualinux_time(lua_State *L)\n{\n\tlua_pushinteger(L, (lua_Integer)ktime_get_real_ns());\n\treturn 1;\n}\n\n/***\n* Calculates the difference between two timestamps.\n*\n* @function difftime\n* @tparam integer t2 later timestamp (e.g., from `linux.time()`).\n* @tparam integer t1 earlier timestamp (e.g., from `linux.time()`).\n* @treturn integer difference `t2 - t1` in nanoseconds.\n*/\nstatic int lualinux_difftime(lua_State *L)\n{\n\tu64 t2 = (u64) luaL_checkinteger(L, 1);\n\tu64 t1 = (u64) luaL_checkinteger(L, 2);\n\tlua_pushinteger(L, (lua_Integer)(t2 - t1));\n\treturn 1;\n}\n\n/***\n* Looks up a kernel symbol by name.\n* Uses `kallsyms_lookup_name` (potentially via kprobes) to find the address\n* of a kernel symbol.\n*\n* @function lookup\n* @tparam string symbol_name kernel symbol name to look up.\n* @treturn lightuserdata symbol address if found, otherwise `nil` (represented as a NULL lightuserdata).\n* @usage\n*   local addr = linux.lookup(\"jiffies\")\n*   if addr then print(\"Address of jiffies:\", addr) end\n*/\nstatic int lualinux_lookup(lua_State *L)\n{\n\tconst char *symbol = luaL_checkstring(L, 1);\n\n\tlua_pushlightuserdata(L, lunatik_lookup(symbol));\n\treturn 1;\n}\n\n/***\n* Gets the interface index for a network device name.\n*\n* @function ifindex\n* @tparam string interface_name network interface name (e.g., \"eth0\").\n* @treturn integer interface index.\n* @raise Error if the device is not found.\n* @usage\n*   local index = linux.ifindex(\"lo\")\n*   print(\"Index of lo:\", index)\n*/\nstatic int lualinux_ifindex(lua_State *L)\n{\n\tconst char *ifname = luaL_checkstring(L, 1);\n\tstruct net_device *dev = dev_get_by_name(&init_net, ifname);\n\n\tluaL_argcheck(L, dev != NULL, 1, \"device not found\");\n\tlua_pushinteger(L, dev->ifindex);\n\tdev_put(dev);\n\treturn 1;\n}\n\n/***\n* Gets the HW address for the network interface index.\n*\n* @function ifaddr\n* @tparam integer ifindex interface index number.\n* @treturn string interface HW address.\n* @raise Error if the device is not found.\n* @usage\n*   local addr = linux.ifaddr(index)\n*   print(string.byte(addr,1,6))\n*/\nstatic int lualinux_ifaddr(lua_State *L)\n{\n\tint ifindex = luaL_checkinteger(L, 1);\n\tluaL_Buffer B;\n\tchar *addr = luaL_buffinitsize(L, &B, MAX_ADDR_LEN);\n\tstruct net_device *dev = dev_get_by_index(&init_net, ifindex);\n\n\tluaL_argcheck(L, dev != NULL, 1, \"device not found\");\n\tsize_t len = min_t(size_t, dev->addr_len, MAX_ADDR_LEN);\n\tmemcpy(addr, dev->dev_addr, len);\n\tdev_put(dev);\n\tluaL_pushresultsize(&B, len);\n\treturn 1;\n}\n\n/***\n* Table of task state constants.\n* Exports task state flags from `<linux/sched.h>`. These are used with\n* `linux.schedule()`.\n*\n* @table task\n*   @tfield integer INTERRUPTIBLE Task is waiting for a signal or a resource (sleeping), can be interrupted.\n*   @tfield integer UNINTERRUPTIBLE Task is waiting (sleeping),\n*   cannot be interrupted by signals (except fatal ones if KILLABLE is also implied by context).\n*   @tfield integer KILLABLE Task is waiting (sleeping) like UNINTERRUPTIBLE, but can be interrupted by fatal signals.\n*   @tfield integer IDLE Task is idle, similar to UNINTERRUPTIBLE but avoids loadavg accounting.\n* @see linux.schedule\n*/\nstatic const lunatik_reg_t lualinux_task[] = {\n\t{\"INTERRUPTIBLE\", TASK_INTERRUPTIBLE},\n\t{\"UNINTERRUPTIBLE\", TASK_UNINTERRUPTIBLE},\n\t{\"KILLABLE\", TASK_KILLABLE},\n\t{\"IDLE\", TASK_IDLE},\n\t{NULL, 0}\n};\n\n/***\n* Table of file mode constants.\n* Exports file permission flags from `<linux/stat.h>`. These can be used, for\n* example, with `device.new()` to set the mode of a character device.\n*/\nstatic const lunatik_reg_t lualinux_stat[] = {\n\t/* user */\n\t{\"IRWXU\", S_IRWXU},\n\t{\"IRUSR\", S_IRUSR},\n\t{\"IWUSR\", S_IWUSR},\n\t{\"IXUSR\", S_IXUSR},\n\t/* group */\n\t{\"IRWXG\", S_IRWXG},\n\t{\"IRGRP\", S_IRGRP},\n\t{\"IWGRP\", S_IWGRP},\n\t{\"IXGRP\", S_IXGRP},\n\t/* other */\n\t{\"IRWXO\", S_IRWXO},\n\t{\"IROTH\", S_IROTH},\n\t{\"IWOTH\", S_IWOTH},\n\t{\"IXOTH\", S_IXOTH},\n\t/* user, group, other */\n\t{\"IRWXUGO\", (S_IRWXU|S_IRWXG|S_IRWXO)},\n\t{\"IALLUGO\", (S_ISUID|S_ISGID|S_ISVTX|S_IRWXUGO)},\n\t{\"IRUGO\", (S_IRUSR|S_IRGRP|S_IROTH)},\n\t{\"IWUGO\", (S_IWUSR|S_IWGRP|S_IWOTH)},\n\t{\"IXUGO\", (S_IXUSR|S_IXGRP|S_IXOTH)},\n\t{NULL, 0}\n};\n\n/***\n* Returns the symbolic name of a kernel error number.\n* For example, it converts `2` to `\"ENOENT\"`.\n*\n* @function errname\n* @tparam integer err error number (e.g., 2).\n* @treturn string symbolic error name (e.g., \"ENOENT\").\n* Returns \"unknown\" (or the error number as a string) if the name cannot be resolved.\n* @usage\n* local name = linux.errname(2)\n* print(\"Error name:\", name) -- \"ENOENT\"\n*/\nstatic int lualinux_errname(lua_State *L)\n{\n    int e = (int)luaL_checkinteger(L, 1);\n    lunatik_pusherrname(L, e);\n    return 1;\n}\n\nstatic const lunatik_namespace_t lualinux_flags[] = {\n\t{\"stat\", lualinux_stat},\n\t{\"task\", lualinux_task},\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg lualinux_lib[] = {\n\t{\"random\", lualinux_random},\n\t{\"schedule\", lualinux_schedule},\n\t{\"tracing\", lualinux_tracing},\n\t{\"time\", lualinux_time},\n\t{\"difftime\", lualinux_difftime},\n\t{\"lookup\", lualinux_lookup},\n\t{\"ifindex\", lualinux_ifindex},\n\t{\"ifaddr\", lualinux_ifaddr},\n\t{\"errname\", lualinux_errname},\n\t{NULL, NULL}\n};\n\nLUNATIK_NEWLIB(linux, lualinux_lib, NULL, lualinux_flags);\n\nstatic int __init lualinux_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit lualinux_exit(void)\n{\n}\n\nmodule_init(lualinux_init);\nmodule_exit(lualinux_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ringzero.com.br>\");\n\n"
  },
  {
    "path": "lib/luanetfilter.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2024-2026 Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com>\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Lua interface to the Linux Netfilter framework.\n* @module netfilter\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/netfilter.h>\n\n#include <lunatik.h>\n\n#include \"luanetfilter.h\"\n#include \"luaskb.h\"\n\n/***\n* Registered Netfilter hook. Garbage collecting this object unregisters the hook.\n* @type netfilter_hook\n*/\ntypedef struct luanetfilter_s {\n\tlunatik_object_t *runtime;\n\tlunatik_object_t *skb;\n\tu32 mark;\n\tstruct nf_hook_ops nfops;\n} luanetfilter_t;\n\nstatic void luanetfilter_release(void *private);\n\nstatic inline bool luanetfilter_pushcb(lua_State *L, luanetfilter_t *luanf)\n{\n\tif (lunatik_getregistry(L, luanf) != LUA_TTABLE) {\n\t\tpr_err(\"couldn't find ops table\\n\");\n\t\treturn false;\n\t}\n\n\tif (lua_getfield(L, -1, \"hook\") != LUA_TFUNCTION) {\n\t\tpr_err(\"operation not defined\\n\");\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nstatic inline lunatik_object_t *luanetfilter_pushskb(lua_State *L, luanetfilter_t *luanf, struct sk_buff *skb)\n{\n\tif (lunatik_getregistry(L, luanf->skb) != LUA_TUSERDATA) {\n\t\tpr_err(\"couldn't find skb\\n\");\n\t\treturn NULL;\n\t}\n\n\tlunatik_object_t *object = lunatik_toobject(L, -1);\n\tif (unlikely(object == NULL)) {\n\t\tpr_err(\"couldn't get skb object\\n\");\n\t\treturn NULL;\n\t}\n\n\tluaskb_reset(object, skb);\n\treturn object;\n}\n\nstatic int luanetfilter_hook_cb(lua_State *L, luanetfilter_t *luanf, struct sk_buff *skb)\n{\n\tlunatik_object_t *object = NULL;\n\tint ret = -1;\n\n\tif (!luanetfilter_pushcb(L, luanf) || (object = luanetfilter_pushskb(L, luanf, skb)) == NULL)\n\t\tgoto out;\n\n\tif (lua_pcall(L, 1, 2, 0) != LUA_OK) {\n\t\tpr_err(\"%s\\n\", lua_tostring(L, -1));\n\t\tlua_pop(L, 1);\n\t\tgoto clear;\n\t}\n\n\tif (!lua_isnil(L, -1))\n\t\tskb->mark = (u32)lua_tointeger(L, -1);\n\tret = (int)lua_tointeger(L, -2);\nclear:\n\tluaskb_clear(object);\nout:\n\treturn ret;\n}\n\nstatic inline unsigned int luanetfilter_docall(luanetfilter_t *luanf, struct sk_buff *skb)\n{\n\tint ret;\n\tint policy = NF_ACCEPT;\n\n\tif (unlikely(!luanf || !luanf->runtime)) {\n\t\tpr_err(\"runtime not found\\n\");\n\t\tgoto out;\n\t}\n\n\tif (likely(luanf->mark != skb->mark))\n\t\tgoto out;\n\n\tlunatik_run(luanf->runtime, luanetfilter_hook_cb, ret, luanf, skb);\n\treturn (ret < 0 || ret > NF_MAX_VERDICT) ? policy : ret;\nout:\n\treturn policy;\n}\n\nstatic unsigned int luanetfilter_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)\n{\n\tluanetfilter_t *luanf = (luanetfilter_t *)priv;\n\treturn luanetfilter_docall(luanf, skb);\n}\n\nstatic const luaL_Reg luanetfilter_mt[] = {\n\t{\"__gc\", lunatik_deleteobject},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luanetfilter_class = {\n\t.name = \"netfilter\",\n\t.methods = luanetfilter_mt,\n\t.release = luanetfilter_release,\n\t.opt = LUNATIK_OPT_SOFTIRQ | LUNATIK_OPT_SINGLE,\n};\n\n/***\n* Registers a Netfilter hook.\n* @function register\n* @tparam table opts Hook options: `hook` (function), `pf`, `hooknum`, `priority` (integers),\n*   and optionally `mark` (integer, default 0).\n* @treturn netfilter_hook Registered hook handle.\n*/\nstatic int luanetfilter_register(lua_State *L)\n{\n\tluaL_checktype(L, 1, LUA_TTABLE);\n\tlunatik_object_t *object = lunatik_newobject(L, &luanetfilter_class, sizeof(luanetfilter_t), LUNATIK_OPT_NONE);\n\tluanetfilter_t *nf = (luanetfilter_t *)object->private;\n\tnf->runtime = NULL;\n\n\tstruct nf_hook_ops *nfops = &nf->nfops;\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)\n\tnfops->hook_ops_type = NF_HOOK_OP_UNDEFINED;\n#endif\n\tnfops->hook = luanetfilter_hook;\n\tnfops->dev = NULL;\n\tnfops->priv = nf;\n\tlunatik_setinteger(L, 1, nfops, pf);\n\tlunatik_setinteger(L, 1, nfops, hooknum);\n\tlunatik_setinteger(L, 1, nfops, priority);\n\tlunatik_optinteger(L, 1, nf, mark, 0);\n\n\tif (nf_register_net_hook(&init_net, nfops) != 0)\n\t\tluaL_error(L, \"failed to register netfilter hook\");\n\n\tlunatik_setruntime(L, netfilter, nf);\n\tluaskb_attach(L, nf, skb);\n\tlunatik_getobject(nf->runtime);\n\tlunatik_registerobject(L, 1, object);\n\treturn 1;\n}\n\nstatic const luaL_Reg luanetfilter_lib[] = {\n\t{\"register\", luanetfilter_register},\n\t{NULL, NULL},\n};\n\nstatic void luanetfilter_release(void *private)\n{\n\tluanetfilter_t *nf = (luanetfilter_t *)private;\n\tlunatik_object_t *runtime = nf->runtime;\n\tif (runtime == NULL)\n\t\treturn;\n\n\tnf_unregister_net_hook(&init_net, &nf->nfops);\n\tlunatik_detach(runtime, nf, skb);\n\tlunatik_putobject(runtime);\n\tnf->runtime = NULL;\n}\n\nLUNATIK_CLASSES(netfilter, &luanetfilter_class);\nLUNATIK_NEWLIB(netfilter, luanetfilter_lib, luanetfilter_classes, luanetfilter_flags);\n\nstatic int __init luanetfilter_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luanetfilter_exit(void)\n{\n}\n\nmodule_init(luanetfilter_init);\nmodule_exit(luanetfilter_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com>\");\n\n"
  },
  {
    "path": "lib/luanetfilter.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2024-2025 Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com>\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Low-level Lua interface to the Linux Kernel Netfilter framework.\n*\n* This header file defines constants used by the C implementation and exposed to Lua.\n*\n* @module netfilter\n*/\n\n#ifndef luanetfilter_h\n#define luanetfilter_h\n\n#include <linux/netfilter.h>\n#include <linux/netfilter_ipv4.h>\n#include <linux/netfilter_bridge.h>\n#include <linux/netfilter_arp.h>\n#include <linux/netfilter/x_tables.h>\n\n#include <lunatik.h>\n\n\n/***\n* Table of Netfilter protocol families.\n* @table family\n*   @field UNSPEC Unspecified protocol family.\n*   @tfield integer UNSPEC Unspecified protocol family.\n*   @tfield integer INET Internetwork protocol family (covering IPv4/IPv6).\n*   @tfield integer IPV4 Internet Protocol version 4.\n*   @tfield integer IPV6 Internet Protocol version 6.\n*   @tfield integer ARP Address Resolution Protocol.\n*   @tfield integer NETDEV Network device hooks (ingress/egress).\n*   @tfield integer BRIDGE Ethernet bridging hooks.\n*/\nconst lunatik_reg_t luanetfilter_family[] = {\n\t{\"UNSPEC\", NFPROTO_UNSPEC},\n\t{\"INET\", NFPROTO_INET},\n\t{\"IPV4\", NFPROTO_IPV4},\n\t{\"IPV6\", NFPROTO_IPV6},\n\t{\"ARP\", NFPROTO_ARP},\n\t{\"NETDEV\", NFPROTO_NETDEV},\n\t{\"BRIDGE\", NFPROTO_BRIDGE},\n\t{NULL, 0}\n};\n\n/***\n* Table of Netfilter hook verdicts (actions).\n* These determine the fate of a packet processed by a hook.\n* @table action\n*   @tfield integer DROP Drop the packet silently.\n*   @tfield integer ACCEPT Let the packet pass.\n*   @tfield integer STOLEN Packet is consumed by the hook; processing stops.\n*   @tfield integer QUEUE Queue the packet to a userspace program.\n*   @tfield integer REPEAT Re-inject the packet into the current hook (use with caution).\n*   @tfield integer STOP Terminate rule traversal in the current chain (iptables specific).\n*   @tfield integer CONTINUE Alias for ACCEPT, primarily for Xtables.\n*   @tfield integer RETURN Return from the current chain to the calling chain (iptables specific).\n*/\nconst lunatik_reg_t luanetfilter_action[] = {\n\t{\"DROP\", NF_DROP},\n\t{\"ACCEPT\", NF_ACCEPT},\n\t{\"STOLEN\", NF_STOLEN},\n\t{\"QUEUE\", NF_QUEUE},\n\t{\"REPEAT\", NF_REPEAT},\n\t{\"STOP\", NF_STOP},\n\t{\"CONTINUE\", XT_CONTINUE},\n\t{\"RETURN\", XT_RETURN},\n\t{NULL, 0}\n};\n\n/***\n* Table of Netfilter hooks in the INET (IPv4/IPv6) family.\n* These define points in the network stack where packet processing can occur.\n* @table inet_hooks\n*   @tfield integer PRE_ROUTING After packet reception, before routing decision.\n*   @tfield integer LOCAL_IN For packets destined to the local machine, after routing.\n*   @tfield integer FORWARD For packets to be forwarded to another interface, after routing.\n*   @tfield integer LOCAL_OUT For packets generated locally, before sending to an interface.\n*   @tfield integer POST_ROUTING Before packets are sent out, after routing and just before handing to hardware.\n*/\nconst lunatik_reg_t luanetfilter_inet_hooks[] = {\n\t{\"PRE_ROUTING\", NF_INET_PRE_ROUTING},\n\t{\"LOCAL_IN\", NF_INET_LOCAL_IN},\n\t{\"FORWARD\", NF_INET_FORWARD},\n\t{\"LOCAL_OUT\", NF_INET_LOCAL_OUT},\n\t{\"POST_ROUTING\", NF_INET_POST_ROUTING},\n\t{NULL, 0}\n};\n\n/***\n* Table of Netfilter hooks in the BRIDGE family.\n* These define points for processing layer 2 (Ethernet) bridge traffic.\n* @table bridge_hooks\n*   @tfield integer PRE_ROUTING For packets entering the bridge, before any bridge processing (e.g., ebtables broute chain).\n*   @tfield integer LOCAL_IN For bridged packets destined for the bridge interface itself (if IP processing is enabled on the bridge).\n*   @tfield integer FORWARD For packets being forwarded by the bridge between its ports (e.g., ebtables filter chain).\n*   @tfield integer LOCAL_OUT For packets originating from the bridge interface itself.\n*   @tfield integer POST_ROUTING For packets leaving the bridge, after all bridge processing (e.g., ebtables nat chain).\n*/\nstatic const lunatik_reg_t luanetfilter_bridge_hooks[] = {\n\t{\"PRE_ROUTING\", NF_BR_PRE_ROUTING},\n\t{\"LOCAL_IN\", NF_BR_LOCAL_IN},\n\t{\"FORWARD\", NF_BR_FORWARD},\n\t{\"LOCAL_OUT\", NF_BR_LOCAL_OUT},\n\t{\"POST_ROUTING\", NF_BR_POST_ROUTING},\n\t{NULL, 0},\n};\n\n/***\n* Table of Netfilter hooks in the ARP family.\n* @table arp_hooks\n*   @tfield integer IN For incoming ARP packets.\n*   @tfield integer OUT For outgoing ARP packets.\n*   @tfield integer FORWARD For forwarded ARP packets (e.g., by an ARP proxy).\n*/\nstatic const lunatik_reg_t luanetfilter_arp_hooks[] = {\n\t{\"IN\", NF_ARP_IN},\n\t{\"OUT\", NF_ARP_OUT},\n\t{\"FORWARD\", NF_ARP_FORWARD},\n\t{NULL, 0}\n};\n\n/***\n* Table of Netfilter hooks in the NETDEV family.\n* These hooks operate at the network device driver level.\n* @table netdev_hooks\n*   @tfield integer INGRESS For packets as they are received by a network device, very early in the stack.\n*   @tfield integer EGRESS For packets just before they are transmitted by a network device, very late in the stack (Kernel 5.16+).\n*/\nconst lunatik_reg_t luanetfilter_netdev_hooks[] = {\n    {\"INGRESS\", NF_NETDEV_INGRESS},\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0)\n    {\"EGRESS\", NF_NETDEV_EGRESS},\n#endif\n    {NULL, 0}\n};\n\n/***\n* Table of Netfilter hook priorities in the IP family.\n* Hooks with lower priority numbers are called earlier within the same hook point.\n* @table ip_priority\n*   @tfield integer FIRST Highest priority, hook runs first.\n*   @tfield integer RAW_BEFORE_DEFRAG Priority for `raw` table processing, before packet defragmentation.\n*   @tfield integer CONNTRACK_DEFRAG Priority for connection tracking related to defragmentation.\n*   @tfield integer RAW Priority for `raw` table processing.\n*   @tfield integer SELINUX_FIRST Early priority for SELinux hooks.\n*   @tfield integer CONNTRACK Priority for main connection tracking.\n*   @tfield integer MANGLE Priority for `mangle` table processing (packet alteration).\n*   @tfield integer NAT_DST Priority for Destination NAT (`nat` table, PREROUTING/OUTPUT).\n*   @tfield integer FILTER Priority for `filter` table processing (packet filtering).\n*   @tfield integer SECURITY Priority for security modules like SELinux.\n*   @tfield integer NAT_SRC Priority for Source NAT (`nat` table, POSTROUTING/INPUT).\n*   @tfield integer SELINUX_LAST Late priority for SELinux hooks.\n*   @tfield integer CONNTRACK_HELPER Priority for connection tracking helper modules.\n*   @tfield integer LAST Lowest priority, hook runs last.\n*/\nstatic const lunatik_reg_t luanetfilter_ip_priority[] = {\n\t{\"FIRST\", NF_IP_PRI_FIRST},\n\t{\"RAW_BEFORE_DEFRAG\", NF_IP_PRI_RAW_BEFORE_DEFRAG},\n\t{\"CONNTRACK_DEFRAG\", NF_IP_PRI_CONNTRACK_DEFRAG},\n\t{\"RAW\", NF_IP_PRI_RAW},\n\t{\"SELINUX_FIRST\", NF_IP_PRI_SELINUX_FIRST},\n\t{\"CONNTRACK\", NF_IP_PRI_CONNTRACK},\n\t{\"MANGLE\", NF_IP_PRI_MANGLE},\n\t{\"NAT_DST\", NF_IP_PRI_NAT_DST},\n\t{\"FILTER\", NF_IP_PRI_FILTER},\n\t{\"SECURITY\", NF_IP_PRI_SECURITY},\n\t{\"NAT_SRC\", NF_IP_PRI_NAT_SRC},\n\t{\"SELINUX_LAST\", NF_IP_PRI_SELINUX_LAST},\n\t{\"CONNTRACK_HELPER\", NF_IP_PRI_CONNTRACK_HELPER},\n\t{\"LAST\", NF_IP_PRI_LAST},\n\t{NULL, 0},\n};\n\n/***\n* Table of Netfilter hook priorities in the BRIDGE family.\n* Hooks with lower priority numbers are called earlier.\n* @table bridge_priority\n*   @tfield integer FIRST Highest priority for bridge hooks.\n*   @tfield integer NAT_DST_BRIDGED Priority for Destination NAT on bridged-only packets (ebtables `dnat` chain).\n*   @tfield integer FILTER_BRIDGED Priority for filtering bridged-only packets (ebtables `filter` chain in FORWARD).\n*   @tfield integer BRNF Priority for bridge netfilter specific operations (interaction between bridge and IP stack).\n*   @tfield integer NAT_DST_OTHER Priority for Destination NAT on packets routed through the bridge (iptables `PREROUTING` on bridge interface).\n*   @tfield integer FILTER_OTHER Priority for filtering packets routed through the bridge (iptables `FORWARD` or `INPUT` on bridge interface).\n*   @tfield integer NAT_SRC Priority for Source NAT on bridged or routed packets (ebtables `snat` or iptables `POSTROUTING`).\n*   @tfield integer LAST Lowest priority for bridge hooks.\n*/\nstatic const lunatik_reg_t luanetfilter_bridge_priority[] = {\n\t{\"FIRST\", NF_BR_PRI_FIRST},\n\t{\"NAT_DST_BRIDGED\", NF_BR_PRI_NAT_DST_BRIDGED},\n\t{\"FILTER_BRIDGED\", NF_BR_PRI_FILTER_BRIDGED},\n\t{\"BRNF\", NF_BR_PRI_BRNF},\n\t{\"NAT_DST_OTHER\", NF_BR_PRI_NAT_DST_OTHER},\n\t{\"FILTER_OTHER\", NF_BR_PRI_FILTER_OTHER},\n\t{\"NAT_SRC\", NF_BR_PRI_NAT_SRC},\n\t{\"LAST\", NF_BR_PRI_LAST},\n\t{NULL, 0},\n};\n\nstatic const lunatik_namespace_t luanetfilter_flags[] = {\n\t{\"family\", luanetfilter_family},\n\t{\"action\", luanetfilter_action},\n\t{\"inet_hooks\", luanetfilter_inet_hooks},\n\t{\"bridge_hooks\", luanetfilter_bridge_hooks},\n\t{\"arp_hooks\", luanetfilter_arp_hooks},\n\t{\"netdev_hooks\", luanetfilter_netdev_hooks},\n\t{\"ip_priority\", luanetfilter_ip_priority},\n\t{\"bridge_priority\", luanetfilter_bridge_priority},\n\t{NULL, NULL}\n};\n\n#endif\n\n"
  },
  {
    "path": "lib/luanotifier.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2025 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Notifier chain mechanism.\n* This library allows Lua scripts to register callback functions that are\n* invoked when specific kernel events occur, such as keyboard input,\n* network device status changes, or virtual terminal events.\n*\n* @module notifier\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/keyboard.h>\n#include <linux/netdevice.h>\n#include <linux/vt_kern.h>\n#include <linux/vt.h>\n\n#include <lunatik.h>\n\ntypedef int (*luanotifier_register_t)(struct notifier_block *nb);\ntypedef int (*luanotifier_handler_t)(lua_State *L, void *data);\n\n/***\n* Represents a kernel notifier object.\n* This is a userdata object returned by functions like `notifier.keyboard()`,\n* `notifier.netdevice()`, or `notifier.vterm()`. It encapsulates a\n* `struct notifier_block` and the associated Lua callback.\n* @type notifier\n*/\n\ntypedef struct luanotifier_s {\n\tstruct notifier_block nb;\n\tlunatik_object_t *runtime;\n\tluanotifier_handler_t handler;\n\tluanotifier_register_t unregister;\n\tbool running;\n} luanotifier_t;\n\nstatic int luanotifier_keyboard_handler(lua_State *L, void *data)\n{\n\tstruct keyboard_notifier_param *param = (struct keyboard_notifier_param *)data;\n\n\tlua_pushboolean(L, param->down);\n\tlua_pushboolean(L, param->shift);\n\tlua_pushinteger(L, (lua_Integer)(param->value));\n\treturn 3;\n}\n\nstatic int luanotifier_netdevice_handler(lua_State *L, void *data)\n{\n\tstruct net_device *dev = netdev_notifier_info_to_dev(data);\n\n\tlua_pushstring(L, dev->name);\n\treturn 1;\n}\n\nstatic int luanotifier_vt_handler (lua_State* L, void* data)\n{\n\tstruct vt_notifier_param* param = data;\n\n\tlua_pushinteger(L, param->c);\n\tlua_pushinteger(L, param->vc->vc_num);\n\treturn 2;\n}\n\nstatic int luanotifier_handler(lua_State *L, luanotifier_t *notifier, unsigned long event, void *data)\n{\n\tint nargs = 1; /* event */\n\tint ret = NOTIFY_OK;\n\n\tnotifier->running = true;\n\tif (lunatik_getregistry(L, notifier) != LUA_TFUNCTION) {\n\t\tpr_err(\"could not find notifier callback\\n\");\n\t\tgoto err;\n\t}\n\n\tlua_pushinteger(L, (lua_Integer)event);\n\tnargs += notifier->handler(L, data);\n\tif (lua_pcall(L, nargs, 1, 0) != LUA_OK) { /* callback(event, ...) */\n\t\tpr_err(\"%s\\n\", lua_tostring(L, -1));\n\t\tgoto err;\n\t}\n\n\tret = lua_tointeger(L, -1);\nerr:\n\tnotifier->running = false;\n\treturn ret;\n}\n\nstatic int luanotifier_call(struct notifier_block *nb, unsigned long event, void *data)\n{\n\tluanotifier_t *notifier = container_of(nb, luanotifier_t, nb);\n\tbool islocked = !notifier->unregister; /* was called from register_fn? */\n\tint ret;\n\n\tif (islocked)\n\t\tlunatik_handle(notifier->runtime, luanotifier_handler, ret, notifier, event, data);\n\telse\n\t\tlunatik_run(notifier->runtime, luanotifier_handler, ret, notifier, event, data);\n\n\treturn ret;\n}\n\nstatic int luanotifier_new(lua_State *, luanotifier_register_t, luanotifier_register_t, luanotifier_handler_t);\n\n/***\n* Registers a notifier for keyboard events.\n* The provided callback function will be invoked whenever a console keyboard\n* event occurs (e.g., a key is pressed or released).\n* @function keyboard\n* @tparam function callback Lua function called on keyboard events.\n*   It receives the following arguments:\n*\n*   1. `event` (integer): The keyboard event type (see `notifier.kbd`).\n*   2. `down` (boolean): `true` if the key is pressed, `false` if released.\n*   3. `shift` (boolean): `true` if a shift key (Shift, Alt, Ctrl) is held, `false` otherwise.\n*   4. `value` (integer): The key's value (keycode or keysym, depending on the `event`).\n*\n*   The callback should return a `notifier.notify` status code (e.g., `notifier.notify.OK`).\n* @treturn notifier A new notifier object.\n* @see notifier.kbd\n* @see notifier.notify\n* @within notifier\n* @usage local kbd_notifier = notifier.keyboard(function(event, down, shift, value) print(\"Key event:\", event, value); return notifier.notify.OK end)\n*/\n#define LUANOTIFIER_NEWCHAIN(name) \t\t\t\t\t\t\\\nstatic int luanotifier_##name(lua_State *L)\t\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\t\\\n\treturn luanotifier_new(L, register_##name##_notifier, \t\t\t\\\n\t\tunregister_##name##_notifier, luanotifier_##name##_handler);\t\\\n}\n\n/***\n* Registers a notifier for network device events.\n* The provided callback function will be invoked whenever a network device\n* event occurs (e.g., an interface goes up or down).\n* @function netdevice\n* @tparam function callback Lua function called on netdevice events.\n*   It receives the following arguments:\n*\n*   1. `event` (integer): The netdevice event type (see `notifier.netdev`).\n*   2. `name` (string): The name of the network device (e.g., \"eth0\").\n*\n*   The callback should return a `notifier.notify` status code.\n* @treturn notifier A new notifier object.\n* @see notifier.netdev\n* @see notifier.notify\n* @within notifier\n* @usage local net_notif = notifier.netdevice(function(event, name) print(\"Netdev event:\", name, event); return notifier.notify.OK end)\n*/\nLUANOTIFIER_NEWCHAIN(keyboard);\nLUANOTIFIER_NEWCHAIN(netdevice);\n/***\n* Registers a notifier for virtual terminal (vterm) events.\n* The provided callback function will be invoked whenever a virtual terminal\n* event occurs (e.g., a character is written, a terminal is allocated).\n* @function vterm\n* @tparam function callback Lua function called on vterm events.\n*   It receives the following arguments:\n*\n*   1. `event` (integer): The vterm event type (see `notifier.vt`).\n*   2. `c` (integer): The character related to the event (if applicable).\n*   3. `vc_num` (integer): The virtual console number associated with the event.\n*\n*   The callback should return a `notifier.notify` status code.\n* @treturn notifier A new notifier object.\n* @see notifier.vt\n* @see notifier.notify\n* @within notifier\n*/\nLUANOTIFIER_NEWCHAIN(vt);\n\nstatic void luanotifier_release(void *private)\n{\n\tluanotifier_t *notifier = (luanotifier_t *)private;\n\n\t/* notifier might have never been stopped */\n\tif (notifier->unregister)\n\t\tnotifier->unregister(&notifier->nb);\n\n\tlunatik_putobject(notifier->runtime);\n}\n\n#define luanotifier_isruntime(L, notifier)\t(lunatik_toruntime(L) == (notifier)->runtime)\n\nstatic inline void luanotifier_checkrunning(lua_State *L, luanotifier_t *notifier)\n{\n\tif (luanotifier_isruntime(L, notifier) && notifier->running)\n\t\tluaL_error(L, \"[%p] notifier cannot unregister itself (deadlock)\", notifier);\n}\n\n/***\n* Stops and unregisters a notifier.\n* This method is called on a notifier object. Once stopped, the callback\n* will no longer be invoked for kernel events.\n* @function stop\n* @treturn nil\n* @raise Error if the notifier attempts to unregister itself from within its own callback (which would cause a deadlock).\n* @usage my_notifier:stop()\n*/\nstatic int luanotifier_stop(lua_State *L)\n{\n\tlunatik_object_t *object = lunatik_checkobject(L, 1);\n\tluanotifier_t *notifier = (luanotifier_t *)object->private;\n\n\tluanotifier_checkrunning(L, notifier);\n\n\tlunatik_lock(object);\n\tif (notifier->unregister) {\n\t\tnotifier->unregister(&notifier->nb);\n\t\tnotifier->unregister = NULL;\n\t}\n\tlunatik_unlock(object);\n\n\tif (luanotifier_isruntime(L, notifier))\n\t\tlunatik_unregisterobject(L, object);\n\treturn 0;\n}\n\nstatic int luanotifier_delete(lua_State *L)\n{\n\tlunatik_object_t **pobject = lunatik_checkpobject(L, 1);\n\tlunatik_object_t *object = *pobject;\n\n\tluanotifier_checkrunning(L, (luanotifier_t *)object->private);\n\n\tlunatik_putobject(object);\n\t*pobject = NULL;\n\treturn 0;\n}\n\nstatic const luaL_Reg luanotifier_lib[] = {\n\t{\"keyboard\", luanotifier_keyboard},\n\t{\"netdevice\", luanotifier_netdevice},\n\t{\"vterm\", luanotifier_vt},\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg luanotifier_mt[] = {\n\t{\"__gc\", luanotifier_delete},\n\t{\"stop\", luanotifier_stop},\n\t{NULL, NULL}\n};\n\n/***\n* Table of notifier chain return status codes.\n* These values are typically returned by notifier callback functions.\n* @table notify\n*   @tfield integer DONE Indicates the callback is done and doesn't care about further processing.\n*   @tfield integer OK Indicates the callback processed the event successfully and other notifiers can proceed.\n*   @tfield integer BAD Indicates the callback encountered an issue or wants to veto the action.\n*   @tfield integer STOP Indicates the callback handled the event and no further notifiers in the chain should be called.\n* @within notifier\n*/\nstatic const lunatik_reg_t luanotifier_notify[] = {\n\t{\"DONE\", NOTIFY_DONE},\n\t{\"OK\", NOTIFY_OK},\n\t{\"BAD\", NOTIFY_BAD},\n\t{\"STOP\", NOTIFY_STOP},\n\t{NULL, 0}\n};\n\n/***\n* Table of keyboard event types.\n* Used as the `event` argument in `notifier.keyboard` callbacks.\n* @table kbd\n*   @tfield integer KEYCODE Keyboard keycode event, called before any other.\n*   @tfield integer UNBOUND_KEYCODE Keyboard keycode which is not bound to any other.\n*   @tfield integer UNICODE Keyboard unicode character event.\n*   @tfield integer KEYSYM Keyboard keysym event.\n*   @tfield integer POST_KEYSYM Event called after keyboard keysym interpretation.\n* @within notifier\n*/\nstatic const lunatik_reg_t luanotifier_kbd[] = {\n\t{\"KEYCODE\", KBD_KEYCODE},\n\t{\"UNBOUND_KEYCODE\", KBD_UNBOUND_KEYCODE},\n\t{\"UNICODE\", KBD_UNICODE},\n\t{\"KEYSYM\", KBD_KEYSYM},\n\t{\"POST_KEYSYM\", KBD_POST_KEYSYM},\n\t{NULL, 0}\n};\n\n/***\n* Table of network device event types.\n* Used as the `event` argument in `notifier.netdevice` callbacks.\n* @table netdev\n*   @tfield integer UP Network device is up.\n*   @tfield integer DOWN Network device is down.\n*   @tfield integer REBOOT Network device is rebooting (deprecated).\n*   @tfield integer CHANGE Network device has changed state.\n*   @tfield integer REGISTER Network device is being registered.\n*   @tfield integer UNREGISTER Network device is being unregistered.\n*   @tfield integer CHANGEMTU MTU of the network device has changed.\n*   @tfield integer CHANGEADDR Hardware address of the network device has changed.\n*   @tfield integer PRE_CHANGEADDR Notification before hardware address change.\n*   @tfield integer GOING_DOWN Network device is being taken down.\n*   @tfield integer CHANGENAME Name of the network device has changed.\n*   @tfield integer FEAT_CHANGE Features of the network device have changed.\n*   @tfield integer BONDING_FAILOVER Bonding master has failed over to a new slave.\n*   @tfield integer PRE_UP Notification before network device is brought up.\n*   @tfield integer PRE_TYPE_CHANGE Notification before network device type changes.\n*   @tfield integer POST_TYPE_CHANGE Notification after network device type changes.\n*   @tfield integer POST_INIT Notification after network device initialization.\n*   @tfield integer PRE_UNINIT Notification before network device uninitialization (Kernel 6.2+).\n*   @tfield integer RELEASE Network device is being released.\n*   @tfield integer NOTIFY_PEERS Notify peers of a change.\n*   @tfield integer JOIN Network device has joined a multicast group.\n*   @tfield integer CHANGEUPPER Upper device state change.\n*   @tfield integer RESEND_IGMP Resend IGMP joins.\n*   @tfield integer PRECHANGEMTU Notification before MTU change.\n*   @tfield integer CHANGEINFODATA Network device info data has changed.\n*   @tfield integer BONDING_INFO Bonding information update.\n*   @tfield integer CHANGE_TX_QUEUE_LEN Transmit queue length has changed.\n* @within notifier\n*/\nstatic const lunatik_reg_t luanotifier_netdev[] = {\n\t{\"UP\", NETDEV_UP},\n\t{\"DOWN\", NETDEV_DOWN},\n\t{\"REBOOT\", NETDEV_REBOOT},\n\t{\"CHANGE\", NETDEV_CHANGE},\n\t{\"REGISTER\", NETDEV_REGISTER},\n\t{\"UNREGISTER\", NETDEV_UNREGISTER},\n\t{\"CHANGEMTU\", NETDEV_CHANGEMTU},\n\t{\"CHANGEADDR\", NETDEV_CHANGEADDR},\n\t{\"PRE_CHANGEADDR\", NETDEV_PRE_CHANGEADDR},\n\t{\"GOING_DOWN\", NETDEV_GOING_DOWN},\n\t{\"CHANGENAME\", NETDEV_CHANGENAME},\n\t{\"FEAT_CHANGE\", NETDEV_FEAT_CHANGE},\n\t{\"BONDING_FAILOVER\", NETDEV_BONDING_FAILOVER},\n\t{\"PRE_UP\", NETDEV_PRE_UP},\n\t{\"PRE_TYPE_CHANGE\", NETDEV_PRE_TYPE_CHANGE},\n\t{\"POST_TYPE_CHANGE\", NETDEV_POST_TYPE_CHANGE},\n\t{\"POST_INIT\", NETDEV_POST_INIT},\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 2, 0))\n\t{\"PRE_UNINIT\", NETDEV_PRE_UNINIT},\n#endif\n\t{\"RELEASE\", NETDEV_RELEASE},\n\t{\"NOTIFY_PEERS\", NETDEV_NOTIFY_PEERS},\n\t{\"JOIN\", NETDEV_JOIN},\n\t{\"CHANGEUPPER\", NETDEV_CHANGEUPPER},\n\t{\"RESEND_IGMP\", NETDEV_RESEND_IGMP},\n\t{\"PRECHANGEMTU\", NETDEV_PRECHANGEMTU},\n\t{\"CHANGEINFODATA\", NETDEV_CHANGEINFODATA},\n\t{\"BONDING_INFO\", NETDEV_BONDING_INFO},\n\t{\"PRECHANGEUPPER\", NETDEV_PRECHANGEUPPER},\n\t{\"CHANGELOWERSTATE\", NETDEV_CHANGELOWERSTATE},\n\t{\"UDP_TUNNEL_PUSH_INFO\", NETDEV_UDP_TUNNEL_PUSH_INFO},\n\t{\"UDP_TUNNEL_DROP_INFO\", NETDEV_UDP_TUNNEL_DROP_INFO},\n\t{\"CHANGE_TX_QUEUE_LEN\", NETDEV_CHANGE_TX_QUEUE_LEN},\n\t{\"CVLAN_FILTER_PUSH_INFO\", NETDEV_CVLAN_FILTER_PUSH_INFO},\n\t{\"CVLAN_FILTER_DROP_INFO\", NETDEV_CVLAN_FILTER_DROP_INFO},\n\t{\"SVLAN_FILTER_PUSH_INFO\", NETDEV_SVLAN_FILTER_PUSH_INFO},\n\t{\"SVLAN_FILTER_DROP_INFO\", NETDEV_SVLAN_FILTER_DROP_INFO},\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0))\n\t{\"OFFLOAD_XSTATS_ENABLE\", NETDEV_OFFLOAD_XSTATS_ENABLE},\n\t{\"OFFLOAD_XSTATS_DISABLE\", NETDEV_OFFLOAD_XSTATS_DISABLE},\n\t{\"OFFLOAD_XSTATS_REPORT_USED\", NETDEV_OFFLOAD_XSTATS_REPORT_USED},\n\t{\"OFFLOAD_XSTATS_REPORT_DELTA\", NETDEV_OFFLOAD_XSTATS_REPORT_DELTA},\n#endif\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0))\n\t{\"XDP_FEAT_CHANGE\", NETDEV_XDP_FEAT_CHANGE},\n#endif\n\t{NULL, 0}\n};\n\n/***\n* Table of virtual terminal (VT) event types.\n* Used as the `event` argument in `notifier.vterm` callbacks.\n* @table vt\n*   @tfield integer VT_ALLOCATE Virtual terminal is being allocated.\n*   @tfield integer VT_DEALLOCATE Virtual terminal is being deallocated.\n*   @tfield integer VT_WRITE Character is written to virtual terminal.\n*   @tfield integer VT_UPDATE Virtual terminal update event.\n*   @tfield integer VT_PREWRITE Before writing character to virtual terminal.\n* @within notifier\n*/\nstatic const lunatik_reg_t luanotifier_vt_evs[] = {\n\t{\"VT_ALLOCATE\", VT_ALLOCATE},\n\t{\"VT_DEALLOCATE\", VT_DEALLOCATE},\n\t{\"VT_WRITE\", VT_WRITE},\n\t{\"VT_UPDATE\", VT_UPDATE},\n\t{\"VT_PREWRITE\", VT_PREWRITE},\n\t{NULL, 0}\n};\nstatic const lunatik_namespace_t luanotifier_flags[] = {\n\t{\"notify\", luanotifier_notify},\n\t{\"kbd\", luanotifier_kbd},\n\t{\"netdev\", luanotifier_netdev},\n\t{\"vt\", luanotifier_vt_evs},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luanotifier_class = {\n\t.name = \"notifier\",\n\t.methods = luanotifier_mt,\n\t.release = luanotifier_release,\n\t.opt = LUNATIK_OPT_SINGLE,\n};\n\nstatic int luanotifier_new(lua_State *L, luanotifier_register_t register_fn, luanotifier_register_t unregister_fn,\n\tluanotifier_handler_t handler_fn)\n{\n\tlunatik_object_t *object;\n\tluanotifier_t *notifier;\n\n\tluaL_checktype(L, 1, LUA_TFUNCTION); /* callback */\n\n\tobject = lunatik_newobject(L, &luanotifier_class, sizeof(luanotifier_t), LUNATIK_OPT_NONE);\n\tnotifier = (luanotifier_t *)object->private;\n\n\tlunatik_setruntime(L, notifier, notifier);\n\tlunatik_getobject(notifier->runtime);\n\n\tnotifier->nb.notifier_call = luanotifier_call;\n\tnotifier->unregister = NULL;\n\tnotifier->running = false;\n\tnotifier->handler = handler_fn;\n\n\tlunatik_registerobject(L, 1, object);\n\n\tif (register_fn(&notifier->nb) != 0) {\n\t\tlunatik_unregisterobject(L, object);\n\t\tluaL_error(L, \"couldn't create notifier\");\n\t}\n\n\tnotifier->unregister = unregister_fn;\n\treturn 1; /* object */\n}\n\nLUNATIK_CLASSES(notifier, &luanotifier_class);\nLUNATIK_NEWLIB(notifier, luanotifier_lib, luanotifier_classes, luanotifier_flags);\n\nstatic int __init luanotifier_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luanotifier_exit(void)\n{\n}\n\nmodule_init(luanotifier_init);\nmodule_exit(luanotifier_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ring-0.io>\");\n\n"
  },
  {
    "path": "lib/luaprobe.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2025 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* kprobes mechanism.\n* This library allows Lua scripts to dynamically probe (instrument) kernel\n* functions or specific instruction addresses. Callbacks can be registered\n* to execute Lua code just before (pre-handler) and/or just after\n* (post-handler) the probed instruction is executed.\n*\n* @module probe\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/kprobes.h>\n#include <linux/string.h>\n\n#include <lunatik.h>\n\n/***\n* Represents a kernel probe (kprobe) object.\n* This is a userdata object returned by `probe.new()`. It encapsulates a\n* `struct kprobe` and the associated Lua callback handlers. This object\n* can be used to enable, disable, or stop (unregister) the probe.\n* @type probe\n*/\ntypedef struct luaprobe_s {\n\tstruct kprobe kp;\n\tlunatik_object_t *runtime;\n} luaprobe_t;\n\nstatic void (*luaprobe_showregs)(struct pt_regs *);\n\nstatic int luaprobe_dump(lua_State *L)\n{\n\tstruct pt_regs *regs = lua_touserdata(L, lua_upvalueindex(1));\n\tif (regs == NULL)\n\t\tluaL_error(L, LUNATIK_ERR_NULLPTR);\n\n\tluaprobe_showregs(regs);\n\treturn 0;\n}\n\nstatic int luaprobe_handler(lua_State *L, luaprobe_t *probe, const char *handler, struct pt_regs *regs)\n{\n\tstruct kprobe *kp = &probe->kp;\n\tconst char *symbol = kp->symbol_name;\n\n\tif (lunatik_getregistry(L, probe) != LUA_TTABLE) {\n\t\tpr_err(\"couldn't find probe table\\n\");\n\t\tgoto out;\n\t}\n\n\tlunatik_optcfunction(L, -1, handler, lunatik_nop);\n\n\tif (symbol != NULL)\n\t\tlua_pushstring(L, symbol);\n\telse\n\t\tlua_pushlightuserdata(L, kp->addr);\n\n\tlua_pushlightuserdata(L, regs);\n\tlua_pushcclosure(L, luaprobe_dump, 1);\n\tlua_pushvalue(L, -1); /* save dump() on the stack */\n\tlua_insert(L, -4); /* stack: dump, handler, symbol | addr, dump */\n\n\tif (lua_pcall(L, 2, 0, 0) != LUA_OK) /* handler(symbol | addr, dump) */\n\t\tpr_err(\"%s\\n\", lua_tostring(L, -1));\n\n\tlua_pushnil(L);\n\tlua_setupvalue(L, -2, 1); /* clean up regs */\nout:\n\treturn 0;\n}\n\nstatic int __kprobes luaprobe_pre_handler(struct kprobe *kp, struct pt_regs *regs)\n{\n\tluaprobe_t *probe = container_of(kp, luaprobe_t, kp);\n\tint ret;\n\n\tlunatik_run(probe->runtime, luaprobe_handler, ret, probe, \"pre\", regs);\n\treturn ret;\n}\n\nstatic void __kprobes luaprobe_post_handler(struct kprobe *kp, struct pt_regs *regs, unsigned long flags)\n{\n\tluaprobe_t *probe = container_of(kp, luaprobe_t, kp);\n\tint ret;\n\n\t/* flags always seems to be zero; see:https://docs.kernel.org/trace/kprobes.html#api-reference */\n\tlunatik_run(probe->runtime, luaprobe_handler, ret, probe, \"post\", regs);\n\t(void)ret;\n}\n\nstatic void luaprobe_delete(luaprobe_t *probe)\n{\n\tstruct kprobe *kp = &probe->kp;\n\tconst char *symbol_name = kp->symbol_name;\n\n\tif (kp->pre_handler != NULL) {\n\t\tdisable_kprobe(kp);\n\t\tkp->pre_handler = NULL;\n\t\tkp->post_handler = NULL;\n\t\tunregister_kprobe(kp);\n\t}\n\n\tif (symbol_name != NULL) {\n\t\tkfree(symbol_name);\n\t\tkp->symbol_name = NULL;\n\t}\n}\n\nstatic void luaprobe_release(void *private)\n{\n\tluaprobe_t *probe = (luaprobe_t *)private;\n\n\t/* device might have never been stopped */\n\tluaprobe_delete(probe);\n\tlunatik_putobject(probe->runtime);\n}\n\n/***\n* Stops and unregisters the probe.\n* This method is called on a probe object. Once stopped, the kprobe is\n* disabled and unregistered from the kernel, and its handlers will no longer\n* be called. The associated resources are released.\n* @function stop\n* @treturn nil\n* @usage my_probe_object:stop()\n*/\nstatic int luaprobe_stop(lua_State *L)\n{\n\tlunatik_object_t *object = lunatik_checkobject(L, 1);\n\tluaprobe_t *probe = (luaprobe_t *)object->private;\n\n\tlunatik_lock(object);\n\tluaprobe_delete(probe);\n\tlunatik_unlock(object);\n\n\tif (lunatik_toruntime(L) == probe->runtime)\n\t\tlunatik_unregisterobject(L, object);\n\treturn 0;\n}\n\n/***\n* Enables or disables an already registered probe.\n* This method is called on a probe object.\n* @function enable\n* @tparam boolean enable_flag If `true`, the probe is enabled. If `false`, the probe is disabled.\n*   A disabled probe remains registered but its handlers will not be executed.\n* @treturn nil\n* @raise Error if the probe was not properly registered or has been stopped.\n* @usage my_probe_object:enable(false) -- Disable the probe\n*/\nstatic int luaprobe_enable(lua_State *L)\n{\n\tlunatik_object_t *object = lunatik_checkobject(L, 1);\n\tluaprobe_t *probe = (luaprobe_t *)object->private;\n\tstruct kprobe *kp = &probe->kp;\n\tbool enable = lua_toboolean(L, 2);\n\n\tlunatik_lock(object);\n\tkp = &probe->kp;\n\n\tif (kp->pre_handler == NULL)\n\t\tgoto err;\n\n\tif (enable)\n\t\tenable_kprobe(kp);\n\telse\n\t\tdisable_kprobe(kp);\n\tlunatik_unlock(object);\n\treturn 0;\nerr:\n\tlunatik_unlock(object);\n\treturn luaL_argerror(L, 1, LUNATIK_ERR_NULLPTR);\n}\n\nstatic int luaprobe_new(lua_State *L);\n\n/***\n* Creates and registers a new kernel probe.\n* This function installs a kprobe at the specified kernel symbol or address.\n* Lua callback functions can be provided to execute when the probe hits.\n*\n* @function new\n* @tparam string|lightuserdata symbol_or_address kernel symbol name (string)\n*   or the absolute kernel address (lightuserdata) to probe.\n*   Suitable symbol names are typically those exported by the kernel or other modules,\n*   often visible in `/proc/kallsyms` (when viewed from userspace). The `syscall`\n*   module (e.g., `syscall.numbers.openat`) can be used to get system call numbers.\n*\n*   For system call addresses, you can use `syscall.address(syscall.numbers.openat)`.\n*   For other kernel symbols, `linux.lookup(\"symbol_name\")` can provide the address.\n*   Directly using addresses requires knowing the exact memory location, which can\n*   vary between kernel builds and is generally less portable than using symbol names\n*   or lookup functions.\n* @tparam table handlers A table containing the callback functions for the probe.\n*   It can have the following fields:\n*\n*   - `pre` (function, optional): A Lua function to be called just *before* the\n*     probed instruction is executed.\n*   - `post` (function, optional): A Lua function to be called just *after* the\n*     probed instruction has executed.\n*\n*   Both `pre` and `post` handlers receive two arguments:\n*\n*   1. `target` (string|lightuserdata): The symbol name or address that was probed.\n*   2. `dump_regs` (function): A closure that, when called without arguments,\n*      will print the current CPU registers and stack trace to the system log.\n*      This is useful for debugging.\n*\n* @treturn probe A new probe object. This object can be used to later `stop()` or `enable()`/`disable()` the probe.\n* @raise Error if the probe cannot be registered (e.g., symbol not found, memory allocation failure, invalid address).\n* @within probe\n*/\nstatic const luaL_Reg luaprobe_lib[] = {\n\t{\"new\", luaprobe_new},\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg luaprobe_mt[] = {\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"stop\", luaprobe_stop},\n\t{\"enable\", luaprobe_enable},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luaprobe_class = {\n\t.name = \"probe\",\n\t.methods = luaprobe_mt,\n\t.release = luaprobe_release,\n\t.opt = LUNATIK_OPT_SINGLE,\n};\n\nstatic int luaprobe_new(lua_State *L)\n{\n\tlunatik_object_t *object = lunatik_newobject(L, &luaprobe_class, sizeof(luaprobe_t), LUNATIK_OPT_NONE);\n\tluaprobe_t *probe = (luaprobe_t *)object->private;\n\tstruct kprobe *kp = &probe->kp;\n\tint ret;\n\n\tmemset(probe, 0, sizeof(luaprobe_t));\n\n\tlunatik_setruntime(L, probe, probe);\n\tlunatik_getobject(probe->runtime);\n\n\tif (lua_islightuserdata(L, 1))\n\t\tkp->addr = lua_touserdata(L, 1);\n\telse {\n\t\tsize_t symbol_len;\n\t\tconst char *symbol_name = luaL_checklstring(L, 1, &symbol_len);\n\n\t\tif ((kp->symbol_name = kstrndup(symbol_name, symbol_len, lunatik_gfp(probe->runtime))) == NULL)\n\t\t\tluaL_error(L, \"out of memory\");\n\t}\n\n\tluaL_checktype(L, 2, LUA_TTABLE); /* handlers */\n\n\tkp->pre_handler = luaprobe_pre_handler;\n\tkp->post_handler = luaprobe_post_handler;\n\n\tif ((ret = register_kprobe(kp)) != 0) {\n\t\tkp->pre_handler = NULL; /* shouldn't unregister on release() */\n\t\tluaL_error(L, \"failed to register probe (%d)\", ret);\n\t}\n\n\tlunatik_registerobject(L, 2, object);\n\treturn 1; /* object */\n}\n\nLUNATIK_CLASSES(probe, &luaprobe_class);\nLUNATIK_NEWLIB(probe, luaprobe_lib, luaprobe_classes, NULL);\n\nstatic int __init luaprobe_init(void)\n{\n\tif ((luaprobe_showregs = (void (*)(struct pt_regs *))lunatik_lookup(\"show_regs\")) == NULL)\n\t\treturn -ENXIO;\n\treturn 0;\n}\n\nstatic void __exit luaprobe_exit(void)\n{\n}\n\nmodule_init(luaprobe_init);\nmodule_exit(luaprobe_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ring-0.io>\");\n\n"
  },
  {
    "path": "lib/luarcu.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2025 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* RCU-synchronized hash table.\n* Provides a concurrent hash table using Read-Copy-Update (RCU) synchronization.\n* Reads are lockless; writes are serialized. Keys are strings, values can be\n* booleans, integers, lunatik objects, or `nil` (to delete an entry).\n*\n* See `examples/shared.lua` for a practical example.\n* @module rcu\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/spinlock.h>\n#include <linux/hashtable.h>\n#include <linux/random.h>\n\n#include <lunatik.h>\n\n#include \"luarcu.h\"\n\n#define LUARCU_MAXKEY\t(LUAL_BUFFERSIZE)\n\ntypedef struct luarcu_entry_s {\n\tlunatik_value_t value;\n\tstruct hlist_node hlist;\n\tstruct rcu_head rcu;\n\tchar key[];\n} luarcu_entry_t;\n\n/***\n* RCU hash table object.\n* Supports table-like access via `__index` and `__newindex`.\n* @type rcu_table\n* @usage\n*  local t = rcu.table()\n*  t[\"key\"] = true        -- boolean\n*  t[\"n\"]   = 42          -- integer\n*  t[\"obj\"] = data.new(8) -- object\n*  print(t[\"key\"])        -- true\n*  t[\"key\"] = nil         -- delete\n*/\n\ntypedef struct luarcu_table_s {\n\tsize_t size;\n\tunsigned int seed;\n\tstruct hlist_head hlist[];\n} luarcu_table_t;\n\n#define luarcu_sizeoftable(size)\t(sizeof(luarcu_table_t) + sizeof(struct hlist_head) * (size))\n\n/* size is always a power of 2; thus `size - 1` turns on every valid bit */\n#define luarcu_mask(table)\t\t\t((table)->size - 1)\n#define luarcu_hash(table, key, keylen)\t\t(lunatik_hash((key), (keylen), (table)->seed) & luarcu_mask(table))\n#define luarcu_seed()\t\t\t\tget_random_u32()\n\n#define luarcu_entry(ptr, pos)\t\thlist_entry_safe(rcu_dereference_raw(ptr), typeof(*(pos)), hlist)\n#define luarcu_foreach(table, bucket, n, pos)\t\t\t\t\t\t\t\\\n\tfor (bucket = 0, pos = NULL; pos == NULL && bucket < (table)->size; bucket++)\t\t\\\n\t\tfor (pos = luarcu_entry(hlist_first_rcu(&(table)->hlist[bucket]), pos);\t\t\\\n\t\t\tpos && ({ n = luarcu_entry(hlist_next_rcu(&(pos)->hlist), pos); 1; });\t\\\n\t\t\tpos = n)\n\nstatic int luarcu_table(lua_State *L);\n\nstatic inline luarcu_entry_t *luarcu_lookup(luarcu_table_t *table, unsigned int index,\n\tconst char *key, size_t keylen)\n{\n\tluarcu_entry_t *entry;\n\n\thlist_for_each_entry_rcu(entry, table->hlist + index, hlist)\n\t\tif (strncmp(entry->key, key, keylen) == 0)\n\t\t\treturn entry;\n\treturn NULL;\n}\n\nstatic luarcu_entry_t *luarcu_newentry(const char *key, size_t keylen, lunatik_value_t *value)\n{\n\tluarcu_entry_t *entry;\n\n\tif (keylen >= LUARCU_MAXKEY || (entry = kmalloc(struct_size(entry, key, keylen + 1), GFP_ATOMIC)) == NULL)\n\t\treturn NULL;\n\n\tstrncpy(entry->key, key, keylen);\n\tentry->key[keylen] = '\\0';\n\tentry->value = *value;\n\tif (lunatik_isuserdata(value))\n\t\tlunatik_getobject(value->object);\n\treturn entry;\n}\n\nstatic inline void luarcu_free(luarcu_entry_t *entry)\n{\n\tif (lunatik_isuserdata(&entry->value))\n\t\tlunatik_putobject(entry->value.object);\n\tkfree_rcu(entry, rcu);\n}\n\nLUNATIK_OBJECTCHECKER(luarcu_checktable, luarcu_table_t *);\n\nvoid luarcu_getvalue(lunatik_object_t *table, const char *key, size_t keylen, lunatik_value_t *value)\n{\n\tluarcu_table_t *_table = (luarcu_table_t *)table->private;\n\tunsigned int index = luarcu_hash(_table, key, keylen);\n\tluarcu_entry_t *entry;\n\n\trcu_read_lock();\n\tif ((entry = luarcu_lookup(_table, index, key, keylen)) == NULL)\n\t\tvalue->type = LUA_TNIL;\n\telse {\n\t\t*value = entry->value;\n\t\tif (lunatik_isuserdata(value))\n\t\t\tlunatik_getobject(value->object);\n\t}\n\trcu_read_unlock();\n}\nEXPORT_SYMBOL(luarcu_getvalue);\n\nint luarcu_setvalue(lunatik_object_t *table, const char *key, size_t keylen, lunatik_value_t *value)\n{\n\tint ret = 0;\n\tluarcu_table_t *tab = (luarcu_table_t *)table->private;\n\tluarcu_entry_t *old;\n\tunsigned int index = luarcu_hash(tab, key, keylen);\n\n\tlunatik_lock(table);\n\trcu_read_lock();\n\told = luarcu_lookup(tab, index, key, keylen);\n\trcu_read_unlock();\n\tif (value->type != LUA_TNIL) {\n\t\tluarcu_entry_t *new = luarcu_newentry(key, keylen, value);\n\t\tif (new == NULL) {\n\t\t\tret = -ENOMEM;\n\t\t\tgoto unlock;\n\t\t}\n\n\t\tif (!old)\n\t\t\thlist_add_head_rcu(&new->hlist, tab->hlist + index);\n\t\telse {\n\t\t\thlist_replace_rcu(&old->hlist, &new->hlist);\n\t\t\tluarcu_free(old);\n\t\t}\n\t}\n\telse if (old) {\n\t\thlist_del_rcu(&old->hlist);\n\t\tluarcu_free(old);\n\t}\nunlock:\n\tlunatik_unlock(table);\n\treturn ret;\n}\nEXPORT_SYMBOL(luarcu_setvalue);\n\n/***\n* Retrieves a value from the table (RCU-protected, lockless).\n* @function __index\n* @tparam string key\n* @treturn boolean|integer|object|nil\n*/\nstatic int luarcu_index(lua_State *L)\n{\n\tlunatik_object_t *table = lunatik_checkobject(L, 1);\n\tsize_t keylen;\n\tconst char *key = luaL_checklstring(L, 2, &keylen);\n\tlunatik_value_t value;\n\n\tluarcu_getvalue(table, key, keylen, &value);\n\tlunatik_pushvalue(L, &value);\n\treturn 1; /* value */\n}\n\n/***\n* Sets or removes a value in the table (serialized).\n* Assigning `nil` removes the entry.\n* @function __newindex\n* @tparam string key\n* @tparam boolean|integer|object|nil value\n* @raise Error on memory allocation failure.\n*/\nstatic int luarcu_newindex(lua_State *L)\n{\n\tlunatik_object_t *table = lunatik_checkobject(L, 1);\n\tsize_t keylen;\n\tconst char *key = luaL_checklstring(L, 2, &keylen);\n\n\tlunatik_value_t value;\n\tlunatik_checkvalue(L, 3, &value);\n\tif (luarcu_setvalue(table, key, keylen, &value) < 0)\n\t\tluaL_error(L, \"not enough memory\");\n\treturn 0;\n}\n\nstatic void luarcu_release(void *private)\n{\n\tluarcu_table_t *table = (luarcu_table_t *)private;\n\tunsigned int bucket;\n\tluarcu_entry_t *n, *entry;\n\n\tluarcu_foreach(table, bucket, n, entry) {\n\t\thlist_del_rcu(&entry->hlist);\n\t\tluarcu_free(entry);\n\t}\n}\n\nstatic inline void luarcu_inittable(luarcu_table_t *table, size_t size)\n{\n\t__hash_init(table->hlist, size);\n\ttable->size = size;\n\ttable->seed = luarcu_seed();\n}\n\nstatic int luarcu_map_handle(lua_State *L)\n{\n\tconst char *key = (const char *)lua_touserdata(L, 2);\n\tlunatik_value_t *value = (lunatik_value_t *)lua_touserdata(L, 3);\n\n\tBUG_ON(!key || !value);\n\n\tlua_pop(L, 2); /* key, value */\n\n\tlua_pushstring(L, key);\n\tlunatik_pushvalue(L, value);\n\tlua_call(L, 2, 0);\n\n\treturn 0;\n}\n\nstatic inline int luarcu_map_call(lua_State *L, int cb, const char *key, lunatik_value_t *value)\n{\n\tlua_pushcfunction(L, luarcu_map_handle);\n\tlua_pushvalue(L, cb);\n\tlua_pushlightuserdata(L, (void *)key);\n\tlua_pushlightuserdata(L, value);\n\n\treturn lua_pcall(L, 3, 0, 0); /* handle(cb, key, value) */\n}\n\n/***\n* Iterates over the table calling `callback(key, value)` for each entry.\n* Iteration is RCU-protected; order is not guaranteed.\n* @function map\n* @tparam function callback `function(key, value)`.\n* @raise Error if callback raises.\n*/\nstatic int luarcu_map(lua_State *L)\n{\n\tluarcu_table_t *table = luarcu_checktable(L, 1);\n\tunsigned int bucket;\n\tluarcu_entry_t *n, *entry;\n\n\tluaL_checktype(L, 2, LUA_TFUNCTION); /* cb */\n\tlua_remove(L, 1); /* table */\n\n\trcu_read_lock();\n\tluarcu_foreach(table, bucket, n, entry) {\n\t\tchar key[LUARCU_MAXKEY];\n\n\t\tstrncpy(key, entry->key, LUARCU_MAXKEY);\n\t\tkey[LUARCU_MAXKEY - 1] = '\\0';\n\t\tlunatik_value_t value = entry->value;\n\t\tif (lunatik_isuserdata(&value))\n\t\t\tlunatik_getobject(value.object);\n\n\t\trcu_read_unlock();\n\t\tint ret = luarcu_map_call(L, 1, key, &value);\n\t\tif (ret != LUA_OK)\n\t\t\tlua_error(L);\n\t\trcu_read_lock();\n\t}\n\trcu_read_unlock();\n\treturn 0;\n}\n\nstatic const struct luaL_Reg luarcu_lib[] = {\n\t{\"table\", luarcu_table},\n\t{\"map\", luarcu_map},\n\t{NULL, NULL}\n};\n\nstatic const struct luaL_Reg luarcu_mt[] = {\n\t{\"__newindex\", luarcu_newindex},\n\t{\"__index\", luarcu_index},\n\t{\"__gc\", lunatik_deleteobject},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luarcu_class = {\n\t.name = \"rcu\",\n\t.methods = luarcu_mt,\n\t.release = luarcu_release,\n\t.opt = LUNATIK_OPT_SOFTIRQ,\n};\n\nlunatik_object_t *luarcu_newtable(size_t size, lunatik_opt_t opt)\n{\n\tlunatik_object_t *object;\n\n\tsize = roundup_pow_of_two(size);\n\tif ((object = lunatik_createobject(&luarcu_class, luarcu_sizeoftable(size), opt)) != NULL)\n\t\tluarcu_inittable((luarcu_table_t *)object->private, size);\n\treturn object;\n}\nEXPORT_SYMBOL(luarcu_newtable);\n\n/***\n* Creates a new RCU hash table.\n* @function table\n* @tparam[opt=256] integer size Number of hash buckets (rounded up to power of two).\n* @treturn rcu_table\n* @usage\n*   local t = rcu.table()      -- 256 buckets (default)\n*   local t = rcu.table(8192)  -- 8192 buckets\n* @within rcu\n*/\nstatic int luarcu_table(lua_State *L)\n{\n\tsize_t size = roundup_pow_of_two(luaL_optinteger(L, 1, LUARCU_DEFAULT_SIZE));\n\tlunatik_object_t *object = lunatik_newobject(L, &luarcu_class, luarcu_sizeoftable(size), LUNATIK_OPT_NONE);\n\n\tluarcu_inittable((luarcu_table_t *)object->private, size);\n\treturn 1; /* object */\n}\n\nLUNATIK_CLASSES(rcu, &luarcu_class);\nLUNATIK_NEWLIB(rcu, luarcu_lib, luarcu_classes, NULL);\n\nstatic int __init luarcu_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luarcu_exit(void)\n{\n}\n\nmodule_init(luarcu_init);\nmodule_exit(luarcu_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ringzero.com.br>\");\n\n"
  },
  {
    "path": "lib/luarcu.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef luarcu_h\n#define luarcu_h\n\n#define LUARCU_DEFAULT_SIZE\t(256)\n\nlunatik_object_t *luarcu_newtable(size_t size, lunatik_opt_t opt);\nvoid luarcu_getvalue(lunatik_object_t *table, const char *key, size_t keylen, lunatik_value_t *value);\nint luarcu_setvalue(lunatik_object_t *table, const char *key, size_t keylen, lunatik_value_t *value);\n\nstatic inline lunatik_object_t *luarcu_getobject(lunatik_object_t *table, const char *key, size_t keylen)\n{\n\tlunatik_value_t value;\n\tluarcu_getvalue(table, key, keylen, &value);\n\treturn lunatik_isuserdata(&value) ? value.object : NULL;\n}\n\nstatic inline int luarcu_setobject(lunatik_object_t *table, const char *key, size_t keylen, lunatik_object_t *obj)\n{\n\tlunatik_value_t value = {.type = LUA_TUSERDATA, .object = obj};\n\treturn luarcu_setvalue(table, key, keylen, &value);\n}\n\n#endif\n\n"
  },
  {
    "path": "lib/luasignal.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2025-2026 L Venkata Subramanyam <202301280@dau.ac.in>\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#include <linux/module.h>\n#include <linux/sched/signal.h>\n#include <linux/pid.h>\n#include <linux/signal.h>\n#include <linux/errno.h>\n\n#include <lunatik.h>\n\n/***\n* POSIX Signals\n* @module signal\n*/\n\n/***\n* Modifies signal mask for current task.\n*\n* @function sigmask\n* @tparam integer sig Signal number.\n* @tparam[opt] integer cmd SIG_BLOCK (0, default) or SIG_UNBLOCK (1).\n* @raise Error if the signal is invalid or the operation is not permitted.\n*/\nstatic int luasignal_sigmask(lua_State *L)\n{\n\tsigset_t newmask;\n\tsigemptyset(&newmask);\n\n\tint signum = luaL_checkinteger(L, 1);\n\tint cmd = luaL_optinteger(L, 2, 0);\n\n\tsigaddset(&newmask, signum);\n\n\tlunatik_try(L, sigprocmask, cmd, &newmask, NULL);\n\treturn 0;\n}\n\n/***\n* Checks if the current task has pending signals.\n*\n* @function sigpending\n* @treturn boolean\n*/\nstatic int luasignal_sigpending(lua_State *L)\n{\n\tlua_pushboolean(L, signal_pending(current));\n\treturn 1;\n}\n\n/***\n* Checks signal state for current task.\n*\n* @function sigstate\n* @tparam integer sig Signal number.\n* @tparam[opt] string state `\"blocked\"` (default), `\"pending\"`, or `\"allowed\"`.\n* @treturn boolean\n* @usage\n* signal.sigstate(15) -- check if SIGTERM is blocked\n* signal.sigstate(signal.flags.TERM, \"pending\")\n*/\nstatic int luasignal_sigstate(lua_State *L)\n{\n\tenum sigstate_cmd {\n\t\tSIGSTATE_BLOCKED,\n\t\tSIGSTATE_PENDING,\n\t\tSIGSTATE_ALLOWED,\n\t};\n\n\tconst char *const sigstate_opts[] = {\n\t\t[SIGSTATE_BLOCKED] = \"blocked\",\n\t\t[SIGSTATE_PENDING] = \"pending\",\n\t\t[SIGSTATE_ALLOWED] = \"allowed\",\n\t};\n\n\tint signum = luaL_checkinteger(L, 1);\n\tenum sigstate_cmd cmd = (enum sigstate_cmd)luaL_checkoption(L, 2, \"blocked\", sigstate_opts);\n\n\tbool result;\n\tswitch (cmd) {\n\tcase SIGSTATE_BLOCKED:\n\t\tresult = sigismember(&current->blocked, signum);\n\t\tbreak;\n\tcase SIGSTATE_PENDING:\n\t\tresult = sigismember(&current->pending.signal, signum);\n\t\tbreak;\n\tcase SIGSTATE_ALLOWED:\n\t\tresult = !sigismember(&current->blocked, signum);\n\t\tbreak;\n\t}\n\tlua_pushboolean(L, result);\n\treturn 1;\n}\n\n/***\n* Sends a signal to a process.\n*\n* @function kill\n* @tparam integer pid Target process ID.\n* @tparam[opt] integer sig Signal to send (default: `signal.flags.KILL`).\n* @treturn boolean `true` on success.\n* @raise Error if the process is not found or the operation is not permitted.\n*/\nstatic int luasignal_kill(lua_State *L)\n{\n\tpid_t nr = (pid_t)luaL_checkinteger(L, 1);\n\tint sig = luaL_optinteger(L, 2, SIGKILL);\n\tstruct pid *pid = find_get_pid(nr);\n\n\tif (pid == NULL)\n\t\tlunatik_throw(L, ESRCH);\n\n\tint ret = kill_pid(pid, sig, 1);\n\tput_pid(pid);\n\n\tif (ret)\n\t\tlunatik_throw(L, -ret);\n\n\tlua_pushboolean(L, true);\n\treturn 1;\n}\n\n/***\n* Signal constants.\n* @table flags\n*/\nstatic const lunatik_reg_t luasignal_flags[] = {\n\t{\"HUP\", SIGHUP},\n\t{\"INT\", SIGINT},\n\t{\"QUIT\", SIGQUIT},\n\t{\"ILL\", SIGILL},\n\t{\"TRAP\", SIGTRAP},\n\t{\"ABRT\", SIGABRT},\n\t{\"BUS\", SIGBUS},\n\t{\"FPE\", SIGFPE},\n\t{\"KILL\", SIGKILL},\n\t{\"USR1\", SIGUSR1},\n\t{\"SEGV\", SIGSEGV},\n\t{\"USR2\", SIGUSR2},\n\t{\"PIPE\", SIGPIPE},\n\t{\"ALRM\", SIGALRM},\n\t{\"TERM\", SIGTERM},\n#ifdef SIGSTKFLT\n\t{\"STKFLT\", SIGSTKFLT},\n#endif\n\t{\"CHLD\", SIGCHLD},\n\t{\"CONT\", SIGCONT},\n\t{\"STOP\", SIGSTOP},\n\t{\"TSTP\", SIGTSTP},\n\t{\"TTIN\", SIGTTIN},\n\t{\"TTOU\", SIGTTOU},\n\t{\"URG\", SIGURG},\n\t{\"XCPU\", SIGXCPU},\n\t{\"XFSZ\", SIGXFSZ},\n\t{\"VTALRM\", SIGVTALRM},\n\t{\"PROF\", SIGPROF},\n\t{\"WINCH\", SIGWINCH},\n\t{\"IO\", SIGIO},\n\t{\"PWR\", SIGPWR},\n\t{\"SYS\", SIGSYS},\n\t{NULL, 0}\n};\n\nstatic const lunatik_namespace_t luasignal_namespaces[] = {\n\t{\"flags\", luasignal_flags},\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg luasignal_lib[] = {\n\t{\"sigmask\", luasignal_sigmask},\n\t{\"sigpending\", luasignal_sigpending},\n\t{\"sigstate\", luasignal_sigstate},\n\t{\"kill\", luasignal_kill},\n\t{NULL, NULL}\n};\n\nLUNATIK_NEWLIB(signal, luasignal_lib, NULL, luasignal_namespaces);\n\nstatic int __init luasignal_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luasignal_exit(void)\n{\n}\n\nmodule_init(luasignal_init);\nmodule_exit(luasignal_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"L Venkata Subramanyam <202301280@dau.ac.in>\");\n\n"
  },
  {
    "path": "lib/luaskb.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Linux socket buffer interface.\n* @module skb\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/skbuff.h>\n#include <linux/if_vlan.h>\n#include <linux/netdevice.h>\n#include <linux/tcp.h>\n#include <linux/udp.h>\n#include <net/ip.h>\n#include <net/ip6_checksum.h>\n\n#include \"luaskb.h\"\n\nLUNATIK_PRIVATECHECKER(luaskb_check, luaskb_t *,\n\tluaL_argcheck(L, private->skb != NULL, ix, \"skb is not set\");\n);\n\n#define luaskb_pushoptinteger(L, cond, val)\t\\\n\t((cond) ? lua_pushinteger(L, val) : lua_pushnil(L))\n\n/* FRAGLIST GSO skbs hold segments in frag_list; skb_copy refuses to copy\n * them (ambiguous semantics: copy the container or the segments?). */\n#ifdef SKB_GSO_FRAGLIST\n#define luaskb_checkfraglist(L, lskb, ix)\t\t\t\t\\\n\tluaL_argcheck(L, !(skb_shinfo((lskb)->skb)->gso_type &\t\t\\\n\t\tSKB_GSO_FRAGLIST), (ix), \"FRAGLIST GSO skbs cannot be copied\")\n#else\n#define luaskb_checkfraglist(L, lskb, ix)\n#endif\n\n#define luaskb_csum4(skb, iph, iphlen)\t\t\t\t\t\\\n\tcsum_tcpudp_magic((iph)->saddr, (iph)->daddr,\t\t\t\\\n\t\tntohs((iph)->tot_len) - (iphlen), (iph)->protocol,\t\\\n\t\tskb_checksum((skb), (iphlen),\t\t\t\t\\\n\t\t\tntohs((iph)->tot_len) - (iphlen), 0))\n\n#define luaskb_csum6(skb, ip6h)\t\t\t\t\t\t\\\n\tcsum_ipv6_magic(&(ip6h)->saddr, &(ip6h)->daddr,\t\t\t\\\n\t\tntohs((ip6h)->payload_len), (ip6h)->nexthdr,\t\t\\\n\t\tskb_checksum((skb), skb_transport_offset(skb),\t\t\\\n\t\t\tntohs((ip6h)->payload_len), 0))\n\n/***\n* @function __len\n* @treturn integer skb length in bytes\n*/\nstatic int luaskb_len(lua_State *L)\n{\n\tluaskb_t *lskb = luaskb_check(L, 1);\n\tlua_pushinteger(L, lskb->skb->len);\n\treturn 1;\n}\n\n/***\n* @function ifindex\n* @treturn integer network interface index, or nil if not available\n*/\nstatic int luaskb_ifindex(lua_State *L)\n{\n\tluaskb_t *lskb = luaskb_check(L, 1);\n\tstruct net_device *dev = lskb->skb->dev;\n\tluaskb_pushoptinteger(L, dev, dev->ifindex);\n\treturn 1;\n}\n\n/***\n* @function vlan\n* @treturn integer VLAN tag ID, or nil if not present\n*/\nstatic int luaskb_vlan(lua_State *L)\n{\n\tluaskb_t *lskb = luaskb_check(L, 1);\n\tstruct sk_buff *skb = lskb->skb;\n\tluaskb_pushoptinteger(L, skb_vlan_tag_present(skb), skb_vlan_tag_get_id(skb));\n\treturn 1;\n}\n\n#define luaskb_checklinearize(L, lskb, ix)\t\\\n\tluaL_argcheck(L, skb_linearize((lskb)->skb) == 0, (ix), \"skb linearization failed\")\n\n/***\n* @function data\n* @tparam[opt] string layer \"net\" (default, L3) or \"mac\" (L2, includes MAC header)\n* @treturn data\n* @raise if linearization fails, MAC header is not set, or layer is invalid\n*/\nstatic int luaskb_data(lua_State *L)\n{\n\tluaskb_t *lskb = luaskb_check(L, 1);\n\tluaskb_checklinearize(L, lskb, 1);\n\n\tlunatik_object_t *data = lskb->data;\n\tstruct sk_buff *skb = lskb->skb;\n\tstatic const char *const layers[] = {\"net\", \"mac\", NULL};\n\tbool mac = luaL_checkoption(L, 2, \"net\", layers);\n\n\tvoid *ptr = skb->data;\n\tsize_t size = skb_headlen(skb);\n\n\tif (mac) {\n\t\tluaL_argcheck(L, skb_mac_header_was_set(skb), 2, \"MAC header not set\");\n\t\tptr  += skb_mac_offset(skb);\n\t\tsize += skb_mac_header_len(skb);\n\t}\n\n\tif (data)\n\t\tlunatik_getregistry(L, data); /* push data */\n\telse /* copy: allocate on demand; release() has no lua_State to unregister */\n\t\tdata = luadata_new(L, LUNATIK_OPT_SINGLE); /* push data */\n\tluadata_reset(data, ptr, size, LUADATA_OPT_NONE);\n\treturn 1;\n}\n\n/***\n* Expands (skb_put) or shrinks (skb_trim) the skb data area.\n* @function resize\n* @tparam integer n desired size in bytes\n* @raise if insufficient tailroom for expansion\n*/\nstatic int luaskb_resize(lua_State *L)\n{\n\tluaskb_t *lskb = luaskb_check(L, 1);\n\tstruct sk_buff *skb = lskb->skb;\n\tsize_t new_size = (size_t)luaL_checkinteger(L, 2);\n\tsize_t cur_size = skb_headlen(skb);\n\n\tif (new_size > cur_size) {\n\t\tsize_t needed = new_size - cur_size;\n\t\tluaL_argcheck(L, skb_tailroom(skb) >= needed, 2, \"insufficient tailroom\");\n\t\tskb_put(skb, needed);\n\t}\n\telse if (new_size < cur_size)\n\t\tskb_trim(skb, new_size);\n\treturn 0;\n}\n\nstatic inline void luaskb_csum(struct sk_buff *skb, u8 proto, __sum16 csum)\n{\n\tif (proto == IPPROTO_UDP)\n\t\tudp_hdr(skb)->check = csum;\n\telse if (proto == IPPROTO_TCP)\n\t\ttcp_hdr(skb)->check = csum;\n}\n\n/***\n* Recomputes IP and transport-layer (TCP/UDP) checksums.\n* @function checksum\n*/\nstatic int luaskb_checksum(lua_State *L)\n{\n\tluaskb_t *lskb = luaskb_check(L, 1);\n\tstruct sk_buff *skb = lskb->skb;\n\n\tif (skb->protocol == htons(ETH_P_IP)) {\n\t\tstruct iphdr *iph = ip_hdr(skb);\n\t\tunsigned int iphlen = ip_hdrlen(skb);\n\t\tip_send_check(iph);\n\t\tluaskb_csum(skb, iph->protocol, 0);\n\t\tluaskb_csum(skb, iph->protocol, luaskb_csum4(skb, iph, iphlen));\n\t}\n\telse if (skb->protocol == htons(ETH_P_IPV6)) {\n\t\tstruct ipv6hdr *ip6h = ipv6_hdr(skb);\n\t\tluaskb_csum(skb, ip6h->nexthdr, 0);\n\t\tluaskb_csum(skb, ip6h->nexthdr, luaskb_csum6(skb, ip6h));\n\t}\n\treturn 0;\n}\n\n/***\n* Forwards the skb out through its ingress device.\n* @function forward\n* @raise if skb has no device, MAC header is not set, or clone fails\n*/\nstatic int luaskb_forward(lua_State *L)\n{\n\tluaskb_t *lskb = luaskb_check(L, 1);\n\tstruct sk_buff *skb = lskb->skb;\n\tstruct net_device *dev = skb->dev;\n\n\tluaL_argcheck(L, dev != NULL, 1, \"skb has no device\");\n\tluaL_argcheck(L, skb_mac_header_was_set(skb), 1, \"MAC header not set\");\n\n\tstruct sk_buff *nskb = lunatik_checknull(L, skb_clone(skb, GFP_ATOMIC));\n\n\tskb_push(nskb, nskb->data - skb_mac_header(nskb));\n\tdev_queue_xmit(nskb);\n\treturn 0;\n}\n\nstatic int luaskb_copy(lua_State *L);\n\nstatic void luaskb_release(void *private)\n{\n\tluaskb_t *lskb = (luaskb_t *)private;\n\tif (lskb->skb)\n\t\tkfree_skb(lskb->skb);\n\tif (lskb->data)\n\t\tluadata_close(lskb->data);\n}\n\nstatic const luaL_Reg luaskb_lib[] = {\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg luaskb_mt[] = {\n\t{\"__gc\",     lunatik_deleteobject},\n\t{\"__len\",    luaskb_len},\n\t{\"ifindex\",  luaskb_ifindex},\n\t{\"vlan\",     luaskb_vlan},\n\t{\"data\",     luaskb_data},\n\t{\"resize\",   luaskb_resize},\n\t{\"checksum\", luaskb_checksum},\n\t{\"forward\",  luaskb_forward},\n\t{\"copy\",     luaskb_copy},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luaskb_class = {\n\t.name    = \"skb\",\n\t.methods = luaskb_mt,\n\t.release = luaskb_release,\n\t.opt = LUNATIK_OPT_SOFTIRQ | LUNATIK_OPT_SINGLE,\n};\n\n/***\n* Returns an independent copy of the skb with its own data buffer.\n* The skb is linearized before copying to avoid failures on fragmented skbs\n* (e.g. bridged traffic with paged data).\n* @function copy\n* @treturn skb\n* @raise if skb is FRAGLIST GSO, linearization fails, or copy allocation fails\n*/\nstatic int luaskb_copy(lua_State *L)\n{\n\tluaskb_t *lskb = luaskb_check(L, 1);\n\tluaskb_checkfraglist(L, lskb, 1);\n\tluaskb_checklinearize(L, lskb, 1);\n\n\tlunatik_object_t *object = lunatik_newobject(L, &luaskb_class, sizeof(luaskb_t), LUNATIK_OPT_NONE);\n\tluaskb_t *copy = (luaskb_t *)object->private;\n\tcopy->skb = lunatik_checknull(L, skb_copy(lskb->skb, GFP_ATOMIC));\n\treturn 1;\n}\n\nlunatik_object_t *luaskb_new(lua_State *L)\n{\n\tlunatik_require(L, \"skb\");\n\tlunatik_object_t *object = lunatik_newobject(L, &luaskb_class, sizeof(luaskb_t), LUNATIK_OPT_NONE);\n\tluaskb_t *lskb = (luaskb_t *)object->private;\n\tlskb->data = luadata_new(L, LUNATIK_OPT_SINGLE);\n\tlunatik_getobject(lskb->data);\n\tlunatik_register(L, -1, lskb->data);\n\tlua_pop(L, 1);\n\treturn object;\n}\nEXPORT_SYMBOL(luaskb_new);\n\nLUNATIK_CLASSES(skb, &luaskb_class);\nLUNATIK_NEWLIB(skb, luaskb_lib, luaskb_classes, NULL);\n\nstatic int __init luaskb_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luaskb_exit(void)\n{\n}\n\nmodule_init(luaskb_init);\nmodule_exit(luaskb_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ringzero.com.br>\");\nMODULE_AUTHOR(\"Carlos Carvalho <carloslack@gmail.com>\");\nMODULE_AUTHOR(\"Mohammad Shehar Yaar Tausif <sheharyaar48@gmail.com>\");\n\n"
  },
  {
    "path": "lib/luaskb.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef luaskb_h\n#define luaskb_h\n\n#include <lunatik.h>\n#include \"luadata.h\"\n\ntypedef struct {\n\tstruct sk_buff *skb;\n\tlunatik_object_t *data;\n} luaskb_t;\n\n#define luaskb_reset(object, skb)\t(((luaskb_t *)(object)->private)->skb = (skb))\n\nstatic inline void luaskb_clear(lunatik_object_t *object)\n{\n\tluaskb_t *lskb = (luaskb_t *)object->private;\n\tluadata_clear(lskb->data);\n\tlskb->skb = NULL;\n}\n\nlunatik_object_t *luaskb_new(lua_State *L);\n\n#define luaskb_attach(L, obj, field)\tlunatik_attach(L, obj, field, luaskb_new)\n\n#endif\n\n"
  },
  {
    "path": "lib/luaskel.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n\n#include <lunatik.h>\n\ntypedef struct luaskel_s {\n\tint unused;\n} luaskel_t;\n\nLUNATIK_PRIVATECHECKER(luaskel_check, luaskel_t *);\n\nstatic int luaskel_nop(lua_State *L)\n{\n\tluaskel_t *skel = luaskel_check(L, 1)\n\t(void)skel; /* do nothing */\n\treturn 0;\n}\n\nstatic void luaskel_release(void *private)\n{\n\tluaskel_t *skel = (luaskel_t *)private;\n\t(void)skel; /* do nothing */\n}\n\nstatic int luaskel_new(lua_State *L);\n\nstatic const luaL_Reg luaskel_lib[] = {\n\t{\"new\", luaskel_new},\n\t{\"nop\", luaskel_nop},\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg luaskel_mt[] = {\n\t{\"nop\", luaskel_nop},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luaskel_class = {\n\t.name = \"skel\",\n\t.methods = luaskel_mt,\n\t.release = luaskel_release,\n\t.opt = LUNATIK_OPT_MONITOR,\n};\n\nstatic int luaskel_new(lua_State *L)\n{\n\tlunatik_object_t *object = lunatik_newobject(L, &luaskel_class, sizeof(luaskel_t), LUNATIK_OPT_NONE);\n\tluaskel_t *skel = (luaskel_t *)object->private;\n\t(void)skel; /* do nothing */\n\treturn 1; /* object */\n}\n\nLUNATIK_CLASSES(skel, &luaskel_class);\nLUNATIK_NEWLIB(skel, luaskel_lib, luaskel_classes, NULL);\n\nstatic int __init luaskel_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luaskel_exit(void)\n{\n}\n\nmodule_init(luaskel_init);\nmodule_exit(luaskel_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ringzero.com.br>\");\n\n"
  },
  {
    "path": "lib/luasocket.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Low-level Lua interface for kernel networking sockets.\n* This library provides support for creating and managing various types of\n* sockets within the Linux kernel, enabling network communication directly\n* from Lua scripts running in kernel space. It is inspired by\n* [Chengzhi Tan](https://github.com/tcz717)'s\n* [GSoC project](https://summerofcode.withgoogle.com/archive/2018/projects/5993341447569408).\n*\n* It allows operations such as creating sockets, binding, listening, connecting,\n* sending, and receiving data. The library also exposes constants for address\n* families, socket types, IP protocols, and message flags.\n*\n* For higher-level IPv4 TCP/UDP socket operations with string-based IP addresses\n* (e.g., \"127.0.0.1\"), consider using the `socket.inet` library.\n*\n* @module socket\n* @see socket.inet\n*/\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/version.h>\n#include <linux/string.h>\n#include <linux/net.h>\n#include <linux/un.h>\n#include <net/sock.h>\n#if (LINUX_VERSION_CODE <= KERNEL_VERSION(6, 1, 0))\n#include <linux/l2tp.h>\n#endif\n\n#include <lunatik.h>\n\n#define luasocket_msgaddr(msg, addr, size)\t\\\ndo {\t\t\t\t\t\t\\\n\tmsg.msg_namelen = size;\t\t\t\\\n\tmsg.msg_name = &addr;\t\t\t\\\n} while (0)\n\n#define LUASOCKET_ADDRMAX\t(sizeof_field(struct sockaddr_storage, __data))\n\nstatic int luasocket_new(lua_State *L);\nstatic int luasocket_accept(lua_State *L);\n\n#define LUASOCKET_ISUNIX(family)\t((family) == AF_UNIX || (family) == AF_LOCAL)\n\nstatic size_t luasocket_checkaddr(lua_State *L, struct socket *socket, struct sockaddr_storage *addr, int ix)\n{\n\taddr->ss_family = socket->sk->sk_family;\n\tif (addr->ss_family == AF_INET) {\n\t\tstruct sockaddr_in *addr_in = (struct sockaddr_in *)addr;\n\t\taddr_in->sin_addr.s_addr = htonl((u32)luaL_checkinteger(L, ix));\n\t\taddr_in->sin_port = htons((u16)lunatik_checkinteger(L, ix + 1, 0, U16_MAX));\n\t\treturn sizeof(struct sockaddr_in);\n\t}\n#ifdef CONFIG_UNIX\n\telse if (LUASOCKET_ISUNIX(addr->ss_family)) {\n\t\tsize_t len;\n\t\tstruct sockaddr_un *addr_un = (struct sockaddr_un *)addr;\n\t\tconst char *addr_data = luaL_checklstring(L, ix, &len);\n\t\tluaL_argcheck(L, len + 1 <= UNIX_PATH_MAX, ix, \"out of bounds\");\n\t\tstrncpy(addr_un->sun_path, addr_data, len);\n\t\taddr_un->sun_path[len] = '\\0';\n\t\treturn sizeof(struct sockaddr_un);\n\t}\n#endif\n\telse if (addr->ss_family == AF_PACKET) {\n\t\tstruct sockaddr_ll *addr_ll = (struct sockaddr_ll *)addr;\n\t\taddr_ll->sll_protocol = htons((u16)lunatik_checkinteger(L, ix, 0, U16_MAX));\n\t\taddr_ll->sll_ifindex = (int)lunatik_checkinteger(L, ix + 1, 0, INT_MAX);;\n\t\treturn sizeof(struct sockaddr_ll);\n\t}\n\telse {\n\t\tsize_t len;\n\t\tconst char *addr_data = luaL_checklstring(L, ix, &len);\n\t\tluaL_argcheck(L, len <= LUASOCKET_ADDRMAX, ix, \"out of bounds\");\n\t\tmemcpy(addr->__data, addr_data, len);\n\t\treturn sizeof(struct sockaddr_storage);\n\t}\n}\n\nstatic int luasocket_pushaddr(lua_State *L, struct sockaddr_storage *addr)\n{\n\tint n;\n\tif (addr->ss_family == AF_INET) {\n\t\tstruct sockaddr_in *addr_in = (struct sockaddr_in *)addr;\n\t\tlua_pushinteger(L, (lua_Integer)ntohl(addr_in->sin_addr.s_addr));\n\t\tlua_pushinteger(L, (lua_Integer)ntohs(addr_in->sin_port));\n\t\tn = 2;\n\t}\n#ifdef CONFIG_UNIX\n\telse if (LUASOCKET_ISUNIX(addr->ss_family)) {\n\t\tstruct sockaddr_un *addr_un = (struct sockaddr_un *)addr;\n\t\tlua_pushstring(L, addr_un->sun_path);\n\t\tn = 1;\n\t}\n#endif\n\telse {\n\t\tlua_pushlstring(L, (const char *)addr->__data, LUASOCKET_ADDRMAX);\n\t\tn = 1;\n\t}\n\treturn n;\n}\n\nLUNATIK_PRIVATECHECKER(luasocket_check, struct socket *);\n\n#define luasocket_setmsg(m)\t\tmemset(&(m), 0, sizeof(m))\n\n/***\n* Sends a message through the socket.\n*\n* For connection-oriented sockets (`SOCK_STREAM`), `addr` and `port` are usually omitted\n* as the connection is already established.\n* For connectionless sockets (`SOCK_DGRAM`), `addr` and `port` (if applicable for the\n* address family) specify the destination.\n*\n* @function send\n* @tparam string message message to send.\n* @tparam[opt] integer|string addr destination address.\n*\n* - For `AF_INET` (IPv4) sockets: An integer representing the IPv4 address (e.g., from `net.aton()`).\n* - For other address families (e.g., `AF_PACKET`): A packed string representing the destination address\n*   (e.g., MAC address for `AF_PACKET`). The exact format depends on the family.\n* @tparam[opt] integer port destination port number (required if `addr` is an IPv4 address for `AF_INET`).\n* @treturn integer number of bytes sent.\n* @raise Error if the send operation fails or if address parameters are incorrect for the socket type.\n* @usage\n*   -- For a connected TCP socket:\n*   local bytes_sent = tcp_conn_sock:send(\"Hello, server!\")\n*\n*   -- For a UDP socket (sending to 192.168.1.100, port 1234):\n*   local bytes_sent = udp_sock:send(\"UDP packet\", net.aton(\"192.168.1.100\"), 1234)\n* @see net.aton\n*/\nstatic int luasocket_send(lua_State *L)\n{\n\tstruct socket *socket = luasocket_check(L, 1);\n\tsize_t len;\n\tstruct kvec vec;\n\tstruct msghdr msg;\n\tstruct sockaddr_storage addr;\n\tint nargs = lua_gettop(L);\n\tint ret;\n\n\tluasocket_setmsg(msg);\n\n\tvec.iov_base = (void *)luaL_checklstring(L, 2, &len);\n\tvec.iov_len = len;\n\n\tif (unlikely(nargs >= 3)) {\n\t\tsize_t size = luasocket_checkaddr(L, socket, &addr, 3);\n\t\tluasocket_msgaddr(msg, addr, size);\n\t}\n\n\tlunatik_tryret(L, ret, kernel_sendmsg, socket, &msg, &vec, 1, len);\n\tlua_pushinteger(L, ret);\n\treturn 1;\n}\n\n/***\n* Receives a message from the socket.\n*\n* @function receive\n* @tparam integer length maximum number of bytes to receive.\n* @tparam[opt=0] integer flags Optional message flags (e.g., `socket.msg.PEEK`).\n*   See the `socket.msg` table for available flags. These can be OR'd together.\n* @tparam[opt=false] boolean from If `true`, the function also returns the sender's address\n*   and port (for `AF_INET`). This is typically used with connectionless sockets (`SOCK_DGRAM`).\n* @treturn string received message (as a string of bytes).\n* @treturn[opt] integer|string addr If `from` is true, the sender's address.\n*   - For `AF_INET`: An integer representing the IPv4 address (can be converted with `net.ntoa()`).\n*   - For other families: A packed string representing the sender's address.\n* @treturn[opt] integer port If `from` is true and the family is `AF_INET`, the sender's port number.\n* @raise Error if the receive operation fails.\n* @usage\n*   -- For a connected TCP socket:\n*   local data = tcp_conn_sock:receive(1024)\n*   if data then print(\"Received:\", data) end\n*\n*   -- For a UDP socket, getting sender info:\n*   local data, sender_ip_int, sender_port = udp_sock:receive(1500, 0, true)\n*   if data then print(\"Received from \" .. net.ntoa(sender_ip_int) .. \":\" .. sender_port .. \": \" .. data) end\n* @see socket.msg\n* @see net.ntoa\n*/\nstatic int luasocket_receive(lua_State *L)\n{\n\tstruct socket *socket = luasocket_check(L, 1);\n\tsize_t len = (size_t)luaL_checkinteger(L, 2);\n\tluaL_Buffer B;\n\tstruct kvec vec;\n\tstruct msghdr msg;\n\tstruct sockaddr_storage addr;\n\tint flags = luaL_optinteger(L, 3, 0);\n\tint from = lua_toboolean(L, 4);\n\tint ret;\n\n\tluasocket_setmsg(msg);\n\n\tvec.iov_base = (void *)luaL_buffinitsize(L, &B, len);\n\tvec.iov_len = len;\n\n\tif (unlikely(from))\n\t\tluasocket_msgaddr(msg, addr, sizeof(addr));\n\n\tlunatik_tryret(L, ret, kernel_recvmsg, socket, &msg, &vec, 1, len, flags);\n\tluaL_pushresultsize(&B, ret);\n\n\treturn unlikely(from) ? luasocket_pushaddr(L, (struct sockaddr_storage *)msg.msg_name) + 1 : 1;\n}\n\n/***\n* Binds the socket to a local address.\n* This is typically used on the server side before calling `listen()` or on\n* connectionless sockets to specify a local port/interface for receiving.\n*\n* @function bind\n* @tparam integer|string addr local address to bind to. Interpretation depends on `socket.sk.sk_family`:\n*\n*   - `AF_INET` (IPv4): An integer representing the IPv4 address (e.g., from `net.aton()`).\n*     Use `0` (or `net.aton(\"0.0.0.0\")`) to bind to all available interfaces.\n*     The `port` argument is also required.\n*   - `AF_PACKET`: An integer representing the ethernet protocol in host byte order\n*     (e.g., `0x0003` for `ETH_P_ALL`, `0x88CC` for `ETH_P_LLDP`)\n*     The `port` argument is also required.\n*   - Other families: A packed string directly representing parts of the family-specific address structure.\n*\n* @tparam[opt] integer port local port or interface index.\n*   - `AF_INET`: TCP/UDP port number.\n*   - `AF_PACKET`: Network interface index (e.g., from `linux.ifindex(\"eth0\")`).\n*\n* @treturn nil\n* @raise Error if the bind operation fails (e.g., address already in use, invalid address).\n* @usage\n*   -- Bind TCP/IPv4 socket to localhost, port 8080\n*   tcp_server_sock:bind(net.aton(\"127.0.0.1\"), 8080)\n*\n*   -- Bind AF_PACKET socket to protocol `ETH_P_LLDP` on a specific interface\n*   af_packet_sock:bind(0x88CC, linux.ifindex(\"eth0\"))\n* @see net.aton\n* @see linux.ifindex\n*/\nstatic int luasocket_bind(lua_State *L)\n{\n\tstruct socket *socket = luasocket_check(L, 1);\n\tstruct sockaddr_storage addr;\n\tsize_t size = luasocket_checkaddr(L, socket, &addr, 2);\n#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 19, 0))\n\tlunatik_try(L, kernel_bind, socket, (struct sockaddr *)&addr, size);\n#else\n\tlunatik_try(L, kernel_bind, socket, (struct sockaddr_unsized *)&addr, size);\n#endif\n\treturn 0;\n}\n\n/***\n* Puts a connection-oriented socket into the listening state.\n* This is required for server sockets (e.g., `SOCK_STREAM`) to be able to\n* accept incoming connections.\n*\n* @function listen\n* @tparam[opt] integer backlog maximum length of the queue for pending connections.\n*   If omitted, a system-dependent default (e.g., `SOMAXCONN`) is used.\n* @treturn nil\n* @raise Error if the listen operation fails (e.g., socket not bound, invalid state).\n* @usage\n*   tcp_server_sock:listen(10)\n*/\nstatic int luasocket_listen(lua_State *L)\n{\n\tstruct socket *socket = luasocket_check(L, 1);\n\tint backlog = luaL_optinteger(L, 2, SOMAXCONN);\n\n\tlunatik_try(L, kernel_listen, socket, backlog);\n\treturn 0;\n}\n\n/***\n* Initiates a connection on a socket.\n* This is typically used by client sockets to establish a connection to a server.\n* For datagram sockets, this sets the default destination address for `send` and\n* the only address from which datagrams are received.\n*\n* @function connect\n* @tparam integer|string addr destination address to connect to.\n*   Interpretation depends on `socket.sk.sk_family`:\n*\n*   - `AF_INET` (IPv4): An integer representing the IPv4 address (e.g., from `net.aton()`).\n*     The `port` argument is also required.\n*   - Other families: A packed string representing the family-specific destination address.\n* @tparam[opt] integer port destination port number (required and used only if the family is `AF_INET`).\n* @tparam[opt=0] integer flags Optional connection flags.\n* @treturn nil\n* @raise Error if the connect operation fails (e.g., connection refused, host unreachable).\n* @usage\n*   tcp_client_sock:connect(net.aton(\"192.168.1.100\"), 80)\n*/\nstatic int luasocket_connect(lua_State *L)\n{\n\tstruct socket *socket = luasocket_check(L, 1);\n\tstruct sockaddr_storage addr;\n\tint nargs = lua_gettop(L);\n\tsize_t size = luasocket_checkaddr(L, socket, &addr, 2);\n\tint flags = luaL_optinteger(L, nargs >= 4 ? 4 : 3, 0);\n\n#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 19, 0))\n\tlunatik_try(L, kernel_connect, socket, (struct sockaddr *)&addr, size, flags);\n#else\n\tlunatik_try(L, kernel_connect, socket, (struct sockaddr_unsized *)&addr, size, flags);\n#endif\n\treturn 0;\n}\n\n#define LUASOCKET_NEWGETTER(what) \t\t\t\t\t\t\\\nstatic int luasocket_get##what(lua_State *L)\t\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\t\\\n\tstruct socket *socket = luasocket_check(L, 1);\t\t\t\t\\\n\tstruct sockaddr_storage addr;\t\t\t\t\t\t\\\n\tlunatik_try(L, kernel_get##what, socket, (struct sockaddr *)&addr);\t\\\n\treturn luasocket_pushaddr(L, &addr);\t\t\t\t\t\\\n}\n\n/***\n* Gets the local address to which the socket is bound.\n*\n* @function getsockname\n* @treturn integer|string addr local address.\n*\n* - For `AF_INET`: An integer representing the IPv4 address (can be converted with `net.ntoa()`).\n* - For other families: A packed string representing the local address.\n* @treturn[opt] integer port If the family is `AF_INET`, the local port number.\n* @raise Error if the operation fails.\n* @usage\n*   local local_ip_int, local_port = my_socket:getsockname()\n*   if my_socket.sk.sk_family == socket.af.INET then print(\"Bound to \" .. net.ntoa(local_ip_int) .. \":\" .. local_port) end\n*/\nLUASOCKET_NEWGETTER(sockname);\n\n/***\n* Gets the address of the peer to which the socket is connected.\n* This is typically used with connection-oriented sockets after a connection\n* has been established, or with connectionless sockets after `connect()` has\n* been called to set a default peer.\n*\n* @function getpeername\n* @treturn integer|string addr peer's address.\n*\n* - For `AF_INET`: An integer representing the IPv4 address (can be converted with `net.ntoa()`).\n* - For other families: A packed string representing the peer's address.\n* @treturn[opt] integer port If the family is `AF_INET`, the peer's port number.\n* @raise Error if the operation fails (e.g., socket not connected).\n* @usage\n*   local peer_ip_int, peer_port = connected_socket:getpeername()\n*   if connected_socket.sk.sk_family == socket.af.INET then print(\"Connected to \" .. net.ntoa(peer_ip_int) .. \":\" .. peer_port) end\n*/\nLUASOCKET_NEWGETTER(peername);\n\n/***\n* Closes the socket.\n* This shuts down the socket for both reading and writing and releases\n* associated kernel resources.\n* This method is also called automatically when the socket object is garbage collected\n* or via Lua 5.4's to-be-closed mechanism.\n*\n* @function close\n* @treturn nil\n*/\nstatic void luasocket_release(void *private)\n{\n\tstruct socket *sock = (struct socket *)private;\n\tkernel_sock_shutdown(sock, SHUT_RDWR);\n\tsock_release(sock);\n}\n\nstatic const luaL_Reg luasocket_lib[] = {\n\t{\"new\", luasocket_new},\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg luasocket_mt[] = {\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"__close\", lunatik_closeobject},\n\t{\"close\", lunatik_closeobject},\n\t{\"send\", luasocket_send},\n\t{\"receive\", luasocket_receive},\n\t{\"bind\", luasocket_bind},\n\t{\"listen\", luasocket_listen},\n\t{\"accept\", luasocket_accept},\n\t{\"connect\", luasocket_connect},\n\t{\"getsockname\", luasocket_getsockname},\n\t{\"getpeername\", luasocket_getpeername},\n\t{NULL, NULL}\n};\n\n/***\n* Table of address family constants.\n* These constants are used in `socket.new()` to specify the\n* communication domain of the socket.\n* (Constants from `<linux/socket.h>`)\n* @table af\n*\n*   @tfield integer UNSPEC Unspecified.\n*   @tfield integer UNIX Unix domain sockets.\n*   @tfield integer LOCAL POSIX name for AF_UNIX.\n*   @tfield integer INET Internet IP Protocol (IPv4).\n*   @tfield integer AX25 Amateur Radio AX.25.\n*   @tfield integer IPX Novell IPX.\n*   @tfield integer APPLETALK AppleTalk DDP.\n*   @tfield integer NETROM Amateur Radio NET/ROM.\n*   @tfield integer BRIDGE Multiprotocol bridge.\n*   @tfield integer ATMPVC ATM PVCs.\n*   @tfield integer X25 Reserved for X.25 project.\n*   @tfield integer INET6 IP version 6.\n*   @tfield integer ROSE Amateur Radio X.25 PLP.\n*   @tfield integer DECnet Reserved for DECnet project.\n*   @tfield integer NETBEUI Reserved for 802.2LLC project.\n*   @tfield integer SECURITY Security callback pseudo AF.\n*   @tfield integer KEY PF_KEY key management API.\n*   @tfield integer NETLINK Netlink.\n*   @tfield integer ROUTE Alias to emulate 4.4BSD.\n*   @tfield integer PACKET Packet family.\n*   @tfield integer ASH Ash.\n*   @tfield integer ECONET Acorn Econet.\n*   @tfield integer ATMSVC ATM SVCs.\n*   @tfield integer RDS RDS sockets.\n*   @tfield integer SNA Linux SNA Project.\n*   @tfield integer IRDA IRDA sockets.\n*   @tfield integer PPPOX PPPoX sockets.\n*   @tfield integer WANPIPE Wanpipe API Sockets.\n*   @tfield integer LLC Linux LLC.\n*   @tfield integer IB Native InfiniBand address.\n*   @tfield integer MPLS MPLS.\n*   @tfield integer CAN Controller Area Network.\n*   @tfield integer TIPC TIPC sockets.\n*   @tfield integer BLUETOOTH Bluetooth sockets.\n*   @tfield integer IUCV IUCV sockets.\n*   @tfield integer RXRPC RxRPC sockets.\n*   @tfield integer ISDN mISDN sockets.\n*   @tfield integer PHONET Phonet sockets.\n*   @tfield integer IEEE802154 IEEE802154 sockets.\n*   @tfield integer CAIF CAIF sockets.\n*   @tfield integer ALG Algorithm sockets.\n*   @tfield integer NFC NFC sockets.\n*   @tfield integer VSOCK vSockets.\n*   @tfield integer KCM Kernel Connection Multiplexor.\n*   @tfield integer QIPCRTR Qualcomm IPC Router.\n*   @tfield integer SMC SMCP sockets (PF_SMC reuses AF_INET).\n*   @tfield integer XDP XDP sockets.\n*   @tfield integer MCTP Management component transport protocol (Kernel 5.15+).\n*   @tfield integer MAX Maximum value for AF constants.\n*\n* @within socket\n*/\nstatic const lunatik_reg_t luasocket_af[] = {\n\t{\"UNSPEC\", AF_UNSPEC},\n\t{\"UNIX\", AF_UNIX},\n\t{\"LOCAL\", AF_LOCAL},\n\t{\"INET\", AF_INET},\n\t{\"AX25\", AF_AX25},\n\t{\"IPX\", AF_IPX},\n\t{\"APPLETALK\", AF_APPLETALK},\n\t{\"NETROM\", AF_NETROM},\n\t{\"BRIDGE\", AF_BRIDGE},\n\t{\"ATMPVC\", AF_ATMPVC},\n\t{\"X25\", AF_X25},\n\t{\"INET6\", AF_INET6},\n\t{\"ROSE\", AF_ROSE},\n\t{\"DECnet\", AF_DECnet},\n\t{\"NETBEUI\", AF_NETBEUI},\n\t{\"SECURITY\", AF_SECURITY},\n\t{\"KEY\", AF_KEY},\n\t{\"NETLINK\", AF_NETLINK},\n\t{\"ROUTE\", AF_ROUTE},\n\t{\"PACKET\", AF_PACKET},\n\t{\"ASH\", AF_ASH},\n\t{\"ECONET\", AF_ECONET},\n\t{\"ATMSVC\", AF_ATMSVC},\n\t{\"RDS\", AF_RDS},\n\t{\"SNA\", AF_SNA},\n\t{\"IRDA\", AF_IRDA},\n\t{\"PPPOX\", AF_PPPOX},\n\t{\"WANPIPE\", AF_WANPIPE},\n\t{\"LLC\", AF_LLC},\n\t{\"IB\", AF_IB},\n\t{\"MPLS\", AF_MPLS},\n\t{\"CAN\", AF_CAN},\n\t{\"TIPC\", AF_TIPC},\n\t{\"BLUETOOTH\", AF_BLUETOOTH},\n\t{\"IUCV\", AF_IUCV},\n\t{\"RXRPC\", AF_RXRPC},\n\t{\"ISDN\", AF_ISDN},\n\t{\"PHONET\", AF_PHONET},\n\t{\"IEEE802154\", AF_IEEE802154},\n\t{\"CAIF\", AF_CAIF},\n\t{\"ALG\", AF_ALG},\n\t{\"NFC\", AF_NFC},\n\t{\"VSOCK\", AF_VSOCK},\n\t{\"KCM\", AF_KCM},\n\t{\"QIPCRTR\", AF_QIPCRTR},\n\t{\"SMC\", AF_SMC},\n\t{\"XDP\", AF_XDP},\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)\n\t{\"MCTP\", AF_MCTP},\n#endif\n\t{\"MAX\", AF_MAX},\n\t{NULL, 0}\n};\n\n/***\n* Table of message flags.\n* These flags can be used with `sock:receive()` and `sock:send()`\n* to modify their behavior.\n* (Constants from `<linux/socket.h>`)\n* @table msg\n*\n*   @tfield integer OOB Process out-of-band data.\n*   @tfield integer PEEK Peek at incoming message without removing it from the queue.\n*   @tfield integer DONTROUTE Don't use a gateway to send out the packet.\n*   @tfield integer TRYHARD Synonym for DONTROUTE for DECnet.\n*   @tfield integer CTRUNC Control data lost before delivery.\n*   @tfield integer PROBE Do not send data, only probe path (e.g., for MTU discovery).\n*   @tfield integer TRUNC Normal data lost before delivery.\n*   @tfield integer DONTWAIT Enables non-blocking operation.\n*   @tfield integer EOR Terminates a record (if supported by the protocol).\n*   @tfield integer WAITALL Wait for full request or error.\n*   @tfield integer FIN FIN segment.\n*   @tfield integer SYN SYN segment.\n*   @tfield integer CONFIRM Confirm path validity (e.g., ARP entry).\n*   @tfield integer RST RST segment.\n*   @tfield integer ERRQUEUE Fetch message from error queue.\n*   @tfield integer NOSIGNAL Do not generate SIGPIPE.\n*   @tfield integer MORE Sender will send more data.\n*   @tfield integer WAITFORONE For `recvmmsg()`: block until at least one packet is available.\n*   @tfield integer SENDPAGE_NOPOLICY Internal: `sendpage()` should not apply policy.\n*   @tfield integer BATCH For `sendmmsg()`: more messages are coming.\n*   @tfield integer EOF End of file.\n*   @tfield integer NO_SHARED_FRAGS Internal: `sendpage()` page fragments are not shared.\n*   @tfield integer SENDPAGE_DECRYPTED Internal: `sendpage()` page may carry plain text and require encryption.\n*   @tfield integer ZEROCOPY Use user data in kernel path for zero-copy transmit.\n*   @tfield integer FASTOPEN Send data in TCP SYN (TCP Fast Open).\n*   @tfield integer CMSG_CLOEXEC Set close-on-exec for file descriptor received via SCM_RIGHTS.\n*\n* @within socket\n*/\nstatic const lunatik_reg_t luasocket_msg[] = {\n\t{\"OOB\", MSG_OOB},\n\t{\"PEEK\", MSG_PEEK},\n\t{\"DONTROUTE\", MSG_DONTROUTE},\n\t{\"TRYHARD\", MSG_TRYHARD},\n\t{\"CTRUNC\", MSG_CTRUNC},\n\t{\"PROBE\", MSG_PROBE},\n\t{\"TRUNC\", MSG_TRUNC},\n\t{\"DONTWAIT\", MSG_DONTWAIT},\n\t{\"EOR\", MSG_EOR},\n\t{\"WAITALL\", MSG_WAITALL},\n\t{\"FIN\", MSG_FIN},\n\t{\"SYN\", MSG_SYN},\n\t{\"CONFIRM\", MSG_CONFIRM},\n\t{\"RST\", MSG_RST},\n\t{\"ERRQUEUE\", MSG_ERRQUEUE},\n\t{\"NOSIGNAL\", MSG_NOSIGNAL},\n\t{\"MORE\", MSG_MORE},\n\t{\"WAITFORONE\", MSG_WAITFORONE},\n\t{\"SENDPAGE_NOPOLICY\", MSG_SENDPAGE_NOPOLICY},\n#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)\n\t{\"SENDPAGE_NOTLAST\", MSG_SENDPAGE_NOTLAST},\n#endif\n\t{\"BATCH\", MSG_BATCH},\n\t{\"EOF\", MSG_EOF},\n\t{\"NO_SHARED_FRAGS\", MSG_NO_SHARED_FRAGS},\n\t{\"SENDPAGE_DECRYPTED\", MSG_SENDPAGE_DECRYPTED},\n\t{\"ZEROCOPY\", MSG_ZEROCOPY},\n\t{\"FASTOPEN\", MSG_FASTOPEN},\n\t{\"CMSG_CLOEXEC\", MSG_CMSG_CLOEXEC},\n\t{NULL, 0}\n};\n\n/***\n* Table of socket type and flag constants.\n* Socket types are used in `socket.new()`. Socket flags can be used in\n* `socket.new()` (by ORing with type), `sock:accept()`, and `sock:connect()`.\n* (Constants from `<linux/net.h>`)\n* @table sock\n*   @tfield integer STREAM Stream socket (e.g., TCP).\n*   @tfield integer DGRAM Datagram socket (e.g., UDP).\n*   @tfield integer RAW Raw socket.\n*   @tfield integer RDM Reliably-delivered message socket.\n*   @tfield integer SEQPACKET Sequential packet socket.\n*   @tfield integer DCCP Datagram Congestion Control Protocol socket.\n*   @tfield integer PACKET Linux specific packet socket (deprecated in favor of AF_PACKET).\n*   @tfield integer CLOEXEC Atomically set the close-on-exec flag for the new socket.\n*   @tfield integer NONBLOCK Atomically set the O_NONBLOCK flag for the new socket.\n* @within socket\n*/\nstatic const lunatik_reg_t luasocket_sock[] = {\n\t{\"STREAM\", SOCK_STREAM},\n\t{\"DGRAM\", SOCK_DGRAM},\n\t{\"RAW\", SOCK_RAW},\n\t{\"RDM\", SOCK_RDM},\n\t{\"SEQPACKET\", SOCK_SEQPACKET},\n\t{\"DCCP\", SOCK_DCCP},\n\t{\"PACKET\", SOCK_PACKET},\n\t{\"CLOEXEC\", SOCK_CLOEXEC},\n\t{\"NONBLOCK\", SOCK_NONBLOCK},\n\t{NULL, 0}\n};\n\n/***\n* Table of IP protocol constants.\n* These are used in `socket.new()` to specify the protocol for `AF_INET` or `AF_INET6` sockets.\n* (Constants from `<uapi/linux/in.h>`)\n* @table ipproto\n*   @tfield integer IP Dummy protocol for TCP. (Typically 0)\n*   @tfield integer ICMP Internet Control Message Protocol.\n*   @tfield integer IGMP Internet Group Management Protocol.\n*   @tfield integer IPIP IPIP tunnels.\n*   @tfield integer TCP Transmission Control Protocol.\n*   @tfield integer EGP Exterior Gateway Protocol.\n*   @tfield integer PUP PUP protocol.\n*   @tfield integer UDP User Datagram Protocol.\n*   @tfield integer IDP XNS IDP protocol.\n*   @tfield integer TP SO Transport Protocol Class 4.\n*   @tfield integer DCCP Datagram Congestion Control Protocol.\n*   @tfield integer IPV6 IPv6-in-IPv4 tunnelling.\n*   @tfield integer RSVP RSVP Protocol.\n*   @tfield integer GRE Cisco GRE tunnels.\n*   @tfield integer ESP Encapsulation Security Payload protocol.\n*   @tfield integer AH Authentication Header protocol.\n*   @tfield integer MTP Multicast Transport Protocol.\n*   @tfield integer BEETPH IP option pseudo header for BEET.\n*   @tfield integer ENCAP Encapsulation Header.\n*   @tfield integer PIM Protocol Independent Multicast.\n*   @tfield integer COMP Compression Header Protocol.\n*   @tfield integer L2TP Layer Two Tunneling Protocol.\n*   @tfield integer SCTP Stream Control Transport Protocol.\n*   @tfield integer UDPLITE UDP-Lite (RFC 3828).\n*   @tfield integer MPLS MPLS in IP (RFC 4023).\n*   @tfield integer ETHERNET Ethernet-within-IPv6 Encapsulation (Kernel 5.6+).\n*   @tfield integer RAW Raw IP packets.\n*   @tfield integer MPTCP Multipath TCP connection (Kernel 5.6+).\n* @within socket\n*/\nstatic const lunatik_reg_t luasocket_ipproto[] = {\n\t{\"IP\", IPPROTO_IP},\n\t{\"ICMP\", IPPROTO_ICMP},\n\t{\"IGMP\", IPPROTO_IGMP},\n\t{\"IPIP\", IPPROTO_IPIP},\n\t{\"TCP\", IPPROTO_TCP},\n\t{\"EGP\", IPPROTO_EGP},\n\t{\"PUP\", IPPROTO_PUP},\n\t{\"UDP\", IPPROTO_UDP},\n\t{\"IDP\", IPPROTO_IDP},\n\t{\"TP\", IPPROTO_TP},\n\t{\"DCCP\", IPPROTO_DCCP},\n\t{\"IPV6\", IPPROTO_IPV6},\n\t{\"RSVP\", IPPROTO_RSVP},\n\t{\"GRE\", IPPROTO_GRE},\n\t{\"ESP\", IPPROTO_ESP},\n\t{\"AH\", IPPROTO_AH},\n\t{\"MTP\", IPPROTO_MTP},\n\t{\"BEETPH\", IPPROTO_BEETPH},\n\t{\"ENCAP\", IPPROTO_ENCAP},\n\t{\"PIM\", IPPROTO_PIM},\n\t{\"COMP\", IPPROTO_COMP},\n\t{\"L2TP\", IPPROTO_L2TP},\n\t{\"SCTP\", IPPROTO_SCTP},\n\t{\"UDPLITE\", IPPROTO_UDPLITE},\n\t{\"MPLS\", IPPROTO_MPLS},\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)\n\t{\"ETHERNET\", IPPROTO_ETHERNET},\n#endif\n\t{\"RAW\", IPPROTO_RAW},\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)\n\t{\"MPTCP\", IPPROTO_MPTCP},\n#endif\n\t{NULL, 0}\n};\n\nstatic const lunatik_namespace_t luasocket_flags[] = {\n\t{\"af\", luasocket_af},\n\t{\"msg\", luasocket_msg},\n\t{\"sock\", luasocket_sock},\n\t{\"ipproto\", luasocket_ipproto},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luasocket_class = {\n\t.name = \"socket\",\n\t.methods = luasocket_mt,\n\t.release = luasocket_release,\n\t.opt = LUNATIK_OPT_MONITOR | LUNATIK_OPT_EXTERNAL,\n};\n\n#define luasocket_newsocket(L)\t\t(lunatik_newobject((L), &luasocket_class, 0, LUNATIK_OPT_NONE))\n#define luasocket_psocket(object)\t((struct socket **)&object->private)\n\n/***\n* Accepts a connection on a listening socket.\n* This function is used with connection-oriented sockets (e.g., `SOCK_STREAM`)\n* that have been put into the listening state by `sock:listen()`.\n*\n* @function accept\n* @tparam socket self listening socket object.\n* @tparam[opt=0] integer flags Optional flags to apply to the newly accepted socket\n*   (e.g., `socket.sock.NONBLOCK`, `socket.sock.CLOEXEC`).\n* @treturn socket A new socket object representing the accepted connection.\n* @raise Error if the accept operation fails.\n*/\nstatic int luasocket_accept(lua_State *L)\n{\n\tstruct socket *socket = luasocket_check(L, 1);\n\tint flags = luaL_optinteger(L, 2, 0);\n\tlunatik_object_t *object = luasocket_newsocket(L);\n\n\tlunatik_try(L, kernel_accept, socket, luasocket_psocket(object), flags);\n\treturn 1; /* object */\n}\n\n/***\n* Creates a new socket object.\n* This function is the primary way to create a socket.\n*\n* @function new\n* @tparam integer family address family (e.g., `socket.af.INET`).\n* @tparam integer type socket type (e.g., `socket.sock.STREAM`).\n* @tparam integer protocol protocol (e.g., `socket.ipproto.TCP`).\n*   For `AF_PACKET` sockets, `protocol` is typically an `ETH_P_*` value in network byte order\n*   (e.g., `linux.hton16(0x0003)` for `ETH_P_ALL`).\n* @treturn socket A new socket object.\n* @raise Error if socket creation fails.\n* @usage\n*   -- TCP/IPv4 socket\n*   local tcp_sock = socket.new(socket.af.INET, socket.sock.STREAM, socket.ipproto.TCP)\n* @see socket.af\n* @see socket.sock\n* @see socket.ipproto\n* @within socket\n*/\nstatic int luasocket_new(lua_State *L)\n{\n\tint family = luaL_checkinteger(L, 1);\n\tint type = luaL_checkinteger(L, 2);\n\tint proto = luaL_checkinteger(L, 3);\n\tlunatik_object_t *object = luasocket_newsocket(L);\n\n\tlunatik_try(L, sock_create_kern, &init_net, family, type, proto, luasocket_psocket(object));\n\treturn 1; /* object */\n}\n\nLUNATIK_CLASSES(socket, &luasocket_class);\nLUNATIK_NEWLIB(socket, luasocket_lib, luasocket_classes, luasocket_flags);\n\nstatic int __init luasocket_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luasocket_exit(void)\n{\n}\n\nmodule_init(luasocket_init);\nmodule_exit(luasocket_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ringzero.com.br>\");\n\n"
  },
  {
    "path": "lib/luasyscall.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2024-2025 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Accessing kernel system call information.\n* This library allows retrieving the kernel address of a system call given its\n* number, and provides a table of system call numbers accessible by their names (see `syscall.numbers`).\n* This is particularly useful for kernel probing (see `probe`)\n* or other low-level kernel interactions.\n*\n* @module syscall\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/kprobes.h>\n\n#include <lunatik.h>\n\nstatic unsigned long **luasyscall_table;\n\n/***\n* Retrieves the kernel address of a system call.\n* @function address\n* @tparam integer syscall_number system call number (e.g., `__NR_openat`).\n* @treturn lightuserdata kernel address of the system call entry point, or `nil` if the number is invalid or the address cannot be determined.\n* @raise Error if `syscall_number` is out of bounds.\n* @usage\n*   local syscall = require(\"syscall\")\n*   local openat_addr = syscall.address(syscall.numbers.openat)\n* @within syscall\n*/\nstatic int luasyscall_address(lua_State *L)\n{\n\tlua_Integer nr = luaL_checkinteger(L, 1);\n\tluaL_argcheck(L, nr >= 0 && nr < __NR_syscalls, 1, \"out of bounds\");\n\tlua_pushlightuserdata(L, (void *)luasyscall_table[nr]);\n\treturn 1;\n}\n\n/***\n* Table of system call numbers.\n* This table maps system call names (strings) to their corresponding kernel\n* system call numbers (integers, e.g., `__NR_openat`). The availability of\n* specific system calls depends on the kernel version and architecture, as detailed below.\n* @table numbers\n*   @tfield integer io_setup Create an asynchronous I/O context.\n*   @tfield integer io_destroy Destroy an asynchronous I/O context.\n*   @tfield integer io_submit Submit asynchronous I/O blocks.\n*   @tfield integer io_cancel Cancel an outstanding asynchronous I/O operation.\n*   @tfield integer setxattr Set an extended attribute value.\n*   @tfield integer lsetxattr Set an extended attribute value of a symbolic link.\n*   @tfield integer fsetxattr Set an extended attribute value of an open file.\n*   @tfield integer getxattr Get an extended attribute value.\n*   @tfield integer lgetxattr Get an extended attribute value of a symbolic link.\n*   @tfield integer fgetxattr Get an extended attribute value of an open file.\n*   @tfield integer listxattr List extended attribute names.\n*   @tfield integer llistxattr List extended attribute names of a symbolic link.\n*   @tfield integer flistxattr List extended attribute names of an open file.\n*   @tfield integer removexattr Remove an extended attribute.\n*   @tfield integer lremovexattr Remove an extended attribute of a symbolic link.\n*   @tfield integer fremovexattr Remove an extended attribute of an open file.\n*   @tfield integer getcwd Get current working directory.\n*   @tfield integer lookup_dcookie Return a directory entry's path.\n*   @tfield integer eventfd2 Create a file descriptor for event notification.\n*   @tfield integer epoll_create1 Open an epoll file descriptor.\n*   @tfield integer epoll_ctl Control interface for an epoll file descriptor.\n*   @tfield integer epoll_pwait Wait for an I/O event on an epoll file descriptor.\n*   @tfield integer dup Duplicate an old file descriptor.\n*   @tfield integer dup3 Duplicate an old file descriptor to a new one with flags.\n*   @tfield integer inotify_init1 Initialize an inotify instance.\n*   @tfield integer inotify_add_watch Add a watch to an initialized inotify instance.\n*   @tfield integer inotify_rm_watch Remove an existing watch from an inotify instance.\n*   @tfield integer ioctl Control a device.\n*   @tfield integer ioprio_set Set I/O scheduling class and priority.\n*   @tfield integer ioprio_get Get I/O scheduling class and priority.\n*   @tfield integer flock Apply or remove an advisory lock on an open file.\n*   @tfield integer mknodat Create a special or ordinary file relative to a directory file descriptor.\n*   @tfield integer mkdirat Create a directory relative to a directory file descriptor.\n*   @tfield integer unlinkat Remove a file relative to a directory file descriptor.\n*   @tfield integer symlinkat Create a symbolic link relative to a directory file descriptor.\n*   @tfield integer linkat Make a new name for a file relative to a directory file descriptor.\n*   @tfield integer umount2 Unmount a filesystem with flags.\n*   @tfield integer mount Mount a filesystem.\n*   @tfield integer pivot_root Change the root filesystem.\n*   @tfield integer nfsservctl Syscall for NFS server control (obsolete).\n*   @tfield integer fallocate Manipulate file space.\n*   @tfield integer faccessat Check user's permissions for a file relative to a directory file descriptor.\n*   @tfield integer chdir Change working directory.\n*   @tfield integer fchdir Change working directory using a file descriptor.\n*   @tfield integer chroot Change root directory.\n*   @tfield integer fchmod Change permissions of a file given a file descriptor.\n*   @tfield integer fchmodat Change permissions of a file relative to a directory file descriptor.\n*   @tfield integer fchownat Change ownership of a file relative to a directory file descriptor.\n*   @tfield integer fchown Change ownership of a file given a file descriptor.\n*   @tfield integer openat Open or create a file relative to a directory file descriptor.\n*   @tfield integer close Close a file descriptor.\n*   @tfield integer vhangup Virtually hangup the current tty.\n*   @tfield integer pipe2 Create a pipe with flags.\n*   @tfield integer quotactl Manipulate disk quotas.\n*   @tfield integer getdents64 Get directory entries.\n*   @tfield integer read Read from a file descriptor.\n*   @tfield integer write Write to a file descriptor.\n*   @tfield integer readv Read data into multiple buffers.\n*   @tfield integer writev Write data from multiple buffers.\n*   @tfield integer pread64 Read from a file descriptor at a given offset.\n*   @tfield integer pwrite64 Write to a file descriptor at a given offset.\n*   @tfield integer preadv Read data into multiple buffers from a given offset.\n*   @tfield integer pwritev Write data from multiple buffers at a given offset.\n*   @tfield integer signalfd4 Create a file descriptor for accepting signals.\n*   @tfield integer vmsplice Splice user pages to a pipe.\n*   @tfield integer splice Splice data to/from a pipe.\n*   @tfield integer tee Duplicate pipe content.\n*   @tfield integer readlinkat Read value of a symbolic link relative to a directory file descriptor.\n*   @tfield integer sync Commit filesystem caches to disk.\n*   @tfield integer fsync Synchronize a file's in-core state with storage device.\n*   @tfield integer fdatasync Synchronize a file's data in-core state with storage device.\n*   @tfield integer timerfd_create Create a file descriptor for timer notifications.\n*   @tfield integer acct Switch process accounting on or off.\n*   @tfield integer capget Get process capabilities.\n*   @tfield integer capset Set process capabilities.\n*   @tfield integer personality Set the process execution domain.\n*   @tfield integer exit Terminate the current process.\n*   @tfield integer exit_group Terminate all threads in a process.\n*   @tfield integer waitid Wait for process state changes.\n*   @tfield integer set_tid_address Set pointer to thread ID.\n*   @tfield integer unshare Disassociate parts of the process execution context.\n*   @tfield integer set_robust_list Set the address of the robust futex list.\n*   @tfield integer get_robust_list Get the address of the robust futex list.\n*   @tfield integer getitimer Get value of an interval timer.\n*   @tfield integer setitimer Set value of an interval timer.\n*   @tfield integer kexec_load Load a new kernel for later execution.\n*   @tfield integer init_module Load a kernel module.\n*   @tfield integer delete_module Unload a kernel module.\n*   @tfield integer timer_create Create a POSIX per-process timer.\n*   @tfield integer timer_getoverrun Get POSIX per-process timer overrun count.\n*   @tfield integer timer_delete Delete a POSIX per-process timer.\n*   @tfield integer syslog Read and/or clear kernel message ring buffer; set console_loglevel.\n*   @tfield integer ptrace Process trace.\n*   @tfield integer sched_setparam Set scheduling parameters for a process.\n*   @tfield integer sched_setscheduler Set scheduling policy and parameters for a process.\n*   @tfield integer sched_getscheduler Get scheduling policy for a process.\n*   @tfield integer sched_getparam Get scheduling parameters for a process.\n*   @tfield integer sched_setaffinity Set a thread's CPU affinity mask.\n*   @tfield integer sched_getaffinity Get a thread's CPU affinity mask.\n*   @tfield integer sched_yield Yield the processor.\n*   @tfield integer sched_get_priority_max Get maximum priority value for a scheduling policy.\n*   @tfield integer sched_get_priority_min Get minimum priority value for a scheduling policy.\n*   @tfield integer sched_rr_get_interval Get the SCHED_RR interval for the named process.\n*   @tfield integer restart_syscall Restart a system call after a signal.\n*   @tfield integer kill Send signal to a process.\n*   @tfield integer tkill Send signal to a thread.\n*   @tfield integer tgkill Send signal to a thread group.\n*   @tfield integer sigaltstack Set and/or get signal stack context.\n*   @tfield integer rt_sigsuspend Wait for a real-time signal.\n*   @tfield integer rt_sigaction Examine and change a real-time signal action.\n*   @tfield integer rt_sigprocmask Examine and change blocked real-time signals.\n*   @tfield integer rt_sigpending Examine pending real-time signals.\n*   @tfield integer rt_sigqueueinfo Queue a real-time signal and data.\n*   @tfield integer rt_sigreturn Return from signal handler and cleanup stack frame.\n*   @tfield integer setpriority Set program scheduling priority.\n*   @tfield integer getpriority Get program scheduling priority.\n*   @tfield integer reboot Reboot or enable/disable Ctrl-Alt-Del.\n*   @tfield integer setregid Set real and effective group IDs.\n*   @tfield integer setgid Set effective group ID.\n*   @tfield integer setreuid Set real and effective user IDs.\n*   @tfield integer setuid Set effective user ID.\n*   @tfield integer setresuid Set real, effective and saved user IDs.\n*   @tfield integer getresuid Get real, effective and saved user IDs.\n*   @tfield integer setresgid Set real, effective and saved group IDs.\n*   @tfield integer getresgid Get real, effective and saved group IDs.\n*   @tfield integer setfsuid Set filesystem user ID.\n*   @tfield integer setfsgid Set filesystem group ID.\n*   @tfield integer times Get process times.\n*   @tfield integer setpgid Set process group ID.\n*   @tfield integer getpgid Get process group ID.\n*   @tfield integer getsid Get session ID.\n*   @tfield integer setsid Create a session and set the process group ID.\n*   @tfield integer getgroups Get list of supplementary group IDs.\n*   @tfield integer setgroups Set list of supplementary group IDs.\n*   @tfield integer uname Get name and information about current kernel.\n*   @tfield integer sethostname Set the system's hostname.\n*   @tfield integer setdomainname Set the system's NIS/YP domain name.\n*   @tfield integer getrusage Get resource usage.\n*   @tfield integer umask Set file mode creation mask.\n*   @tfield integer prctl Operations on a process or thread.\n*   @tfield integer getcpu Determine CPU and NUMA node on which the calling thread is running.\n*   @tfield integer getpid Get process ID.\n*   @tfield integer getppid Get parent process ID.\n*   @tfield integer getuid Get real user ID.\n*   @tfield integer geteuid Get effective user ID.\n*   @tfield integer getgid Get real group ID.\n*   @tfield integer getegid Get effective group ID.\n*   @tfield integer gettid Get thread ID.\n*   @tfield integer sysinfo Get system information.\n*   @tfield integer mq_open Open a POSIX message queue.\n*   @tfield integer mq_unlink Unlink a POSIX message queue.\n*   @tfield integer mq_timedsend Send a message to a POSIX message queue with timeout.\n*   @tfield integer mq_timedreceive Receive a message from a POSIX message queue with timeout.\n*   @tfield integer mq_notify Register for asynchronous notification of message arrival on a POSIX message queue.\n*   @tfield integer mq_getsetattr Get/set POSIX message queue attributes.\n*   @tfield integer msgget Get a System V message queue identifier.\n*   @tfield integer msgctl System V message control operations.\n*   @tfield integer msgrcv Receive messages from a System V message queue.\n*   @tfield integer msgsnd Send a message to a System V message queue.\n*   @tfield integer semget Get a System V semaphore set identifier.\n*   @tfield integer semctl System V semaphore control operations.\n*   @tfield integer semop System V semaphore operations.\n*   @tfield integer shmget Allocates a System V shared memory segment.\n*   @tfield integer shmctl System V shared memory control.\n*   @tfield integer shmat Attach the System V shared memory segment to the address space of the calling process.\n*   @tfield integer shmdt Detach the System V shared memory segment from the address space of the calling process.\n*   @tfield integer socket Create an endpoint for communication.\n*   @tfield integer socketpair Create a pair of connected sockets.\n*   @tfield integer bind Bind a name to a socket.\n*   @tfield integer listen Listen for connections on a socket.\n*   @tfield integer accept Accept a connection on a socket.\n*   @tfield integer connect Initiate a connection on a socket.\n*   @tfield integer getsockname Get socket name.\n*   @tfield integer getpeername Get name of connected peer socket.\n*   @tfield integer sendto Send a message on a socket.\n*   @tfield integer recvfrom Receive a message from a socket.\n*   @tfield integer setsockopt Set options on sockets.\n*   @tfield integer getsockopt Get options on sockets.\n*   @tfield integer shutdown Shut down part of a full-duplex connection.\n*   @tfield integer sendmsg Send a message on a socket using a message structure.\n*   @tfield integer recvmsg Receive a message from a socket using a message structure.\n*   @tfield integer readahead Initiate readahead on a file descriptor.\n*   @tfield integer brk Change data segment size.\n*   @tfield integer munmap Unmap files or devices into memory.\n*   @tfield integer mremap Remap a virtual memory address.\n*   @tfield integer add_key Add a key to the kernel's key management facility.\n*   @tfield integer request_key Request a key from the kernel's key management facility.\n*   @tfield integer keyctl Manipulate the kernel's key management facility.\n*   @tfield integer clone Create a child process.\n*   @tfield integer execve Execute a program.\n*   @tfield integer rt_tgsigqueueinfo Send a real-time signal with data to a thread group.\n*   @tfield integer perf_event_open Set up performance monitoring.\n*   @tfield integer accept4 Accept a connection on a socket with flags.\n*   @tfield integer recvmmsg Receive multiple messages from a socket.\n*   @tfield integer prlimit64 Get and set resource limits.\n*   @tfield integer fanotify_init Create and initialize fanotify group.\n*   @tfield integer fanotify_mark Add, remove, or modify an fanotify mark on a filesystem object.\n*   @tfield integer syncfs Commit filesystem caches to disk for a specific filesystem.\n*   @tfield integer setns Reassociate thread with a namespace.\n*   @tfield integer sendmmsg Send multiple messages on a socket.\n*   @tfield integer process_vm_readv Read from another process's memory.\n*   @tfield integer process_vm_writev Write to another process's memory.\n*   @tfield integer kcmp Compare two processes to determine if they share a kernel resource.\n*   @tfield integer finit_module Load a kernel module from a file descriptor.\n*   @tfield integer sched_setattr Set scheduling policy and attributes for a thread.\n*   @tfield integer sched_getattr Get scheduling policy and attributes for a thread.\n*   @tfield integer renameat2 Rename a file or directory, with flags.\n*   @tfield integer seccomp Operate on Secure Computing state of the process.\n*   @tfield integer getrandom Obtain a series of random bytes.\n*   @tfield integer memfd_create Create an anonymous file.\n*   @tfield integer bpf Perform a BPF operation.\n*   @tfield integer execveat Execute a program relative to a directory file descriptor.\n*   @tfield integer userfaultfd Create a file descriptor for handling page faults in user space.\n*   @tfield integer membarrier Issue memory barriers.\n*   @tfield integer mlock2 Lock memory with flags.\n*   @tfield integer copy_file_range Copy a range of data from one file to another.\n*   @tfield integer preadv2 Read data into multiple buffers from a given offset, with flags.\n*   @tfield integer pwritev2 Write data from multiple buffers at a given offset, with flags.\n*   @tfield integer pkey_mprotect Set protection on a region of memory, with a protection key.\n*   @tfield integer pkey_alloc Allocate a protection key.\n*   @tfield integer pkey_free Free a protection key.\n*   @tfield integer statx Get file status (extended).\n*   @tfield integer rseq Restartable sequences.\n*   @tfield integer kexec_file_load Load a new kernel for later execution from a file descriptor.\n*   @tfield integer pidfd_send_signal Send a signal to a process specified by a PID file descriptor.\n*   @tfield integer io_uring_setup Setup an io_uring instance.\n*   @tfield integer io_uring_enter Register files or submit I/O to an io_uring instance.\n*   @tfield integer io_uring_register Register files or user buffers for an io_uring instance.\n*   @tfield integer open_tree Open a filesystem object by path and attribute.\n*   @tfield integer move_mount Move a mount.\n*   @tfield integer fsopen Open a filesystem by name and flags.\n*   @tfield integer fsconfig Configure a filesystem.\n*   @tfield integer fsmount Mount a filesystem.\n*   @tfield integer fspick Select a filesystem by fd and path.\n*   @tfield integer pidfd_open Obtain a file descriptor that refers to a process.\n*\n*   --- Conditional on `__ARCH_WANT_TIME32_SYSCALLS` or `__BITS_PER_LONG != 32` ---\n*   @tfield integer io_getevents Read asynchronous I/O events from the completion queue.\n*   @tfield integer pselect6 Synchronous I/O multiplexing with a timeout and a signal mask.\n*   @tfield integer ppoll Wait for some event on a file descriptor with a timeout and a signal mask.\n*   @tfield integer timerfd_settime Arm or disarm a timer that notifies via a file descriptor.\n*   @tfield integer timerfd_gettime Get current setting of a timer that notifies via a file descriptor.\n*   @tfield integer utimensat Change file last access and modification times relative to a directory file descriptor.\n*   @tfield integer futex Fast user-space locking.\n*   @tfield integer nanosleep High-resolution sleep.\n*   @tfield integer timer_gettime Get POSIX per-process timer.\n*   @tfield integer timer_settime Arm/disarm POSIX per-process timer.\n*   @tfield integer clock_settime Set time of a specified clock.\n*   @tfield integer clock_gettime Get time of a specified clock.\n*   @tfield integer clock_getres Get resolution of a specified clock.\n*   @tfield integer clock_nanosleep High-resolution sleep with a specific clock.\n*   @tfield integer rt_sigtimedwait Synchronously wait for queued real-time signals.\n*   @tfield integer gettimeofday Get time.\n*   @tfield integer settimeofday Set time.\n*   @tfield integer adjtimex Tune kernel clock.\n*   @tfield integer semtimedop System V semaphore operations with timeout.\n*   @tfield integer wait4 Wait for process state changes, BSD style.\n*   @tfield integer clock_adjtime Tune a specified clock.\n*   @tfield integer io_pgetevents Read AIO events with timeout and signal mask.\n*\n*   --- Conditional on `__ARCH_WANT_RENAMEAT` ---\n*   @tfield integer renameat Rename a file or directory relative to directory file descriptors.\n*\n*   --- Conditional on `__ARCH_WANT_SYNC_FILE_RANGE2` ---\n*   @tfield integer sync_file_range2 Sync a file segment with disk, with flags.\n*\n*   --- Conditional on `!__ARCH_WANT_SYNC_FILE_RANGE2` (else part) ---\n*   @tfield integer sync_file_range Sync a file segment with disk.\n*\n*   --- Conditional on `__ARCH_WANT_SET_GET_RLIMIT` ---\n*   @tfield integer getrlimit Get resource limits.\n*   @tfield integer setrlimit Set resource limits.\n*\n*   --- Conditional on `!__ARCH_NOMMU` ---\n*   @tfield integer swapon Start swapping to a file or block device.\n*   @tfield integer swapoff Stop swapping to a file or block device.\n*   @tfield integer mprotect Set protection on a region of memory.\n*   @tfield integer msync Synchronize a file with a memory map.\n*   @tfield integer mlock Lock memory.\n*   @tfield integer munlock Unlock memory.\n*   @tfield integer mlockall Lock all pages mapped into the address space of the calling process.\n*   @tfield integer munlockall Unlock all pages mapped into the address space of the calling process.\n*   @tfield integer mincore Determine whether pages are resident in memory.\n*   @tfield integer madvise Give advice about use of memory.\n*   @tfield integer remap_file_pages Create a nonlinear file mapping.\n*   @tfield integer mbind Set memory policy for a memory range.\n*   @tfield integer get_mempolicy Retrieve NUMA memory policy for a thread.\n*   @tfield integer set_mempolicy Set NUMA memory policy for a thread.\n*   @tfield integer migrate_pages Migrate pages of the calling process to a set of nodes.\n*   @tfield integer move_pages Move pages of the calling process to specific nodes.\n*\n*   --- Conditional on `__SYSCALL_COMPAT` or `__BITS_PER_LONG == 32` ---\n*   @tfield integer clock_gettime64 Get time of a specified clock (64-bit time_t).\n*   @tfield integer clock_settime64 Set time of a specified clock (64-bit time_t).\n*   @tfield integer clock_adjtime64 Tune a specified clock (64-bit time_t).\n*   @tfield integer clock_getres_time64 Get resolution of a specified clock (64-bit time_t).\n*   @tfield integer clock_nanosleep_time64 High-resolution sleep with a specific clock (64-bit time_t).\n*   @tfield integer timer_gettime64 Get POSIX per-process timer (64-bit time_t).\n*   @tfield integer timer_settime64 Arm/disarm POSIX per-process timer (64-bit time_t).\n*   @tfield integer timerfd_gettime64 Get current setting of a timerfd (64-bit time_t).\n*   @tfield integer timerfd_settime64 Arm or disarm a timerfd (64-bit time_t).\n*   @tfield integer utimensat_time64 Change file timestamps relative to a directory fd (64-bit time_t).\n*   @tfield integer pselect6_time64 Synchronous I/O multiplexing (64-bit time_t).\n*   @tfield integer ppoll_time64 Wait for some event on a file descriptor (64-bit time_t).\n*   @tfield integer io_pgetevents_time64 Read AIO events with timeout and signal mask (64-bit time_t).\n*   @tfield integer recvmmsg_time64 Receive multiple messages from a socket (64-bit time_t).\n*   @tfield integer mq_timedsend_time64 Send a message to a POSIX message queue with timeout (64-bit time_t).\n*   @tfield integer mq_timedreceive_time64 Receive a message from a POSIX message queue with timeout (64-bit time_t).\n*   @tfield integer semtimedop_time64 System V semaphore operations with timeout (64-bit time_t).\n*   @tfield integer rt_sigtimedwait_time64 Synchronously wait for queued real-time signals (64-bit time_t).\n*   @tfield integer futex_time64 Fast user-space locking (64-bit time_t).\n*   @tfield integer sched_rr_get_interval_time64 Get the SCHED_RR interval for the named process (64-bit time_t).\n*\n*   --- Conditional on `__ARCH_WANT_SYS_CLONE3` ---\n*   @tfield integer clone3 Create a child process with a new API.\n*\n*   --- Conditional on `LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)` ---\n*   @tfield integer close_range Close a range of file descriptors.\n*\n*   --- Conditional on `LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)` ---\n*   @tfield integer openat2 Open or create a file relative to a directory file descriptor, with extended flags.\n*   @tfield integer pidfd_getfd Obtain a duplicate of another process's file descriptor.\n*\n*   --- Conditional on `LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)` ---\n*   @tfield integer faccessat2 Check user's permissions for a file relative to a directory fd, with flags.\n*\n*   --- Conditional on `LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)` ---\n*   @tfield integer process_madvise Give advice about use of memory to a process.\n*\n*   --- Conditional on `LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)` ---\n*   @tfield integer epoll_pwait2 Wait for an I/O event on an epoll file descriptor, with extended timeout.\n*\n*   --- Conditional on `LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)` ---\n*   @tfield integer mount_setattr Change properties of a mount.\n*\n*   --- Conditional on `LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0)` ---\n*   @tfield integer quotactl_fd Manipulate disk quotas using a file descriptor.\n*\n*   --- Conditional on `LINUX_VERSION_CODE >= KERNEL_VERSION(5, 13, 0)` ---\n*   @tfield integer landlock_create_ruleset Create a new Landlock ruleset.\n*   @tfield integer landlock_add_rule Add a new rule to a Landlock ruleset.\n*   @tfield integer landlock_restrict_self Enforce a Landlock ruleset on the calling thread.\n*\n*   --- Conditional on `__ARCH_WANT_MEMFD_SECRET` ---\n*   @tfield integer memfd_secret Create an anonymous file in RAM for secrets.\n*\n*   --- Conditional on `LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)` ---\n*   @tfield integer process_mrelease Release memory of a remote process.\n*\n*   --- Conditional on `__BITS_PER_LONG == 64` and `!__SYSCALL_COMPAT` ---\n*   @tfield integer fcntl Manipulate file descriptor.\n*   @tfield integer statfs Get filesystem statistics.\n*   @tfield integer fstatfs Get filesystem statistics for an open file.\n*   @tfield integer truncate Truncate a file to a specified length.\n*   @tfield integer ftruncate Truncate an open file to a specified length.\n*   @tfield integer lseek Reposition read/write file offset.\n*   @tfield integer sendfile Transfer data between file descriptors.\n*   @tfield integer newfstatat Get file status relative to a directory fd (new stat struct).\n*   @tfield integer fstat Get file status for an open file (new stat struct).\n*   @tfield integer mmap Map files or devices into memory.\n*   @tfield integer fadvise64 Predeclare an access pattern for file data.\n*   @tfield integer stat Get file status.\n*   @tfield integer lstat Get symbolic link status.\n*\n*   --- Conditional on `__BITS_PER_LONG != 64` or `__SYSCALL_COMPAT` (else part of above) ---\n*   @tfield integer fcntl64 Manipulate file descriptor (64-bit version).\n*   @tfield integer statfs64 Get filesystem statistics (64-bit version).\n*   @tfield integer fstatfs64 Get filesystem statistics for an open file (64-bit version).\n*   @tfield integer truncate64 Truncate a file to a specified length (64-bit version).\n*   @tfield integer ftruncate64 Truncate an open file to a specified length (64-bit version).\n*   @tfield integer llseek Reposition read/write file offset (long long version).\n*   @tfield integer sendfile64 Transfer data between file descriptors (64-bit version).\n*   @tfield integer fstatat64 Get file status relative to a directory fd (64-bit stat struct).\n*   @tfield integer fstat64 Get file status for an open file (64-bit stat struct).\n*   @tfield integer mmap2 Map files or devices into memory (with offset in pages).\n*   @tfield integer fadvise64_64 Predeclare an access pattern for file data (64-bit offset/len).\n*   @tfield integer stat64 Get file status (64-bit stat struct).\n*   @tfield integer lstat64 Get symbolic link status (64-bit stat struct).\n* @within syscall\n*/\nstatic const lunatik_reg_t luasyscall_numbers[] = {\n\t{\"io_setup\", __NR_io_setup},\n\t{\"io_destroy\", __NR_io_destroy},\n\t{\"io_submit\", __NR_io_submit},\n\t{\"io_cancel\", __NR_io_cancel},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"io_getevents\", __NR_io_getevents},\n#endif\n\t{\"setxattr\", __NR_setxattr},\n\t{\"lsetxattr\", __NR_lsetxattr},\n\t{\"fsetxattr\", __NR_fsetxattr},\n\t{\"getxattr\", __NR_getxattr},\n\t{\"lgetxattr\", __NR_lgetxattr},\n\t{\"fgetxattr\", __NR_fgetxattr},\n\t{\"listxattr\", __NR_listxattr},\n\t{\"llistxattr\", __NR_llistxattr},\n\t{\"flistxattr\", __NR_flistxattr},\n\t{\"removexattr\", __NR_removexattr},\n\t{\"lremovexattr\", __NR_lremovexattr},\n\t{\"fremovexattr\", __NR_fremovexattr},\n\t{\"getcwd\", __NR_getcwd},\n\t{\"lookup_dcookie\", __NR_lookup_dcookie},\n\t{\"eventfd2\", __NR_eventfd2},\n\t{\"epoll_create1\", __NR_epoll_create1},\n\t{\"epoll_ctl\", __NR_epoll_ctl},\n\t{\"epoll_pwait\", __NR_epoll_pwait},\n\t{\"dup\", __NR_dup},\n\t{\"dup3\", __NR_dup3},\n\t{\"inotify_init1\", __NR_inotify_init1},\n\t{\"inotify_add_watch\", __NR_inotify_add_watch},\n\t{\"inotify_rm_watch\", __NR_inotify_rm_watch},\n\t{\"ioctl\", __NR_ioctl},\n\t{\"ioprio_set\", __NR_ioprio_set},\n\t{\"ioprio_get\", __NR_ioprio_get},\n\t{\"flock\", __NR_flock},\n\t{\"mknodat\", __NR_mknodat},\n\t{\"mkdirat\", __NR_mkdirat},\n\t{\"unlinkat\", __NR_unlinkat},\n\t{\"symlinkat\", __NR_symlinkat},\n\t{\"linkat\", __NR_linkat},\n#ifdef __ARCH_WANT_RENAMEAT\n\t{\"renameat\", __NR_renameat},\n#endif\n\t{\"umount2\", __NR_umount2},\n\t{\"mount\", __NR_mount},\n\t{\"pivot_root\", __NR_pivot_root},\n\t{\"nfsservctl\", __NR_nfsservctl},\n\t{\"fallocate\", __NR_fallocate},\n\t{\"faccessat\", __NR_faccessat},\n\t{\"chdir\", __NR_chdir},\n\t{\"fchdir\", __NR_fchdir},\n\t{\"chroot\", __NR_chroot},\n\t{\"fchmod\", __NR_fchmod},\n\t{\"fchmodat\", __NR_fchmodat},\n\t{\"fchownat\", __NR_fchownat},\n\t{\"fchown\", __NR_fchown},\n\t{\"openat\", __NR_openat},\n\t{\"close\", __NR_close},\n\t{\"vhangup\", __NR_vhangup},\n\t{\"pipe2\", __NR_pipe2},\n\t{\"quotactl\", __NR_quotactl},\n\t{\"getdents64\", __NR_getdents64},\n\t{\"read\", __NR_read},\n\t{\"write\", __NR_write},\n\t{\"readv\", __NR_readv},\n\t{\"writev\", __NR_writev},\n\t{\"pread64\", __NR_pread64},\n\t{\"pwrite64\", __NR_pwrite64},\n\t{\"preadv\", __NR_preadv},\n\t{\"pwritev\", __NR_pwritev},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"pselect6\", __NR_pselect6},\n\t{\"ppoll\", __NR_ppoll},\n#endif\n\t{\"signalfd4\", __NR_signalfd4},\n\t{\"vmsplice\", __NR_vmsplice},\n\t{\"splice\", __NR_splice},\n\t{\"tee\", __NR_tee},\n\t{\"readlinkat\", __NR_readlinkat},\n\t{\"sync\", __NR_sync},\n\t{\"fsync\", __NR_fsync},\n\t{\"fdatasync\", __NR_fdatasync},\n#ifdef __ARCH_WANT_SYNC_FILE_RANGE2\n\t{\"sync_file_range2\", __NR_sync_file_range2},\n#else\n\t{\"sync_file_range\", __NR_sync_file_range},\n#endif\n\t{\"timerfd_create\", __NR_timerfd_create},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"timerfd_settime\", __NR_timerfd_settime},\n\t{\"timerfd_gettime\", __NR_timerfd_gettime},\n#endif\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"utimensat\", __NR_utimensat},\n#endif\n\t{\"acct\", __NR_acct},\n\t{\"capget\", __NR_capget},\n\t{\"capset\", __NR_capset},\n\t{\"personality\", __NR_personality},\n\t{\"exit\", __NR_exit},\n\t{\"exit_group\", __NR_exit_group},\n\t{\"waitid\", __NR_waitid},\n\t{\"set_tid_address\", __NR_set_tid_address},\n\t{\"unshare\", __NR_unshare},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"futex\", __NR_futex},\n#endif\n\t{\"set_robust_list\", __NR_set_robust_list},\n\t{\"get_robust_list\", __NR_get_robust_list},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"nanosleep\", __NR_nanosleep},\n#endif\n\t{\"getitimer\", __NR_getitimer},\n\t{\"setitimer\", __NR_setitimer},\n\t{\"kexec_load\", __NR_kexec_load},\n\t{\"init_module\", __NR_init_module},\n\t{\"delete_module\", __NR_delete_module},\n\t{\"timer_create\", __NR_timer_create},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"timer_gettime\", __NR_timer_gettime},\n#endif\n\t{\"timer_getoverrun\", __NR_timer_getoverrun},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"timer_settime\", __NR_timer_settime},\n#endif\n\t{\"timer_delete\", __NR_timer_delete},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"clock_settime\", __NR_clock_settime},\n\t{\"clock_gettime\", __NR_clock_gettime},\n\t{\"clock_getres\", __NR_clock_getres},\n\t{\"clock_nanosleep\", __NR_clock_nanosleep},\n#endif\n\t{\"syslog\", __NR_syslog},\n\t{\"ptrace\", __NR_ptrace},\n\t{\"sched_setparam\", __NR_sched_setparam},\n\t{\"sched_setscheduler\", __NR_sched_setscheduler},\n\t{\"sched_getscheduler\", __NR_sched_getscheduler},\n\t{\"sched_getparam\", __NR_sched_getparam},\n\t{\"sched_setaffinity\", __NR_sched_setaffinity},\n\t{\"sched_getaffinity\", __NR_sched_getaffinity},\n\t{\"sched_yield\", __NR_sched_yield},\n\t{\"sched_get_priority_max\", __NR_sched_get_priority_max},\n\t{\"sched_get_priority_min\", __NR_sched_get_priority_min},\n\t{\"sched_rr_get_interval\", __NR_sched_rr_get_interval},\n\t{\"restart_syscall\", __NR_restart_syscall},\n\t{\"kill\", __NR_kill},\n\t{\"tkill\", __NR_tkill},\n\t{\"tgkill\", __NR_tgkill},\n\t{\"sigaltstack\", __NR_sigaltstack},\n\t{\"rt_sigsuspend\", __NR_rt_sigsuspend},\n\t{\"rt_sigaction\", __NR_rt_sigaction},\n\t{\"rt_sigprocmask\", __NR_rt_sigprocmask},\n\t{\"rt_sigpending\", __NR_rt_sigpending},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"rt_sigtimedwait\", __NR_rt_sigtimedwait},\n#endif\n\t{\"rt_sigqueueinfo\", __NR_rt_sigqueueinfo},\n\t{\"rt_sigreturn\", __NR_rt_sigreturn},\n\t{\"setpriority\", __NR_setpriority},\n\t{\"getpriority\", __NR_getpriority},\n\t{\"reboot\", __NR_reboot},\n\t{\"setregid\", __NR_setregid},\n\t{\"setgid\", __NR_setgid},\n\t{\"setreuid\", __NR_setreuid},\n\t{\"setuid\", __NR_setuid},\n\t{\"setresuid\", __NR_setresuid},\n\t{\"getresuid\", __NR_getresuid},\n\t{\"setresgid\", __NR_setresgid},\n\t{\"getresgid\", __NR_getresgid},\n\t{\"setfsuid\", __NR_setfsuid},\n\t{\"setfsgid\", __NR_setfsgid},\n\t{\"times\", __NR_times},\n\t{\"setpgid\", __NR_setpgid},\n\t{\"getpgid\", __NR_getpgid},\n\t{\"getsid\", __NR_getsid},\n\t{\"setsid\", __NR_setsid},\n\t{\"getgroups\", __NR_getgroups},\n\t{\"setgroups\", __NR_setgroups},\n\t{\"uname\", __NR_uname},\n\t{\"sethostname\", __NR_sethostname},\n\t{\"setdomainname\", __NR_setdomainname},\n#ifdef __ARCH_WANT_SET_GET_RLIMIT\n\t{\"getrlimit\", __NR_getrlimit},\n\t{\"setrlimit\", __NR_setrlimit},\n#endif\n\t{\"getrusage\", __NR_getrusage},\n\t{\"umask\", __NR_umask},\n\t{\"prctl\", __NR_prctl},\n\t{\"getcpu\", __NR_getcpu},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"gettimeofday\", __NR_gettimeofday},\n\t{\"settimeofday\", __NR_settimeofday},\n\t{\"adjtimex\", __NR_adjtimex},\n#endif\n\t{\"getpid\", __NR_getpid},\n\t{\"getppid\", __NR_getppid},\n\t{\"getuid\", __NR_getuid},\n\t{\"geteuid\", __NR_geteuid},\n\t{\"getgid\", __NR_getgid},\n\t{\"getegid\", __NR_getegid},\n\t{\"gettid\", __NR_gettid},\n\t{\"sysinfo\", __NR_sysinfo},\n\t{\"mq_open\", __NR_mq_open},\n\t{\"mq_unlink\", __NR_mq_unlink},\n\t{\"mq_timedsend\", __NR_mq_timedsend},\n\t{\"mq_timedreceive\", __NR_mq_timedreceive},\n\t{\"mq_notify\", __NR_mq_notify},\n\t{\"mq_getsetattr\", __NR_mq_getsetattr},\n\t{\"msgget\", __NR_msgget},\n\t{\"msgctl\", __NR_msgctl},\n\t{\"msgrcv\", __NR_msgrcv},\n\t{\"msgsnd\", __NR_msgsnd},\n\t{\"semget\", __NR_semget},\n\t{\"semctl\", __NR_semctl},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"semtimedop\", __NR_semtimedop},\n#endif\n\t{\"semop\", __NR_semop},\n\t{\"shmget\", __NR_shmget},\n\t{\"shmctl\", __NR_shmctl},\n\t{\"shmat\", __NR_shmat},\n\t{\"shmdt\", __NR_shmdt},\n\t{\"socket\", __NR_socket},\n\t{\"socketpair\", __NR_socketpair},\n\t{\"bind\", __NR_bind},\n\t{\"listen\", __NR_listen},\n\t{\"accept\", __NR_accept},\n\t{\"connect\", __NR_connect},\n\t{\"getsockname\", __NR_getsockname},\n\t{\"getpeername\", __NR_getpeername},\n\t{\"sendto\", __NR_sendto},\n\t{\"recvfrom\", __NR_recvfrom},\n\t{\"setsockopt\", __NR_setsockopt},\n\t{\"getsockopt\", __NR_getsockopt},\n\t{\"shutdown\", __NR_shutdown},\n\t{\"sendmsg\", __NR_sendmsg},\n\t{\"recvmsg\", __NR_recvmsg},\n\t{\"readahead\", __NR_readahead},\n\t{\"brk\", __NR_brk},\n\t{\"munmap\", __NR_munmap},\n\t{\"mremap\", __NR_mremap},\n\t{\"add_key\", __NR_add_key},\n\t{\"request_key\", __NR_request_key},\n\t{\"keyctl\", __NR_keyctl},\n\t{\"clone\", __NR_clone},\n\t{\"execve\", __NR_execve},\n#ifndef __ARCH_NOMMU\n\t{\"swapon\", __NR_swapon},\n\t{\"swapoff\", __NR_swapoff},\n\t{\"mprotect\", __NR_mprotect},\n\t{\"msync\", __NR_msync},\n\t{\"mlock\", __NR_mlock},\n\t{\"munlock\", __NR_munlock},\n\t{\"mlockall\", __NR_mlockall},\n\t{\"munlockall\", __NR_munlockall},\n\t{\"mincore\", __NR_mincore},\n\t{\"madvise\", __NR_madvise},\n\t{\"remap_file_pages\", __NR_remap_file_pages},\n\t{\"mbind\", __NR_mbind},\n\t{\"get_mempolicy\", __NR_get_mempolicy},\n\t{\"set_mempolicy\", __NR_set_mempolicy},\n\t{\"migrate_pages\", __NR_migrate_pages},\n\t{\"move_pages\", __NR_move_pages},\n#endif\n\t{\"rt_tgsigqueueinfo\", __NR_rt_tgsigqueueinfo},\n\t{\"perf_event_open\", __NR_perf_event_open},\n\t{\"accept4\", __NR_accept4},\n\t{\"recvmmsg\", __NR_recvmmsg},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"wait4\", __NR_wait4},\n#endif\n\t{\"prlimit64\", __NR_prlimit64},\n\t{\"fanotify_init\", __NR_fanotify_init},\n\t{\"fanotify_mark\", __NR_fanotify_mark},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"clock_adjtime\", __NR_clock_adjtime},\n#endif\n\t{\"syncfs\", __NR_syncfs},\n\t{\"setns\", __NR_setns},\n\t{\"sendmmsg\", __NR_sendmmsg},\n\t{\"process_vm_readv\", __NR_process_vm_readv},\n\t{\"process_vm_writev\", __NR_process_vm_writev},\n\t{\"kcmp\", __NR_kcmp},\n\t{\"finit_module\", __NR_finit_module},\n\t{\"sched_setattr\", __NR_sched_setattr},\n\t{\"sched_getattr\", __NR_sched_getattr},\n\t{\"renameat2\", __NR_renameat2},\n\t{\"seccomp\", __NR_seccomp},\n\t{\"getrandom\", __NR_getrandom},\n\t{\"memfd_create\", __NR_memfd_create},\n\t{\"bpf\", __NR_bpf},\n\t{\"execveat\", __NR_execveat},\n\t{\"userfaultfd\", __NR_userfaultfd},\n\t{\"membarrier\", __NR_membarrier},\n\t{\"mlock2\", __NR_mlock2},\n\t{\"copy_file_range\", __NR_copy_file_range},\n\t{\"preadv2\", __NR_preadv2},\n\t{\"pwritev2\", __NR_pwritev2},\n\t{\"pkey_mprotect\", __NR_pkey_mprotect},\n\t{\"pkey_alloc\", __NR_pkey_alloc},\n\t{\"pkey_free\", __NR_pkey_free},\n\t{\"statx\", __NR_statx},\n#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32\n\t{\"io_pgetevents\", __NR_io_pgetevents},\n#endif\n\t{\"rseq\", __NR_rseq},\n\t{\"kexec_file_load\", __NR_kexec_file_load},\n#if defined(__SYSCALL_COMPAT) || __BITS_PER_LONG == 32\n\t{\"clock_gettime64\", __NR_clock_gettime64},\n\t{\"clock_settime64\", __NR_clock_settime64},\n\t{\"clock_adjtime64\", __NR_clock_adjtime64},\n\t{\"clock_getres_time64\", __NR_clock_getres_time64},\n\t{\"clock_nanosleep_time64\", __NR_clock_nanosleep_time64},\n\t{\"timer_gettime64\", __NR_timer_gettime64},\n\t{\"timer_settime64\", __NR_timer_settime64},\n\t{\"timerfd_gettime64\", __NR_timerfd_gettime64},\n\t{\"timerfd_settime64\", __NR_timerfd_settime64},\n\t{\"utimensat_time64\", __NR_utimensat_time64},\n\t{\"pselect6_time64\", __NR_pselect6_time64},\n\t{\"ppoll_time64\", __NR_ppoll_time64},\n\t{\"io_pgetevents_time64\", __NR_io_pgetevents_time64},\n\t{\"recvmmsg_time64\", __NR_recvmmsg_time64},\n\t{\"mq_timedsend_time64\", __NR_mq_timedsend_time64},\n\t{\"mq_timedreceive_time64\", __NR_mq_timedreceive_time64},\n\t{\"semtimedop_time64\", __NR_semtimedop_time64},\n\t{\"rt_sigtimedwait_time64\", __NR_rt_sigtimedwait_time64},\n\t{\"futex_time64\", __NR_futex_time64},\n\t{\"sched_rr_get_interval_time64\", __NR_sched_rr_get_interval_time64},\n#endif\n\t{\"pidfd_send_signal\", __NR_pidfd_send_signal},\n\t{\"io_uring_setup\", __NR_io_uring_setup},\n\t{\"io_uring_enter\", __NR_io_uring_enter},\n\t{\"io_uring_register\", __NR_io_uring_register},\n\t{\"open_tree\", __NR_open_tree},\n\t{\"move_mount\", __NR_move_mount},\n\t{\"fsopen\", __NR_fsopen},\n\t{\"fsconfig\", __NR_fsconfig},\n\t{\"fsmount\", __NR_fsmount},\n\t{\"fspick\", __NR_fspick},\n\t{\"pidfd_open\", __NR_pidfd_open},\n#ifdef __ARCH_WANT_SYS_CLONE3\n\t{\"clone3\", __NR_clone3},\n#endif\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)\n\t{\"close_range\", __NR_close_range},\n#endif\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)\n\t{\"openat2\", __NR_openat2},\n\t{\"pidfd_getfd\", __NR_pidfd_getfd},\n#endif\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)\n\t{\"faccessat2\", __NR_faccessat2},\n#endif\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)\n\t{\"process_madvise\", __NR_process_madvise},\n#endif\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)\n\t{\"epoll_pwait2\", __NR_epoll_pwait2},\n#endif\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)\n\t{\"mount_setattr\", __NR_mount_setattr},\n#endif\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 14, 0)\n\t{\"quotactl_fd\", __NR_quotactl_fd},\n#endif\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 13, 0)\n\t{\"landlock_create_ruleset\", __NR_landlock_create_ruleset},\n\t{\"landlock_add_rule\", __NR_landlock_add_rule},\n\t{\"landlock_restrict_self\", __NR_landlock_restrict_self},\n#endif\n#ifdef __ARCH_WANT_MEMFD_SECRET\n\t{\"memfd_secret\", __NR_memfd_secret},\n#endif\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)\n\t{\"process_mrelease\", __NR_process_mrelease},\n#endif\n#if __BITS_PER_LONG == 64 && !defined(__SYSCALL_COMPAT)\n\t{\"fcntl\", __NR_fcntl},\n\t{\"statfs\", __NR_statfs},\n\t{\"fstatfs\", __NR_fstatfs},\n\t{\"truncate\", __NR_truncate},\n\t{\"ftruncate\", __NR_ftruncate},\n\t{\"lseek\", __NR_lseek},\n\t{\"sendfile\", __NR_sendfile},\n#if defined(__ARCH_WANT_NEW_STAT) || defined(__ARCH_WANT_STAT64)\n\t{\"newfstatat\", __NR_newfstatat},\n\t{\"fstat\", __NR_fstat},\n#endif\n\t{\"mmap\", __NR_mmap},\n\t{\"fadvise64\", __NR_fadvise64},\n#ifdef __NR3264_stat\n\t{\"stat\", __NR_stat},\n\t{\"lstat\", __NR_lstat},\n#endif\n#else\n\t{\"fcntl64\", __NR_fcntl64},\n\t{\"statfs64\", __NR_statfs64},\n\t{\"fstatfs64\", __NR_fstatfs64},\n\t{\"truncate64\", __NR_truncate64},\n\t{\"ftruncate64\", __NR_ftruncate64},\n\t{\"llseek\", __NR_llseek},\n\t{\"sendfile64\", __NR_sendfile64},\n#if defined(__ARCH_WANT_NEW_STAT) || defined(__ARCH_WANT_STAT64)\n\t{\"fstatat64\", __NR_fstatat64},\n\t{\"fstat64\", __NR_fstat64},\n#endif\n\t{\"mmap2\", __NR_mmap2},\n\t{\"fadvise64_64\", __NR_fadvise64_64},\n#ifdef __NR3264_stat\n\t{\"stat64\", __NR_stat64},\n\t{\"lstat64\", __NR_lstat64},\n#endif\n#endif\n\t{NULL, 0}\n};\n\nstatic const lunatik_namespace_t luasyscall_flags[] = {\n\t{\"numbers\", luasyscall_numbers},\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg luasyscall_lib[] = {\n\t{\"address\", luasyscall_address},\n\t{NULL, NULL}\n};\n\nLUNATIK_NEWLIB(syscall, luasyscall_lib, NULL, luasyscall_flags);\n\nstatic int __init luasyscall_init(void)\n{\n\tif ((luasyscall_table = (unsigned long **)lunatik_lookup(\"sys_call_table\")) == NULL)\n\t\treturn -ENXIO;\n\treturn 0;\n}\n\nstatic void __exit luasyscall_exit(void)\n{\n}\n\nmodule_init(luasyscall_init);\nmodule_exit(luasyscall_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ring-0.io>\");\n\n"
  },
  {
    "path": "lib/luathread.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Kernel thread primitives.\n* @module thread\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/kthread.h>\n\n#include <lunatik.h>\n\n/***\n* Represents a kernel thread object.\n* @type thread\n*/\n\ntypedef struct luathread_s {\n\tstruct task_struct *task;\n\tlunatik_object_t *runtime;\n} luathread_t;\n\nstatic int luathread_run(lua_State *L);\nstatic int luathread_current(lua_State *L);\n\nstatic int luathread_resume(lua_State *L, luathread_t *thread)\n{\n\tint nresults;\n\tint status = lua_resume(L, NULL, 0, &nresults);\n\tif (status != LUA_OK && status != LUA_YIELD) {\n\t\tpr_err(\"[%p] %s\\n\", thread, lua_tostring(L, -1));\n\t\tlua_pop(L, 1);\n\t\treturn -ENOEXEC;\n\t}\n\tlua_pop(L, nresults);\n\treturn 0;\n}\n\nstatic int luathread_func(void *data)\n{\n\tlunatik_object_t *object = (lunatik_object_t *)data;\n\tluathread_t *thread = (luathread_t *)object->private;\n\tint ret, locked = 0;\n\n\tlunatik_run(thread->runtime, luathread_resume, ret, thread);\n\n\twhile (!kthread_should_stop())\n\t\tif ((locked = lunatik_trylock(object)))\n\t\t\tbreak;\n\n\tthread->task = NULL;\n\n\tif (locked)\n\t\tlunatik_unlock(object);\n\n\tlunatik_putobject(thread->runtime);\n\tlunatik_putobject(object);\n\treturn ret;\n}\n\n/***\n* Checks if the current thread has been signaled to stop.\n* @function shouldstop\n* @treturn boolean `true` if the thread should stop, `false` otherwise.\n* @usage\n* while not thread.shouldstop() do\n*   linux.schedule(100)\n* end\n* @see stop\n*/\nstatic int luathread_shouldstop(lua_State *L)\n{\n\tlua_pushboolean(L, (current->flags & PF_KTHREAD) ? (int)kthread_should_stop() : 0);\n\treturn 1;\n}\n\n/***\n* Stops a running kernel thread.\n* Signals the thread to stop and waits for it to exit.\n* @function stop\n* @tparam thread self thread object to stop.\n* @treturn nil\n* @usage\n* my_thread:stop()\n*/\nstatic int luathread_stop(lua_State *L)\n{\n\tlunatik_object_t *object = lunatik_toobject(L, 1);\n\tluathread_t *thread = (luathread_t *)object->private;\n\tlunatik_object_t *runtime = thread->runtime;\n\tstruct task_struct *task = thread->task;\n\n\tif (runtime == NULL)\n\t\tpr_warn(\"[%p] thread wasn't created by us\\n\", thread);\n\telse if (task != NULL) {\n\t\tint result = kthread_stop(task);\n\n\t\tif (result == -EINTR) {\n\t\t\tthread->task = NULL;\n\t\t\tlunatik_putobject(thread->runtime);\n\t\t\tlunatik_putobject(object);\n\t\t\tpr_warn(\"[%p] thread has never run\\n\", thread);\n\t\t}\n\t\telse if (result == -ENOEXEC)\n\t\t\tpr_warn(\"[%p] thread has failed to execute\\n\", thread);\n\t}\n\telse\n\t\tpr_warn(\"[%p] thread has already stopped\\n\", thread);\n\treturn 0;\n}\n\n/***\n* Retrieves information about the kernel task associated with the thread.\n* @function task\n* @tparam thread self thread object.\n* @treturn table A table with fields: `cpu` (SMP only), `command`, `pid`, `tgid`.\n* @usage\n* local info = my_thread:task()\n*/\nstatic int luathread_task(lua_State *L)\n{\n\tlunatik_object_t *object = lunatik_toobject(L, 1);\n\tluathread_t *thread = (luathread_t *)object->private;\n\tstruct task_struct *task = thread->task;\n\n\tlua_createtable(L, 0, 4);\n\tint table = lua_gettop(L);\n\n#ifdef CONFIG_SMP\n\tlua_pushinteger(L, task->on_cpu);\n\tlua_setfield(L, table, \"cpu\");\n#endif\n\n\tlua_pushstring(L, task->comm);\n\tlua_setfield(L, table, \"command\");\n\n\tlua_pushinteger(L, task->pid);\n\tlua_setfield(L, table, \"pid\");\n\n\tlua_pushinteger(L, task->tgid);\n\tlua_setfield(L, table, \"tgid\");\n\treturn 1;\n}\n\nstatic const luaL_Reg luathread_lib[] = {\n\t{\"run\", luathread_run},\n\t{\"shouldstop\", luathread_shouldstop},\n\t{\"current\", luathread_current},\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg luathread_mt[] = {\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"stop\", luathread_stop},\n\t{\"task\", luathread_task},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t luathread_class = {\n\t.name = \"thread\",\n\t.methods = luathread_mt,\n\t.opt = LUNATIK_OPT_MONITOR,\n};\n\n#define luathread_new(L)\t(lunatik_newobject((L), &luathread_class, sizeof(luathread_t), LUNATIK_OPT_NONE))\n\n/***\n* Creates and starts a new kernel thread to run a Lua task.\n* The runtime must be sleepable; the script it loaded must return a function,\n* which becomes the thread body.\n* @function run\n* @tparam runtime runtime A sleepable Lunatik runtime whose script returns a function.\n* @tparam string name A descriptive name for the kernel thread.\n* @treturn thread A new thread object.\n* @raise Error if the runtime is not sleepable or if thread creation fails.\n* @see lunatik.runtime\n*/\nstatic int luathread_run(lua_State *L)\n{\n\tluaL_argcheck(L, lunatik_isready(L), 1, \"not allowed during module load\");\n\tlunatik_object_t *runtime = lunatik_checkobject(L, 1);\n\tluaL_argcheck(L, !lunatik_issoftirq(runtime->opt), 1, \"SOFTIRQ runtime cannot spawn threads\");\n\tconst char *name = luaL_checkstring(L, 2);\n\tlunatik_object_t *object = luathread_new(L);\n\tluathread_t *thread = object->private;\n\n\tlunatik_getobject(object);\n\tlunatik_getobject(runtime);\n\tthread->runtime = runtime;\n\n\tthread->task = kthread_run(luathread_func, object, name);\n\tif (IS_ERR(thread->task))\n\t\tluaL_error(L, \"failed to create a new thread\");\n\n\treturn 1; /* object */\n}\n\n/***\n* Gets a thread object representing the current kernel task.\n* If the current task was not created by `thread.run()`, the returned\n* object will not have an associated Lunatik runtime.\n* @function current\n* @treturn thread A thread object for the current task.\n* @usage\n* local t = thread.current()\n*/\nstatic int luathread_current(lua_State *L)\n{\n\tlunatik_object_t *object = luathread_new(L);\n\tluathread_t *thread = object->private;\n\n\tthread->runtime = NULL;\n\tthread->task = current;\n\treturn 1; /* object */\n}\n\nLUNATIK_CLASSES(thread, &luathread_class);\nLUNATIK_NEWLIB(thread, luathread_lib, luathread_classes, NULL);\n\nstatic int __init luathread_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit luathread_exit(void)\n{\n}\n\nmodule_init(luathread_init);\nmodule_exit(luathread_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ringzero.com.br>\");\n\n"
  },
  {
    "path": "lib/luaxdp.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2024-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* eXpress Data Path (XDP) integration.\n* This library allows Lua scripts to interact with the kernel's XDP subsystem.\n* It enables XDP/eBPF programs to call Lua functions for packet processing,\n* providing a flexible way to implement custom packet handling logic in Lua\n* at a very early stage in the network stack.\n*\n* The primary mechanism involves an XDP program calling the `bpf_luaxdp_run`\n* kfunc, which in turn invokes a Lua callback function previously registered\n* using `xdp.attach()`.\n* @module xdp\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/bpf.h>\n\n#include <lunatik.h>\n\n#include \"luarcu.h\"\n#include \"luadata.h\"\n\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0))\n#include <linux/btf.h>\n#include <linux/btf_ids.h>\n#include <net/xdp.h>\n\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0))\n__bpf_kfunc_start_defs();\n#else\n__diag_push();\n__diag_ignore_all(\"-Wmissing-prototypes\",\n                  \"Global kfuncs as their definitions will be in BTF\");\n#endif\n\nstatic lunatik_object_t *luaxdp_runtimes = NULL;\n\nstatic inline lunatik_object_t *luaxdp_pushdata(lua_State *L, int upvalue, void *ptr, size_t size)\n{\n\tlunatik_object_t *data;\n\n\tlua_pushvalue(L, lua_upvalueindex(upvalue));\n\tdata = (lunatik_object_t *)lunatik_toobject(L, -1);\n\tluadata_reset(data, ptr, size, LUADATA_OPT_KEEP);\n\treturn data;\n}\n\nstatic int luaxdp_callback(lua_State *L)\n{\n\tlunatik_object_t *buffer, *argument;\n\tstruct xdp_buff *ctx = (struct xdp_buff *)lua_touserdata(L, 1);\n\tvoid *arg = lua_touserdata(L, 2);\n\tsize_t arg__sz = (size_t)lua_tointeger(L, 3);\n\n\tlua_pushvalue(L, lua_upvalueindex(1)); /* callback */\n\tbuffer = luaxdp_pushdata(L, 2, ctx->data, ctx->data_end - ctx->data);\n\targument = luaxdp_pushdata(L, 3, arg, arg__sz);\n\n\tif (lua_pcall(L, 2, 1, 0) != LUA_OK) {\n\t\tluadata_clear(buffer);\n\t\tluadata_clear(argument);\n\t\treturn lua_error(L);\n\t}\n\n\tluadata_clear(buffer);\n\tluadata_clear(argument);\n\treturn 1;\n}\n\nstatic int luaxdp_handler(lua_State *L, struct xdp_buff *ctx, void *arg, size_t arg__sz)\n{\n\tint action = -1;\n\tint status;\n\n\tif (lunatik_getregistry(L, luaxdp_callback) != LUA_TFUNCTION) {\n\t\tpr_err(\"couldn't find callback\");\n\t\tgoto out;\n\t}\n\n\tlua_pushlightuserdata(L, ctx);\n\tlua_pushlightuserdata(L, arg);\n\tlua_pushinteger(L, (lua_Integer)arg__sz);\n\tif ((status = lua_pcall(L, 3, 1, 0)) != LUA_OK) {\n\t\tpr_err(\"%s\\n\", lua_tostring(L, -1));\n\t\tgoto out;\n\t}\n\n\taction = lua_tointeger(L, -1);\nout:\n\treturn action;\n}\n\nstatic inline int luaxdp_checkruntimes(void)\n{\n\tconst char *key = \"runtimes\";\n\tif (luaxdp_runtimes == NULL &&\n\t   (luaxdp_runtimes = luarcu_getobject(lunatik_env, key, sizeof(key))) == NULL)\n\t\treturn -1;\n\treturn 0;\n}\n\n__bpf_kfunc int bpf_luaxdp_run(char *key, size_t key__sz, struct xdp_md *xdp_ctx, void *arg, size_t arg__sz)\n{\n\tlunatik_object_t *runtime;\n\tstruct xdp_buff *ctx = (struct xdp_buff *)xdp_ctx;\n\tint action = -1;\n\tsize_t keylen = key__sz - 1;\n\n\tif (unlikely(luaxdp_checkruntimes() != 0)) {\n\t\tpr_err(\"couldn't find _ENV.runtimes\\n\");\n\t\tgoto out;\n\t}\n\n\tkey[keylen] = '\\0';\n\tif ((runtime = luarcu_getobject(luaxdp_runtimes, key, keylen)) == NULL) {\n\t\tpr_err(\"couldn't find runtime '%s'\\n\", key);\n\t\tgoto out;\n\t}\n\n\tlunatik_run(runtime, luaxdp_handler, action, ctx, arg, arg__sz);\n\tlunatik_putobject(runtime);\nout:\n\treturn action;\n}\n\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0))\n__bpf_kfunc_end_defs();\n#else\n__diag_pop();\n#endif\n\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 9, 0))\nBTF_KFUNCS_START(bpf_luaxdp_set)\nBTF_ID_FLAGS(func, bpf_luaxdp_run)\nBTF_KFUNCS_END(bpf_luaxdp_set)\n#else\nBTF_SET8_START(bpf_luaxdp_set)\nBTF_ID_FLAGS(func, bpf_luaxdp_run)\nBTF_SET8_END(bpf_luaxdp_set)\n#endif\n\nstatic const struct btf_kfunc_id_set bpf_luaxdp_kfunc_set = {\n\t.owner = THIS_MODULE,\n\t.set   = &bpf_luaxdp_set,\n};\n\n/***\n* Unregisters the Lua callback function associated with the current Lunatik runtime.\n* After calling this, `bpf_luaxdp_run` calls targeting this runtime will no longer\n* invoke a Lua function (they will likely return an error or default action).\n* @function detach\n* @treturn nil\n* @usage\n*   xdp.detach()\n* @within xdp\n*/\nstatic int luaxdp_detach(lua_State *L)\n{\n\tlunatik_unregister(L, luaxdp_callback);\n\treturn 0;\n}\n\n/***\n* Registers a Lua callback function to be invoked by an XDP/eBPF program.\n* When an XDP program calls the `bpf_luaxdp_run` kfunc, Lunatik will execute\n* the registered Lua `callback` associated with the current Lunatik runtime.\n* The runtime invoking this function must be non-sleepable.\n*\n* The `bpf_luaxdp_run` kfunc is called from an eBPF program with the following signature:\n* `int bpf_luaxdp_run(char *key, size_t key_sz, struct xdp_md *xdp_ctx, void *arg, size_t arg_sz)`\n*\n* - `key`: A string identifying the Lunatik runtime (e.g., the script name like \"examples/filter/sni\").\n*   This key is used to look up the runtime in Lunatik's internal table of active runtimes.\n* - `key_sz`: Length of the key string (including the null terminator).\n* - `xdp_ctx`: The XDP metadata context (`struct xdp_md *`).\n* - `arg`: A pointer to arbitrary data passed from eBPF to Lua.\n* - `arg_sz`: The size of the `arg` data.\n*\n* @function attach\n* @tparam function callback Lua function to call. It receives two arguments:\n*\n* 1. `buffer` (data): A `data` object representing the network packet buffer (`xdp_md`).\n*    The `data` object points to `xdp_ctx->data` and its size is `xdp_ctx->data_end - xdp_ctx->data`.\n* 2. `argument` (data): A `data` object representing the `arg` passed from the eBPF program.\n*    Its size is `arg_sz`.\n*\n*   The callback function should return an integer verdict, typically one of the values\n*   from the `xdp.action` table (e.g., `xdp.action.PASS`, `xdp.action.DROP`).\n* @treturn nil\n* @raise Error if the current runtime is sleepable or if internal setup fails.\n* @usage\n*   -- Lua script (e.g., \"my_xdp_handler.lua\" which is run via `lunatik run my_xdp_handler.lua`)\n*   local xdp = require(\"xdp\")\n*\n*   local function my_packet_processor(packet_buffer, custom_arg)\n*     print(\"Packet received, size:\", #packet_buffer)\n*     return xdp.action.PASS\n*   end\n*   xdp.attach(my_packet_processor)\n*\n*   -- In eBPF C code, to call the above Lua function:\n*   -- char rt_key[] = \"my_xdp_handler.lua\"; // Key matches the script name\n*   -- int verdict = bpf_luaxdp_run(rt_key, sizeof(rt_key), ctx, NULL, 0);\n* @see xdp.action\n* @see data\n* @within xdp\n*/\nstatic int luaxdp_attach(lua_State *L)\n{\n\tlunatik_checkruntime(L, LUNATIK_OPT_SOFTIRQ);\n\tluaL_checktype(L, 1, LUA_TFUNCTION); /* callback */\n\n\tluadata_new(L, LUNATIK_OPT_SINGLE); /* buffer */\n\tluadata_new(L, LUNATIK_OPT_SINGLE); /* argument */\n\n\tlua_pushcclosure(L, luaxdp_callback, 3);\n\tlunatik_register(L, -1, luaxdp_callback);\n\treturn 0;\n}\n#endif\n\nstatic const luaL_Reg luaxdp_lib[] = {\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0))\n\t{\"attach\", luaxdp_attach},\n\t{\"detach\", luaxdp_detach},\n#endif\n\t{NULL, NULL}\n};\n\n/***\n* Table of XDP action verdicts.\n* These constants define the possible return values from an XDP program (and thus\n* from the Lua callback attached via `xdp.attach`) to indicate how the packet\n* should be handled.\n* (Constants from `<uapi/linux/bpf.h>`)\n* @table action\n*   @tfield integer ABORTED Indicates an error; packet is dropped. (XDP_ABORTED)\n*   @tfield integer DROP Drop the packet silently. (XDP_DROP)\n*   @tfield integer PASS Pass the packet to the normal network stack. (XDP_PASS)\n*   @tfield integer TX Transmit the packet back out the same interface it arrived on. (XDP_TX)\n*   @tfield integer REDIRECT Redirect the packet to another interface or BPF map. (XDP_REDIRECT)\n* @within xdp\n*/\nstatic const lunatik_reg_t luaxdp_action[] = {\n\t{\"ABORTED\", XDP_ABORTED},\n\t{\"DROP\", XDP_DROP},\n\t{\"PASS\", XDP_PASS},\n\t{\"TX\", XDP_TX},\n\t{\"REDIRECT\", XDP_REDIRECT},\n\t{NULL, 0}\n};\n\nstatic const lunatik_namespace_t luaxdp_flags[] = {\n\t{\"action\", luaxdp_action},\n\t{NULL, NULL}\n};\n\nLUNATIK_NEWLIB(xdp, luaxdp_lib, NULL, luaxdp_flags);\n\nstatic int __init luaxdp_init(void)\n{\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0))\n\treturn register_btf_kfunc_id_set(BPF_PROG_TYPE_XDP, &bpf_luaxdp_kfunc_set);\n#else\n\treturn 0;\n#endif\n}\n\nstatic void __exit luaxdp_exit(void)\n{\n#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0))\n\tif (luaxdp_runtimes != NULL)\n\t\tlunatik_putobject(luaxdp_runtimes);\n#endif\n}\n\nmodule_init(luaxdp_init);\nmodule_exit(luaxdp_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ring-0.io>\");\n\n"
  },
  {
    "path": "lib/lunatik/runner.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n--- Manages the execution and lifecycle of Lunatik scripts.\n-- This module provides functionalities to run scripts as isolated runtimes,\n-- spawn them into separate kernel threads, and manage their state (start, stop, list, shutdown).\n-- It uses RCU-safe tables to store references to active runtimes and threads.\n-- In following descriptions, 'env' variable stands for 'lunatik._ENV'.\n--\n-- @module lunatik.runner\n\nlocal lunatik = require(\"lunatik\")\nlocal thread  = require(\"thread\")\nlocal rcu     = require(\"rcu\")\n\nlocal env = lunatik._ENV\n\nlocal runner = {}\n\n--- Removes the \".lua\" extension from a script filename.\n-- @local\n-- @function trim\n-- @tparam string script script filename (e.g., \"myscript.lua\").\n-- @treturn string script name without the \".lua\" extension (e.g., \"myscript\").\nlocal function trim(script) -- drop \".lua\" file extension\n\treturn script:gsub(\"(%w+).lua\", \"%1\")\nend\n\n--- Runs a Lunatik script in the current context.\n-- Creates a new Lunatik runtime for the given script and registers it.\n-- Throws an error if a script with the same name is already running.\n-- @tparam string script path or name of the Lua script to run. The \".lua\" extension will be trimmed.\n-- @tparam[opt] string context Execution context: `\"process\"` (default) or `\"softirq\"` (for netfilter/XDP hooks).\n-- @treturn table created Lunatik runtime object.\n-- @raise error if the script is already running.\nfunction runner.run(script, ...)\n\tlocal script = trim(script)\n\tif env.runtimes[script] then\n\t\terror(string.format(\"%s is already running\", script))\n\tend\n\tlocal runtime = lunatik.runtime(script, ...)\n\tenv.runtimes[script] = runtime\n\treturn runtime\nend\n\n--- Spawns a Lunatik script in a new kernel thread.\n-- First, it runs the script using `runner.run`, then creates a new kernel thread\n-- to execute the runtime. The thread is named based on the script's filename.\n-- The spawned script is expected to return a function, which will then be executed in the new thread.\n-- @tparam string script path or name of the Lua script to spawn.\n-- @treturn userdata kernel thread object.\nfunction runner.spawn(script, ...)\n\tlocal runtime = runner.run(script, ...)\n\tlocal name = string.match(script, \"(%w*/*%w*)$\")\n\tlocal t = thread.run(runtime, name)\n\tenv.threads[script] = t\n\treturn t\nend\n\n--- Stops an item (runtime or thread) in the given registry.\n-- If the item exists in the registry, its `stop()` method is called,\n-- and it's removed from the registry.\n-- @local\n-- @function stop\n-- @tparam table registry registry table (e.g., `env.threads` or `env.runtimes`).\n-- @tparam string script key (script name) of the item to stop.\nlocal function stop(registry, script)\n\tif registry[script] then\n\t\tregistry[script]:stop()\n\t\tregistry[script] = nil\n\tend\nend\n\n--- Stops a running script and its associated thread, if any.\n-- It attempts to stop the thread first, then the runtime.\n-- @tparam string script name of the script to stop. The \".lua\" extension will be trimmed.\nfunction runner.stop(script)\n\tlocal script = trim(script)\n\tstop(env.threads, script)\n\tstop(env.runtimes, script)\nend\n\n--- Lists the names of all currently running scripts.\n-- Iterates over the `env.runtimes` RCU table to collect script names.\n-- @treturn string A comma-separated string of running script names, or an empty string if no scripts are running.\nfunction runner.list()\n\tlocal list = {}\n\trcu.map(env.runtimes, function (script)\n\t\ttable.insert(list, script)\n\tend)\n\treturn table.concat(list, ', ')\nend\n\n--- Shuts down all running scripts and their threads.\n-- Iterates over `env.runtimes` and calls `runner.stop` for each script.\nfunction runner.shutdown()\n\trcu.map(env.runtimes, function (script)\n\t\trunner.stop(script)\n\tend)\nend\n\n--- Initializes the runner's internal state.\n-- Creates RCU-safe tables for storing runtimes and threads.\n-- This is typically called during Lunatik's initialization.\nfunction runner.startup()\n\tif env.runtimes then return end\n\tenv.runtimes = rcu.table()\n\tenv.threads = rcu.table()\nend\n\nreturn runner\n\n"
  },
  {
    "path": "lib/mailbox.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2024 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only \n--\n\n---\n-- Inter-runtime communication mechanism using FIFOs and completions.\n-- This module provides a way for different Lunatik runtimes (Lua states)\n-- to send and receive messages to/from each other. It uses a FIFO queue\n-- for message storage and a completion object for synchronization.\n--\n-- Mailboxes are unidirectional (`inbox` for receiving only, `outbox` for sending only).\n-- Messages are serialized as strings.\n--\n-- @module mailbox\n-- @see fifo\n-- @see completion\n--\n\nlocal fifo       = require(\"fifo\")\nlocal completion = require(\"completion\")\n\n---\n-- The main mailbox table.\n-- @table mailbox\nlocal mailbox = {} \n\n---\n-- Metatable for MailBox objects.\n-- This table defines the methods available on mailbox instances.\n-- @type MailBox\n-- @field queue (fifo) The underlying FIFO queue used for message storage.\n-- @field event (completion) The completion object used for synchronization.\nlocal MailBox = {}\nMailBox.__index = MailBox\n\n---\n-- Internal constructor for mailbox objects.\n-- @param q (fifo|number) Either an existing FIFO object or a capacity for a new FIFO.\n-- @param e (completion) [optional] An existing completion object. If nil and `q` is a number, a new completion is created.\n-- @param allowed (string) The allowed operation (\"send\" or \"receive\").\n-- @param forbidden (string) The forbidden operation (\"send\" or \"receive\").\n-- @return (MailBox) The new mailbox object.\n-- @local\nlocal function new(q, e, allowed, forbidden)\n\tlocal mbox = {}\n\tif type(q) == 'userdata' then\n\t\tmbox.queue, mbox.event = q, e\n\telse\n\t\tmbox.queue, mbox.event = fifo.new(q), completion.new()\n\tend\n\tmbox[forbidden] = function () error(allowed .. \"-only mailbox\") end\n\treturn setmetatable(mbox, MailBox)\nend\n\n---\n-- Creates a new inbox (receive-only mailbox).\n-- @param q (fifo|number) Either an existing FIFO object or a capacity for a new FIFO.\n--   If a number, a new FIFO with this capacity will be created.\n-- @param e (completion) [optional] An existing completion object. If nil and `q` is a number,\n--   a new completion object will be created.\n-- @return (MailBox) A new inbox object.\n-- @usage\n--   local my_inbox = mailbox.inbox(10) -- Inbox with capacity for 10 messages\n--   local msg = my_inbox:receive()\nfunction mailbox.inbox(q, e)\n\treturn new(q, e, 'receive', 'send')\nend\n\n---\n-- Creates a new outbox (send-only mailbox).\n-- @param q (fifo|number) Either an existing FIFO object or a capacity for a new FIFO.\n--   If a number, a new FIFO with this capacity will be created.\n-- @param e (completion) [optional] An existing completion object. If nil and `q` is a number,\n--   a new completion object will be created.\n-- @return (MailBox) A new outbox object.\n-- @usage\n--   local my_outbox = mailbox.outbox(10) -- Outbox with capacity for 10 messages\n--   my_outbox:send(\"hello\")\nfunction mailbox.outbox(q, e)\n\treturn new(q, e, 'send', 'receive')\nend\n\nlocal sizeoft = string.packsize(\"T\")\n\n---\n-- Receives a message from the mailbox.\n-- This function will block until a message is available or the timeout expires.\n-- Not available on outboxes.\n-- @function MailBox:receive\n-- @tparam[opt] number timeout maximum time to wait in jiffies.\n--   If omitted or negative, waits indefinitely. If 0, returns immediately.\n-- @treturn[1] string received message.\n-- @treturn[1] nil If no message is received (e.g., FIFO is empty after event or on timeout).\n-- @treturn[2] string Error message if the wait times out or another error occurs.\n-- @raise Error if called on an outbox, or if the underlying event wait fails,\n--   or if a malformed message is encountered.\nfunction MailBox:receive(timeout)\n\tlocal ok, err = self.event:wait(timeout)\n\tif not ok then error(err) end\n\n\tlocal queue = self.queue\n\tlocal header, header_size = queue:pop(sizeoft)\n\n\tif header_size == 0 then\n\t\treturn nil\n\telseif header_size < sizeoft then\n\t\terror(\"malformed message\")\n\tend\n\n\treturn queue:pop(string.unpack(\"T\", header))\nend\n\n---\n-- Sends a message to the mailbox.\n-- Not available on inboxes.\n-- @function MailBox:send\n-- @tparam string message message to send.\n-- @raise Error if called on an inbox.\nfunction MailBox:send(message)\n\tself.queue:push(string.pack(\"s\", message))\n\tself.event:complete()\nend\n\nreturn mailbox\n\n"
  },
  {
    "path": "lib/net.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2023-2025 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n---\n-- Network utility functions.\n-- This module provides helper functions for network-related operations,\n-- primarily for converting between string and integer representations of IPv4 addresses.\n-- @module net\n--\n\nlocal net = {}\n\n---\n-- Converts an IPv4 address string to its integer representation.\n-- \"Address to Number\"\n-- @param addr (string) The IPv4 address string (e.g., \"127.0.0.1\").\n-- @return (number) The IPv4 address as an integer.\n-- @usage\n--   local ip_int = net.aton(\"192.168.1.1\")\nfunction net.aton(addr)\n\tlocal i = 1\n\tlocal bits = { 24, 16, 8, 0 }\n\tlocal ip = 0\n\tfor n in string.gmatch(addr, \"(%d+)\") do\n\t\tlocal n = tonumber(n) & 0xFF\n\t\tip = ip | (n << bits[i])\n\t\ti = i + 1\n\tend\n\treturn ip\nend\n\n---\n-- Converts an integer representation of an IPv4 address to its string form.\n-- \"Number to Address\"\n-- @param ip (number) The IPv4 address as an integer.\n-- @return (string) The IPv4 address string (e.g., \"127.0.0.1\").\n-- @usage\n--   local ip_str = net.ntoa(3232235777)  -- \"192.168.1.1\"\nfunction net.ntoa(ip)\n\tlocal n = 4\n\tlocal bytes = {}\n\tfor i = 1, n do\n\t\tlocal shift = (n - i) * 8\n\t\tlocal mask = 0xFF << shift\n\t\tbytes[i] = (ip & mask) >> shift\n\tend\n\treturn table.concat(bytes, \".\")\nend\n\nreturn net\n\n"
  },
  {
    "path": "lib/socket/inet.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2023-2025 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only \n--\n\n---\n-- Internet (AF_INET) socket operations.\n-- This module provides a higher-level abstraction over the raw `socket` and `net`\n-- modules for creating and managing INET (IPv4) sockets, including TCP and UDP.\n--\n-- It simplifies common socket operations like binding, connecting, sending,\n-- and receiving data.\n--\n-- @module socket.inet\n-- @see socket\n--\n\nlocal socket = require(\"socket\")\nlocal net = require(\"net\")\n\n---\n-- Base class for socket types.\n-- @type inet\n-- @field localhost (string) The loopback address '127.0.0.1'.\nlocal inet = {localhost = '127.0.0.1'}\n\n---\n-- Constructor for inet socket objects.\n-- Initializes a new socket object, setting up its metatable for OOP-like behavior.\n-- @param o (table) [optional] An initial table to be used as the object.\n-- @return (table) The new inet socket object.\nfunction inet:new(o)\n\tlocal o = o or {}\n\tself.__index = self\n\tself.__close = self.close\n\treturn setmetatable(o, self)\nend\n\nlocal af = socket.af\n---\n-- Metamethod to create a new socket instance when `inet()` or `inet.tcp()` or `inet.udp()` is called.\n-- This is the primary way to create new socket instances (e.g., `local sock = inet.tcp()`).\n-- @return (table) A new socket object (e.g., a TCP or UDP socket object).\n-- @see socket.new\n-- @usage\n-- local tcp_socket = inet.tcp()\n-- local udp_socket = inet.udp()\nfunction inet:__call()\n\tlocal o = self:new()\n\to.socket = socket.new(af.INET, self.type, self.proto)\n\treturn o\nend\n\n---\n-- Closes the underlying socket.\n-- @see socket.close\nfunction inet:close()\n\tself.socket:close()\nend\n\n---\n-- Receives data from the socket.\n-- For UDP, if the `raw` parameter (third boolean) is true, it returns the raw IP address.\n-- @param ... Varargs passed directly to the underlying `socket:receive()`.\n-- Typically `(len, flags, raw_ip_for_udp)`.\n-- @return Varargs returned by the underlying `socket:receive()`.\n-- Typically `(data, ip_address, port)` or `(data, error_message)`.\n-- @see socket.receive\nfunction inet:receive(...)\n\treturn self.socket:receive(...)\nend\n\n---\n-- Sends data through the socket.\n-- If `addr` and `port` are provided (typical for UDP), the address is converted\n-- using `net.aton` before sending.\n-- @param msg (string) The message to send.\n-- @param addr (string) [optional] The destination IP address (e.g., for UDP).\n-- @param port (number) [optional] The destination port (e.g., for UDP).\n-- @return (number) Number of bytes sent on success\n-- @raise error on failure\n-- @see socket.send\nfunction inet:send(msg, addr, port)\n\tlocal sock = self.socket\n\treturn not addr and sock:send(msg) or sock:send(msg, net.aton(addr), port)\nend\n\n---\n-- Binds the socket to a specific address and port.\n-- The address '*' is treated as `INADDR_ANY` (0.0.0.0).\n-- @param addr (string) The IP address to bind to. Use '*' for any address.\n-- @param port (number) The port number to bind to.\n-- @return (boolean or nil) True on success, or nil and an error message on failure.\n-- @see socket.bind\nfunction inet:bind(addr, port)\n\tlocal ip = addr ~= '*' and net.aton(addr) or 0\n\treturn self.socket:bind(ip, port)\nend\n\n---\n-- Connects the socket to a remote address and port.\n-- @param addr (string) The remote IP address.\n-- @param port (number) The remote port.\n-- @param flags (number) [optional] Connection flags.\n-- @return (boolean or nil) True on success, or nil and an error message on failure.\n-- @see socket.connect\nfunction inet:connect(addr, port, flags)\n\tself.socket:connect(net.aton(addr), port, flags)\nend\n\n---\n-- Internal helper function to get socket address information.\n-- @param what (string) Either \"sockname\" or \"peername\".\n-- @return (string) IP address in string format.\n-- @return (number) Port number.\n-- @local\n-- @see socket.getsockname\n-- @see socket.getpeername\n-- @see net.ntoa\nfunction inet:getaddr(what)\n\tlocal socket = self.socket\n\tlocal ip, port = socket['get' .. what](socket)\n\treturn net.ntoa(ip), port\nend\n\n---\n-- Gets the local address (IP and port) to which the socket is bound.\n-- @return (string) Local IP address.\n-- @return (number) Local port number.\n-- @see socket.getsockname\nfunction inet:getsockname()\n\treturn self:getaddr(\"sockname\")\nend\n\n---\n-- Gets the remote address (IP and port) to which the socket is connected.\n-- @return (string) Remote IP address.\n-- @return (number) Remote port number.\n-- @see socket.getpeername\nfunction inet:getpeername()\n\treturn self:getaddr(\"peername\")\nend\n\nlocal sock, ipproto = socket.sock, socket.ipproto\n\n---\n-- TCP socket specialization.\n-- Provides methods specific to TCP sockets (e.g., `listen`, `accept`).\n-- Create TCP sockets using `inet.tcp()`.\n-- @table inet.tcp\n-- @field type The socket type (e.g., `socket.sock.STREAM`).\n-- @field proto The protocol (e.g., `socket.ipproto.TCP`).\ninet.tcp = inet:new{type = sock.STREAM, proto = ipproto.TCP}\n\n---\n-- Listens for incoming connections on a TCP socket.\n-- @param backlog (number) The maximum length of the queue of pending connections.\n-- @return (boolean or nil) True on success, or nil and an error message on failure.\n-- @see socket.listen\nfunction inet.tcp:listen(backlog)\n\tself.socket:listen(backlog)\nend\n\n---\n-- Accepts an incoming connection on a listening TCP socket.\n-- @param flags (number) [optional] Flags for the accept operation.\n-- @return (table or nil) A new socket object for the accepted connection, or nil and an error message.\n-- @see socket.accept\nfunction inet.tcp:accept(flags)\n\treturn self.socket:accept(flags)\nend\n\n---\n-- UDP socket specialization.\n-- Provides methods specific to UDP sockets (e.g., `receivefrom`, `sendto`).\n-- Create UDP sockets using `inet.udp()`.\n-- @table inet.udp\n-- @field type The socket type (e.g., `socket.sock.DGRAM`).\n-- @field proto The protocol (e.g., `socket.ipproto.UDP`).\ninet.udp = inet:new{type = sock.DGRAM, proto = ipproto.UDP}\n\n---\n-- Receives data from a UDP socket, along with the sender's address.\n-- This is a wrapper around `inet:receive` that converts the raw IP address\n-- from `net.aton` format to a string using `net.ntoa`.\n-- @param len (number) [optional] The maximum number of bytes to receive.\n-- @param flags (number) [optional] Flags for the receive operation.\n-- @return (string or nil) The received data, or nil on error.\n-- @return (string or nil) The sender's IP address, or an error message.\n-- @return (number or nil) The sender's port number.\n-- @see socket.receive\nfunction inet.udp:receivefrom(len, flags)\n\tlocal msg, ip, port = self:receive(len, flags, true)\n\treturn msg, net.ntoa(ip), port\nend\n\n---\n-- Alias for `inet.udp:send`. Sends data to a specific address and port using UDP.\n-- @function inet.udp:sendto\n-- @param msg (string) The message to send.\n-- @param addr (string) The destination IP address.\n-- @param port (number) The destination port.\n-- @return (boolean or nil) True on success, or nil and an error message on failure.\n-- @see inet.send\n-- @see socket.send\ninet.udp.sendto = inet.udp.send -- Alias for send\n\nreturn inet\n\n"
  },
  {
    "path": "lib/socket/raw.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ashwani Kumar Kamal <ashwanikamal.im421@gmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n---\n-- RAW AF_PACKET socket operations.\n-- This module provides a higher-level abstraction over the `socket` module.\n--\n-- @module socket.raw\n-- @see socket\n--\nlocal socket = require(\"socket\")\nlocal eth    = require(\"linux.eth\")\n\nlocal af   = socket.af\nlocal sock = socket.sock\n\nlocal raw = {}\n\n---\n-- Creates and binds a raw packet socket for receiving frames.\n-- @param proto (number) EtherType (defaults to ETH_P_ALL).\n-- @param ifindex (number) [optional] Interface index (defaults to listen all interfaces).\n-- @return A new raw packet socket bound for proto and ifindex.\n-- @raise Error if socket.new() or socket.bind() fail.\n-- @usage\n--   local rx <close> = raw.bind(0x0003)\n--   local tx <close> = raw.bind(0x88cc, ifindex)\n-- @see socket.new\n-- @see socket.bind\nfunction raw.bind(proto, ifindex)\n\tlocal proto = proto or eth.ALL\n\tlocal ifindex = ifindex or 0\n\tlocal s = socket.new(af.PACKET, sock.RAW, proto)\n\ts:bind(proto, ifindex)\n\treturn s\nend\n\nreturn raw\n\n"
  },
  {
    "path": "lib/socket/unix.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025-2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n---\n-- UNIX domain socket (AF_UNIX) operations.\n-- This module provides an OOP-style abstraction over the `socket` module for\n-- AF_UNIX sockets, modeled after `socket.inet`.\n--\n-- A socket instance can be created with an optional path stored as default\n-- address, which is then reused automatically in `bind`, `connect`, and\n-- `dgram:sendto` without an explicit per-call address.\n--\n-- @module socket.unix\n-- @see socket\n-- @see socket.inet\n--\nlocal socket = require(\"socket\")\n\nlocal af   = socket.af\nlocal sock = socket.sock\n\n---\n-- Base class for UNIX domain socket objects.\n-- @type unix\nlocal unix = {}\n\n---\n-- Constructor for unix socket objects.\n-- Initializes a new socket object and sets up its metatable.\n-- @param o (table) [optional] An initial table to use as the object.\n-- @return (table) The new unix socket object.\nfunction unix:new(o)\n\tlocal o = o or {}\n\tself.__index = self\n\tself.__close = self.close\n\treturn setmetatable(o, self)\nend\n\n---\n-- Creates a new UNIX domain socket instance.\n-- This is the primary way to create instances (e.g. `local s = unix.stream(path)`).\n-- @param path (string) [optional] Default UNIX socket path stored in the object.\n--   Reused automatically by `bind`, `connect`, `send`, `sendto`, and `receivefrom`\n--   when no explicit path is given.\n-- @return (table) A new unix socket object.\n-- @see socket.new\nfunction unix:__call(path)\n\tlocal o = self:new{path = path}\n\to.socket = socket.new(af.UNIX, self.type, 0)\n\treturn o\nend\n\n---\n-- Closes the underlying socket.\n-- @see socket.close\nfunction unix:close()\n\tself.socket:close()\nend\n\n---\n-- Binds the socket to a UNIX domain path.\n-- @param path (string) [optional] Path to bind to.\n--   Defaults to the path provided at construction time.\n-- @see socket.bind\nfunction unix:bind(path)\n\tself.socket:bind(path or self.path)\nend\n\n---\n-- Connects the socket to a UNIX domain path.\n-- @param path (string) [optional] Path to connect to.\n--   Defaults to the path provided at construction time.\n-- @see socket.connect\nfunction unix:connect(path)\n\tself.socket:connect(path or self.path)\nend\n\n---\n-- Sends data through the socket.\n-- For connected STREAM sockets, `path` is omitted. For DGRAM sockets,\n-- use `sendto()` to send to the stored path or an explicit destination.\n-- @param msg (string) The message to send.\n-- @param path (string) [optional] Explicit destination socket path.\n-- @return (number) Number of bytes sent on success.\n-- @raise error on failure.\n-- @see socket.send\nfunction unix:send(msg, path)\n\treturn path and self.socket:send(msg, path) or self.socket:send(msg)\nend\n\n---\n-- Receives data from the socket.\n-- @param ... Varargs passed directly to `socket:receive()`.\n-- @return Varargs returned by `socket:receive()`.\n-- @see socket.receive\nfunction unix:receive(...)\n\treturn self.socket:receive(...)\nend\n\n---\n-- Gets the local address (path) of the socket.\n-- @return (string) The local socket path.\n-- @see socket.getsockname\nfunction unix:getsockname()\n\treturn self.socket:getsockname()\nend\n\n---\n-- Gets the remote address (path) the socket is connected to.\n-- @return (string) The remote socket path.\n-- @see socket.getpeername\nfunction unix:getpeername()\n\treturn self.socket:getpeername()\nend\n\n---\n-- STREAM socket specialization (connection-oriented).\n-- Create instances with `unix.stream([path])`.\n-- @table unix.stream\n-- @field type The socket type (`socket.sock.STREAM`).\nunix.stream = unix:new{type = sock.STREAM}\n\n---\n-- Listens for incoming connections on a STREAM socket.\n-- @param backlog (number) Maximum length of the queue of pending connections.\n-- @see socket.listen\nfunction unix.stream:listen(backlog)\n\tself.socket:listen(backlog)\nend\n\n---\n-- Accepts an incoming connection on a listening STREAM socket.\n-- @param flags (number) [optional] Flags for the accept operation.\n-- @return A new socket object for the accepted connection.\n-- @see socket.accept\nfunction unix.stream:accept(flags)\n\treturn self.socket:accept(flags)\nend\n\n---\n-- DGRAM socket specialization (connectionless).\n-- Create instances with `unix.dgram([path])`.\n-- The optional path is stored as the default destination for `sendto`.\n-- @table unix.dgram\n-- @field type The socket type (`socket.sock.DGRAM`).\nunix.dgram = unix:new{type = sock.DGRAM}\n\n---\n-- Receives data from a DGRAM socket along with the sender's path.\n-- @param len (number) [optional] Maximum number of bytes to receive.\n-- @param flags (number) [optional] Receive flags.\n-- @return (string or nil) The received data.\n-- @return (string or nil) The sender's socket path.\n-- @see socket.receive\nfunction unix.dgram:receivefrom(len, flags)\n\treturn self:receive(len, flags, true)\nend\n\n---\n-- Sends data to a UNIX domain socket path.\n-- If `path` is omitted, uses the path provided at construction time.\n-- @param msg (string) The message to send.\n-- @param path (string) [optional] Destination socket path.\n-- @return (number) Number of bytes sent on success.\n-- @raise error on failure.\n-- @see unix.send\nfunction unix.dgram:sendto(msg, path)\n\tlocal dest = path or self.path\n\treturn dest and self.socket:send(msg, dest) or self.socket:send(msg)\nend\n\nreturn unix\n\n"
  },
  {
    "path": "lib/syscall/table.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2024 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n---\n-- A table mapping system call names to their kernel addresses.\n-- This module provides a pre-populated Lua table where each key\n-- is a system call name (string, e.g., \"openat\") and its corresponding\n-- value is the kernel address of that system call (lightuserdata).\n--\n-- @module syscall.table\n-- @see syscall\n-- @see syscall.numbers\n-- @see syscall.address\n-- @usage\n--   local syscall_addrs = require(\"syscall.table\")\n--\n--   if syscall_addrs.openat then\n--     print(\"Address of 'openat':\", syscall_addrs.openat)\n--   end\n--\n--   if syscall_addrs.close then\n--     print(\"Address of 'close':\", syscall_addrs.close)\n--   end\n--\n\nlocal syscall = require(\"syscall\")\n\nlocal table = {}\n\nlocal numbers = syscall.numbers\nfor name, number in pairs(numbers) do\n\ttable[name] = syscall.address(number)\nend\n\nreturn table\n\n"
  },
  {
    "path": "lib/util.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025 jperon <cataclop@hotmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n--- Utility functions.\n-- @module util\n\nlocal util = {}\nlocal char, format, gsub, rep, su = string.char, string.format, string.gsub, string.rep, string.unpack\n\n--- Converts a binary string to its hexadecimal representation.\n-- @function bin2hex\n-- @tparam string str binary string to convert.\n-- @treturn string hexadecimal representation of the input.\nfunction util.bin2hex(str)\n\treturn format(rep(\"%.2x\", #str), su(rep(\"B\", #str), str))\nend\n\n--- Converts a hexadecimal string to its binary representation.\n-- @function hex2bin\n-- @tparam string hex hexadecimal string to convert.\n-- @treturn string binary representation of the input.\nfunction util.hex2bin(hex)\n\treturn gsub(hex, \"..\", function(cc) return char(tonumber(cc, 16)) end)\nend\n\n--- Logs a message with a specific prefix.\n-- @function log\n-- @tparam string what prefix for the log message (e.g., \"info\", \"error\").\n-- @tparam ... Additional arguments to log, which will be concatenated with tabs.\n-- @usage util.log(\"info\", \"This is a message\")\n-- @usage util.log(\"error\", \"An error occurred\", \"Error message\")\nfunction util.log(what, ...)\n\tprint(table.concat({what:upper(), ...}, \"\\t\"))\nend\n\n--- Runs a test function and prints the result.\n-- @tparam string test_name test name.\n-- @tparam function func test function to run.\n-- @usage util.test(\"Test Name\", function() ... end)\nfunction util.test(test_name, func)\n\tlocal status, err = pcall(func)\n\tif status then\n\t\tutil.log(\"pass\", test_name)\n\telse\n\t\tutil.log(\"fail\", test_name, err, \"\\n\" .. debug.traceback())\n\tend\nend\n\nreturn util\n\n"
  },
  {
    "path": "lunatik.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_h\n#define lunatik_h\n\n#include <linux/mutex.h>\n#include <linux/spinlock.h>\n#include <linux/slab.h>\n#include <linux/kref.h>\n#include <linux/version.h>\n\n#include <lua.h>\n#include <lauxlib.h>\n\n#define LUNATIK_VERSION\t\"Lunatik 4.2\"\n\ntypedef u8 __bitwise lunatik_opt_t;\n#define LUNATIK_OPT_SOFTIRQ\t((__force lunatik_opt_t)(1U << 0))\n#define LUNATIK_OPT_MONITOR\t((__force lunatik_opt_t)(1U << 1))\n#define LUNATIK_OPT_SINGLE\t((__force lunatik_opt_t)(1U << 2))\n#define LUNATIK_OPT_EXTERNAL\t((__force lunatik_opt_t)(1U << 3))\n#define LUNATIK_OPT_NONE\t((__force lunatik_opt_t)0)\n\n#define lunatik_issoftirq(opt)\t\t((opt) & LUNATIK_OPT_SOFTIRQ)\n#define lunatik_ismonitor(opt)\t\t((opt) & LUNATIK_OPT_MONITOR)\n#define lunatik_issingle(opt)\t\t((opt) & LUNATIK_OPT_SINGLE)\n#define lunatik_isexternal(opt)\t\t((opt) & LUNATIK_OPT_EXTERNAL)\n\n#define lunatik_locker(o, mutex_op, spin_op)\t\\\ndo {\t\t\t\t\t\t\\\n\tif (!lunatik_issoftirq((o)->opt))\t\\\n\t\tmutex_op(&(o)->mutex);\t\t\\\n\telse\t\t\t\t\t\\\n\t\tspin_op(&(o)->spin);\t\t\\\n} while (0)\n\n#define lunatik_newlock(o)\tlunatik_locker((o), mutex_init, spin_lock_init);\n#define lunatik_freelock(o)\tlunatik_locker((o), mutex_destroy, (void));\n#define lunatik_lock(o)\t\tlunatik_locker((o), mutex_lock, spin_lock_bh)\n#define lunatik_unlock(o)\tlunatik_locker((o), mutex_unlock, spin_unlock_bh)\n\n#define lunatik_toruntime(L)\t(*(lunatik_object_t **)lua_getextraspace(L))\n\n#define lunatik_cannotsleep(L, s)\t((s) && lunatik_issoftirq(lunatik_toruntime(L)->opt))\n#define lunatik_getstate(runtime)\t((lua_State *)runtime->private)\n\nstatic inline bool lunatik_isready(lua_State *L)\n{\n\tbool ready;\n\tlua_rawgetp(L, LUA_REGISTRYINDEX, L);\n\tready = lua_toboolean(L, -1);\n\tlua_pop(L, 1);\n\treturn ready;\n}\n\n#define lunatik_handle(runtime, handler, ret, ...)\t\\\ndo {\t\t\t\t\t\t\t\\\n\tlua_State *L = lunatik_getstate(runtime);\t\\\n\tint n = lua_gettop(L);\t\t\t\t\\\n\tret = handler(L, ## __VA_ARGS__);\t\t\\\n\tlua_settop(L, n);\t\t\t\t\\\n} while (0)\n\n#define lunatik_run(runtime, handler, ret, ...)\t\t\t\t\\\ndo {\t\t\t\t\t\t\t\t\t\\\n\tlunatik_lock(runtime);\t\t\t\t\t\t\\\n\tif (unlikely(!lunatik_getstate(runtime)))\t\t\t\\\n\t\tret = -ENXIO;\t\t\t\t\t\t\\\n\telse\t\t\t\t\t\t\t\t\\\n\t\tlunatik_handle(runtime, handler, ret, ## __VA_ARGS__);\t\\\n\tlunatik_unlock(runtime);\t\t\t\t\t\\\n} while(0)\n\ntypedef struct lunatik_reg_s {\n\tconst char *name;\n\tlua_Integer value;\n} lunatik_reg_t;\n\ntypedef struct lunatik_namespace_s {\n\tconst char *name;\n\tconst lunatik_reg_t *reg;\n} lunatik_namespace_t;\n\ntypedef struct lunatik_class_s {\n\tconst char *name;\n\tconst luaL_Reg *methods;\n\tvoid (*release)(void *);\n\tlunatik_opt_t opt;\n} lunatik_class_t;\n\ntypedef struct lunatik_object_s {\n\tstruct kref kref;\n\tconst lunatik_class_t *class;\n\tvoid *private;\n\tunion {\n\t\tstruct mutex mutex;\n\t\tspinlock_t spin;\n\t};\n\tlunatik_opt_t opt;\n\tgfp_t gfp;\n} lunatik_object_t;\n\nextern lunatik_object_t *lunatik_env;\n\nstatic inline int lunatik_trylock(lunatik_object_t *object)\n{\n\treturn unlikely(lunatik_ismonitor(object->opt)) ?\n\t\t(lunatik_issoftirq(object->opt) ? spin_trylock(&object->spin) : mutex_trylock(&object->mutex)) : 1;\n}\n\nint lunatik_runtime(lunatik_object_t **pruntime, const char *script, lunatik_opt_t opt);\nint lunatik_stop(lunatik_object_t *runtime);\n\nstatic inline int lunatik_nop(lua_State *L)\n{\n\treturn 0;\n}\n\n#define LUNATIK_ALLOC(L, a, u)\tvoid *u = NULL; lua_Alloc a = lua_getallocf(L, &u)\nstatic inline const char *lunatik_pushstring(lua_State *L, char *s, size_t len)\n{\n\tLUNATIK_ALLOC(L, alloc, ud);\n\ts[len] = '\\0';\n\treturn lua_pushexternalstring(L, s, len, alloc, ud);\n}\n\nstatic inline void *lunatik_realloc(lua_State *L, void *ptr, size_t size)\n{\n\tLUNATIK_ALLOC(L, alloc, ud);\n\treturn alloc(ud, ptr, LUA_TNONE, size);\n}\n\n#define lunatik_malloc(L, s)\tlunatik_realloc((L), NULL, (s))\n#define lunatik_free(p)\t\tkfree(p)\n#define lunatik_gfp(runtime)\t((runtime)->gfp)\n\n#define lunatik_enomem(L)\tluaL_error((L), \"not enough memory\")\n\nstatic inline void *lunatik_checknull(lua_State *L, void *ptr)\n{\n\tif (ptr == NULL)\n\t\tlunatik_enomem(L);\n\treturn ptr;\n}\n\n#define lunatik_checkalloc(L, s)\t(lunatik_checknull((L), lunatik_malloc((L), (s))))\n#define lunatik_checkzalloc(L, s)\t(memset(lunatik_checkalloc((L), (s)), 0, (s)))\n\nvoid lunatik_pusherrname(lua_State *L, int err);\n\nstatic inline void lunatik_throw(lua_State *L, int ret)\n{\n\tlunatik_pusherrname(L, ret);\n\tlua_error(L);\n}\n\n#define lunatik_tryret(L, ret, op, ...)\t\t\\\ndo {\t\t\t\t\t\t\\\n\tif ((ret = op(__VA_ARGS__)) < 0)\t\\\n\t\tlunatik_throw(L, ret);\t\t\\\n} while (0)\n\n#define lunatik_try(L, op, ...)\t\t\t\t\\\ndo {\t\t\t\t\t\t\t\\\n\tint ret;\t\t\t\t\t\\\n\tlunatik_tryret(L, ret, op, __VA_ARGS__);\t\\\n} while (0)\n\nstatic inline void lunatik_checkfield(lua_State *L, int idx, const char *field, int type)\n{\n\tint _type = lua_getfield(L, idx, field);\n\tif (_type != type)\n\t\tluaL_error(L, \"bad field '%s' (%s expected, got %s)\", field,\n\t\t\tlua_typename(L, type), lua_typename(L, _type));\n}\n\n#define LUNATIK_ERR_NULLPTR\t\"null pointer dereference\"\n#define LUNATIK_ERR_SINGLE\t\"cannot share SINGLE object\"\n#define LUNATIK_ERR_METATABLE\t\"metatable not found\"\n#define LUNATIK_ERR_CONTEXT\t\"process-context class in interrupt-context runtime\"\n#define LUNATIK_ERR_RUNTIME\t\"runtime context mismatch\"\n\nstatic inline lunatik_object_t *lunatik_checkruntime(lua_State *L, lunatik_opt_t opt)\n{\n\tlunatik_object_t *runtime = lunatik_toruntime(L);\n\tif (lunatik_issoftirq(runtime->opt) != lunatik_issoftirq(opt))\n\t\tluaL_error(L, LUNATIK_ERR_RUNTIME);\n\treturn runtime;\n}\n\n#define lunatik_setruntime(L, libname, priv)\t((priv)->runtime = lunatik_checkruntime((L), lua##libname##_class.opt))\n#define lunatik_monitormt(class, monitor)\t((monitor) ? (void *)&(class)->opt : (void *)(class))\n\nstatic inline void lunatik_checkclass(lua_State *L, const lunatik_class_t *class)\n{\n\tif (lunatik_cannotsleep(L, !lunatik_issoftirq(class->opt)))\n\t\tluaL_error(L, \"'%s': %s\", class->name, LUNATIK_ERR_CONTEXT);\n}\n\nstatic inline void lunatik_setclass(lua_State *L, const lunatik_class_t *class, bool monitor)\n{\n\tlua_pushlightuserdata(L, lunatik_monitormt(class, monitor));\n\tif (lua_rawget(L, LUA_REGISTRYINDEX) == LUA_TNIL)\n\t\tluaL_error(L, \"'%s': %s\", class->name, LUNATIK_ERR_METATABLE);\n\tlua_setmetatable(L, -2);\n\tlua_pushlightuserdata(L, (void *)class);\n\tlua_setiuservalue(L, -2, 1); /* pop class */\n}\n\nstatic inline void lunatik_setobject(lunatik_object_t *object, const lunatik_class_t *class, lunatik_opt_t opt)\n{\n\tlunatik_opt_t inherited = opt | class->opt;\n\tkref_init(&object->kref);\n\tobject->private = NULL;\n\tobject->class = class;\n\tobject->opt = lunatik_issingle(opt) ? inherited & ~LUNATIK_OPT_MONITOR : inherited;\n\tobject->gfp = lunatik_issoftirq(object->opt) ? GFP_ATOMIC : GFP_KERNEL;\n\tlunatik_newlock(object);\n}\n\nlunatik_object_t *lunatik_newobject(lua_State *L, const lunatik_class_t *class, size_t size, lunatik_opt_t opt);\nlunatik_object_t *lunatik_createobject(const lunatik_class_t *class, size_t size, lunatik_opt_t opt);\nvoid lunatik_cloneobject(lua_State *L, lunatik_object_t *object);\nvoid lunatik_releaseobject(struct kref *kref);\nint lunatik_closeobject(lua_State *L);\nint lunatik_deleteobject(lua_State *L);\nvoid lunatik_monitorobject(lua_State *L, const lunatik_class_t *class);\n\n#define lunatik_newpobject(L, n)\t(lunatik_object_t **)lua_newuserdatauv((L), sizeof(lunatik_object_t *), (n))\n#define lunatik_argchecknull(L, o, i)\tluaL_argcheck((L), (o) != NULL, (i), LUNATIK_ERR_NULLPTR)\n#define lunatik_checkobject(L, i)\t(*lunatik_checkpobject((L), (i)))\n#define lunatik_toobject(L, i)\t\t(*(lunatik_object_t **)lua_touserdata((L), (i)))\n#define lunatik_getobject(o)\t\tkref_get(&(o)->kref)\n#define lunatik_putobject(o)\t\tkref_put(&(o)->kref, lunatik_releaseobject)\n\nstatic inline void lunatik_require(lua_State *L, const char *libname)\n{\n\tlua_getglobal(L, \"require\");\n\tlua_pushstring(L, libname);\n\tlua_call(L, 1, 0);\n}\n\nstatic inline void lunatik_pushobject(lua_State *L, lunatik_object_t *object)\n{\n\tlunatik_cloneobject(L, object);\n\tlunatik_getobject(object);\n}\n\nstatic inline bool lunatik_hasindex(lua_State *L, int index)\n{\n\tbool hasindex = lua_getfield(L, index, \"__index\") != LUA_TNIL;\n\tlua_pop(L, 1);\n\treturn hasindex;\n}\n\nstatic inline void lunatik_newclass(lua_State *L, const lunatik_class_t *class, bool monitored)\n{\n\tlua_pushlightuserdata(L, lunatik_monitormt(class, monitored));\n\tlua_newtable(L); /* mt = {} */\n\tluaL_setfuncs(L, class->methods, 0);\n\tif (monitored)\n\t\tlunatik_monitorobject(L, class);\n\tif (!lunatik_hasindex(L, -1)) {\n\t\tlua_pushvalue(L, -1);  /* push mt */\n\t\tlua_setfield(L, -2, \"__index\");  /* mt.__index = mt */\n\t}\n\tlua_rawset(L, LUA_REGISTRYINDEX); /* registry[key] = mt */\n}\n\nstatic inline lunatik_class_t *lunatik_getclass(lua_State *L, int ix)\n{\n\tif (lua_isuserdata(L, ix) && lua_getiuservalue(L, ix, 1) != LUA_TNONE) {\n\t\tlunatik_class_t *class = (lunatik_class_t *)lua_touserdata(L, -1);\n\t\tlua_pop(L, 1); /* class */\n\t\treturn class;\n\t}\n\treturn NULL;\n}\n\nstatic inline bool lunatik_isobject(lua_State *L, int ix, lunatik_object_t *object)\n{\n\tlunatik_class_t *class = lunatik_getclass(L, ix);\n\treturn class && object && object->class == class;\n}\n\nstatic inline lunatik_object_t **lunatik_testobject(lua_State *L, int ix)\n{\n\tlunatik_object_t **pobject = (lunatik_object_t **)lua_touserdata(L, ix);\n\treturn (pobject && lunatik_isobject(L, ix, *pobject)) ? pobject : NULL;\n}\n\nstatic inline lunatik_object_t **lunatik_checkpobject(lua_State *L, int ix)\n{\n\tlunatik_object_t **pobject = lunatik_testobject(L, ix);\n\tluaL_argcheck(L, pobject, ix, \"invalid object\");\n\treturn pobject;\n}\n\nstatic inline void lunatik_newnamespaces(lua_State *L, const lunatik_namespace_t *namespaces)\n{\n\tfor (; namespaces->name; namespaces++) {\n\t\tconst lunatik_reg_t *reg;\n\t\tlua_newtable(L); /* namespace = {} */\n\t\tfor (reg = namespaces->reg; reg->name; reg++) {\n\t\t\tlua_pushinteger(L, reg->value);\n\t\t\tlua_setfield(L, -2, reg->name); /* namespace[name] = value */\n\t\t}\n\t\tlua_setfield(L, -2, namespaces->name); /* lib.namespace = namespace */\n\t}\n}\n\n#define LUNATIK_CLASSES(name, ...)\t\\\nstatic const lunatik_class_t *lua##name##_classes[] = { __VA_ARGS__, NULL }\n\nstatic inline void lunatik_newclasses(lua_State *L, const lunatik_class_t **classes)\n{\n\tfor (; *classes; classes++) {\n\t\tconst lunatik_class_t *cls = *classes;\n\t\tlunatik_checkclass(L, cls);\n\t\tif (lunatik_ismonitor(cls->opt))\n\t\t\tlunatik_newclass(L, cls, true);\n\t\tlunatik_newclass(L, cls, false);\n\t}\n}\n\n#define LUNATIK_NEWLIB(libname, funcs, classes, namespaces)\t\t\t\\\nint luaopen_##libname(lua_State *L);\t\t\t\t\t\t\\\nint luaopen_##libname(lua_State *L)\t\t\t\t\t\t\\\n{\t\t\t\t\t\t\t\t\t\t\\\n\tconst lunatik_class_t **clss = classes; /* avoid -Waddress */\t\t\\\n\tconst lunatik_namespace_t *nss = namespaces; /* avoid -Waddress */\t\\\n\tluaL_newlib(L, funcs);\t\t\t\t\t\t\t\\\n\tif (clss)\t\t\t\t\t\t\t\t\\\n\t\tlunatik_newclasses(L, clss);\t\t\t\t\t\\\n\tif (nss)\t\t\t\t\t\t\t\t\\\n\t\tlunatik_newnamespaces(L, nss);\t\t\t\t\t\\\n\treturn 1;\t\t\t\t\t\t\t\t\\\n}\t\t\t\t\t\t\t\t\t\t\\\nEXPORT_SYMBOL_GPL(luaopen_##libname)\n\n#define LUNATIK_OBJECTCHECKER(checker, T)\t\t\t\\\nstatic inline T checker(lua_State *L, int ix)\t\t\t\\\n{\t\t\t\t\t\t\t\t\\\n\tlunatik_object_t *object = lunatik_checkobject(L, ix);\t\\\n\treturn (T)object->private;\t\t\t\t\\\n}\n\n#define LUNATIK_PRIVATECHECKER(checker, T, ...)\t\t\t\\\nstatic inline T checker(lua_State *L, int ix)\t\t\t\\\n{\t\t\t\t\t\t\t\t\\\n\tT private = (T)lunatik_toobject(L, ix)->private;\t\\\n\t/* avoid use-after-free */\t\t\t\t\\\n\tlunatik_argchecknull(L, private, ix);\t\t\t\\\n\t__VA_ARGS__\t\t\t\t\t\t\\\n\treturn private;\t\t\t\t\t\t\\\n}\n\n#define lunatik_getregistry(L, key)\tlua_rawgetp((L), LUA_REGISTRYINDEX, (key))\n\n#define lunatik_setstring(L, idx, hook, field, maxlen)\t\t\\\ndo {\t\t\t\t\t\t\t\t\\\n\tsize_t len;\t\t\t\t\t\t\\\n\tlunatik_checkfield(L, idx, #field, LUA_TSTRING);\t\\\n\tconst char *str = lua_tolstring(L, -1, &len);\t\t\\\n\tif (len > maxlen)\t\t\t\t\t\\\n\t\tluaL_error(L, \"'%s' is too long\", #field);\t\\\n\tstrncpy((char *)hook->field, str, maxlen);\t\t\\\n\tlua_pop(L, 1);\t\t\t\t\t\t\\\n} while (0)\n\n#define lunatik_setinteger(L, idx, hook, field) \t\t\\\ndo {\t\t\t\t\t\t\t\t\\\n\tlunatik_checkfield(L, idx, #field, LUA_TNUMBER);\t\\\n\thook->field = lua_tointeger(L, -1);\t\t\t\\\n\tlua_pop(L, 1);\t\t\t\t\t\t\\\n} while (0)\n\n#define lunatik_optinteger(L, idx, priv, field, opt)\t\t\t\\\ndo {\t\t\t\t\t\t\t\t\t\\\n\tlua_getfield(L, idx, #field);\t\t\t\t\t\\\n\tpriv->field = lua_isnil(L, -1) ? opt : lua_tointeger(L, -1);\t\\\n\tlua_pop(L, 1);\t\t\t\t\t\t\t\\\n} while (0)\n\nstatic inline void lunatik_optcfunction(lua_State *L, int idx, const char *field, lua_CFunction default_func)\n{\n\tif (lua_getfield(L, idx, field) != LUA_TFUNCTION) {\n\t\tlua_pop(L, 1);\n\t\tlua_pushcfunction(L, default_func);\n\t}\n}\n\n#define lunatik_checkbounds(L, idx, val, min, max)\t\\\n\tluaL_argcheck(L, val >= min && val <= max, idx, \"out of bounds\")\n\nstatic inline lua_Integer lunatik_checkinteger(lua_State *L, int idx, lua_Integer min, lua_Integer max)\n{\n\tlua_Integer v = luaL_checkinteger(L, idx);\n\tlunatik_checkbounds(L, idx, v, min, max);\n\treturn v;\n}\n\nstatic inline void lunatik_register(lua_State *L, int ix, void *key)\n{\n\tlua_pushvalue(L, ix);\n\tlua_rawsetp(L, LUA_REGISTRYINDEX, key); /* pop value */\n}\n\nstatic inline void lunatik_unregister(lua_State *L, void *key)\n{\n\tlua_pushnil(L);\n\tlua_rawsetp(L, LUA_REGISTRYINDEX, key); /* pop nil */\n}\n\nstatic inline void lunatik_registerobject(lua_State *L, int ix, lunatik_object_t *object)\n{\n\tlunatik_register(L, ix, object->private); /* private */\n\tlunatik_register(L, -1, object); /* prevent object from being GC'ed (unless stopped) */\n}\n\nstatic inline void lunatik_unregisterobject(lua_State *L, lunatik_object_t *object)\n{\n\tlunatik_unregister(L, object->private); /* remove private */\n\tlunatik_unregister(L, object); /* remove object, now it might be GC'ed */\n}\n\n#define lunatik_attach(L, obj, field, new_fn, ...)\t\\\ndo {\t\t\t\t\t\t\t\\\n\tobj->field = new_fn((L), ##__VA_ARGS__);\t\\\n\tlunatik_register((L), -1, obj->field);\t\t\\\n\tlua_pop((L), 1);\t\t\t\t\\\n} while (0)\n\n#define lunatik_detach(runtime, obj, field)\t\t\t\\\ndo {\t\t\t\t\t\t\t\t\\\n\tlua_State *L = lunatik_getstate(runtime);\t\t\\\n\tif (L != NULL) /* might be called on lunatik_stop */\t\\\n\t\tlunatik_unregister(L, obj->field);\t\t\\\n\tobj->field = NULL;\t\t\t\t\t\\\n} while (0)\n\n#include \"lunatik_val.h\"\n\n#endif\n\n"
  },
  {
    "path": "lunatik_aux.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#include <linux/slab.h>\n#include <linux/fs.h>\n#include <linux/version.h>\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)\n#include <linux/errname.h>\n#endif\n\n#include <lua.h>\n#include <lauxlib.h>\n\n#include <lunatik.h>\n\ntypedef struct lunatik_file {\n\tstruct file *file;\n\tchar *buffer;\n\tloff_t pos;\n} lunatik_file;\n\nstatic const char *lunatik_loader(lua_State *L, void *ud, size_t *size)\n{\n\tlunatik_file *lf = (lunatik_file *)ud;\n\tssize_t ret = kernel_read(lf->file, lf->buffer, PAGE_SIZE, &(lf->pos));\n\n\tif (unlikely(ret < 0))\n\t\tluaL_error(L, \"kernel_read failure %I\", (lua_Integer)ret);\n\n\t*size = (size_t)ret;\n\treturn lf->buffer;\n}\n\nint lunatik_loadfile(lua_State *L, const char *filename, const char *mode)\n{\n\tlunatik_file lf = {NULL, NULL, 0};\n\tint status = LUA_ERRFILE;\n\tint fnameindex = lua_gettop(L) + 1;  /* index of filename on the stack */\n\n\tif (unlikely(lunatik_cannotsleep(L, lunatik_isready(L)))) {\n\t\tlua_pushfstring(L, \"cannot load file on non-sleepable runtime\");\n\t\tgoto error;\n\t}\n\n\tif (unlikely(filename == NULL) || IS_ERR(lf.file = filp_open(filename, O_RDONLY, 0600))) {\n\t\tlua_pushfstring(L, \"cannot open %s\", filename);\n\t\tgoto error;\n\t}\n\n\tlf.buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);\n\tif (lf.buffer == NULL) {\n\t\tlua_pushfstring(L, \"cannot allocate buffer for %s\", filename);\n\t\tgoto close;\n\t}\n\n\tlua_pushfstring(L, \"@%s\", filename);\n\tstatus = lua_load(L, lunatik_loader, &lf, lua_tostring(L, -1), mode);\n\tlua_remove(L, fnameindex);\n\n\tkfree(lf.buffer);\nclose:\n\tfilp_close(lf.file, NULL);\nerror:\n\treturn status;\n}\nEXPORT_SYMBOL(lunatik_loadfile);\n\nvoid lunatik_pusherrname(lua_State *L, int err)\n{\n    err = abs(err);\n#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)\n    const char *name = errname(err);\n    lua_pushstring(L, name ? name : \"unknown\");\n#else\n    char buf[LUAL_BUFFERSIZE];\n    snprintf(buf, sizeof(buf), \"%pE\", ERR_PTR(-err));\n    lua_pushstring(L, buf);\n#endif\n}\nEXPORT_SYMBOL(lunatik_pusherrname);\n\n#ifdef MODULE /* see https://lwn.net/Articles/813350/ */\n#include <linux/kprobes.h>\n\n#ifdef CONFIG_KPROBES\nstatic unsigned long (*__lunatik_lookup)(const char *) = NULL;\n#endif /* CONFIG_KPROBES */\n\nvoid *lunatik_lookup(const char *symbol)\n{\n#ifdef CONFIG_KPROBES\n\tif (__lunatik_lookup == NULL) {\n\t\tstruct kprobe kp = {.symbol_name = \"kallsyms_lookup_name\"};\n\n\t\tif (register_kprobe(&kp) != 0)\n\t\t\treturn NULL;\n\n\t\t__lunatik_lookup = (unsigned long (*)(const char *))kp.addr;\n\t\tunregister_kprobe(&kp);\n\n\t\tBUG_ON(__lunatik_lookup == NULL);\n\t}\n\treturn (void *)__lunatik_lookup(symbol);\n#else /* CONFIG_KPROBES */\n\treturn NULL;\n#endif /* CONFIG_KPROBES */\n}\nEXPORT_SYMBOL(lunatik_lookup);\n#endif /* MODULE */\n\n#if BITS_PER_LONG == 32\n/* require by lib/lualinux.c */\nEXPORT_SYMBOL(__moddi3);\n#endif /* BITS_PER_LONG == 32 */\n\n"
  },
  {
    "path": "lunatik_conf.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_conf_h\n#define lunatik_conf_h\n\n#define LUAI_UACNUMBER\t\tLUA_INTEGER\n#define LUA_NUMBER\t\tLUA_INTEGER\n#define LUA_NUMBER_FMT\t\tLUA_INTEGER_FMT\n\n#define l_randomizePivot(L)\t(~0)\n\n#include <linux/random.h>\n#define luai_makeseed(L)\t\tget_random_u32()\n\n#undef lua_getlocaledecpoint\n#define lua_getlocaledecpoint()\t\t('.')\n\n#define lua_writestring(s,l)\t\tprintk(\"%s\",(s))\n#define lua_writeline()\t\t\tpr_cont(\"\\n\")\n#define lua_writestringerror(...)\tpr_err(__VA_ARGS__)\n\n/* see https://www.gnu.org/software/libc/manual/html_node/Atomic-Types.html */\n#define l_signalT\tint\n\n/* frame size shouldn't be larger than 1024 bytes; thus, LUAL_BUFFERSIZE\n * must be adjusted for the stack of functions that use luaL_Buffer */\n#undef LUAL_BUFFERSIZE\n#define LUAL_BUFFERSIZE\t\t(256) /* {laux,load,lstr,ltab,lutf8}lib.c */\n\n#ifdef lauxlib_c\n#define panic\tlua_panic\n#endif\n\n#include <linux/module.h>\n#ifdef MODULE /* see https://lwn.net/Articles/813350/ */\nvoid *lunatik_lookup(const char *symbol);\n#define lsys_loadlib(l)\t\t__symbol_get((l))\n#define lsys_unloadlib(l)\tsymbol_put_addr((l))\n#else\n#include <linux/kallsyms.h>\n#define lunatik_lookup(s)\t((void *)kallsyms_lookup_name((l)))\n#define lsys_loadlib(l)\t\tlunatik_lookup(l)\n#define lsys_unloadlib(l)\n#endif\n\n#define lsys_sym(L,l,s)\t\t((lua_CFunction)(l))\n\ntypedef struct lua_State lua_State;\n\nconst char *lua_pushfstring(lua_State *L, const char *fmt, ...);\n\nstatic inline void *lsys_load(lua_State *L, const char *symbol, int seeglb)\n{\n\tvoid *lib;\n\t(void)(seeglb); /* not used */\n\tif ((lib = lsys_loadlib(symbol)) == NULL)\n\t\tlua_pushfstring(L, \"%s not found in kernel symbol table\", symbol);\n\treturn lib;\n}\n\nint lunatik_loadfile(lua_State *L, const char *filename, const char *mode);\n#define luaL_loadfilex(L,f,m)\tlunatik_loadfile((L),(f),(m))\n\n#undef LUA_ROOT\n#define LUA_ROOT\t\"/lib/modules/lua/\"\n\n#undef LUA_PATH_DEFAULT\n#define LUA_PATH_DEFAULT  LUA_ROOT\"?.lua;\" LUA_ROOT\"?/init.lua\"\n\n#undef LUAI_MAXSTACK\n#define LUAI_MAXSTACK  200\n\n#ifdef LUNATIK_RUNTIME\nunsigned int luaS_hash(const char *str, size_t l, unsigned int seed); /* required by luarcu */\n#define\tlunatik_hash(str, l, seed)\tluaS_hash((str), (l), (seed))\n#endif /* LUNATIK_RUNTIME */\n\n#define LUNATIK_GCCOUNT\t/* LUA_GCCOUNT in bytes, instead of Kbytes */\n\n#if defined(lcode_c) || defined(ldebug_c) || defined(llex_c) || defined(lparser_c) || defined(lstate_c)\n#ifdef current /* defined by asm/current.h */\n#undef current /* conflicts with Lua namespace */\n#endif\n#endif\n\n#endif\n\n"
  },
  {
    "path": "lunatik_core.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n/***\n* Manages Lunatik runtimes — isolated Lua states running in the kernel.\n* @module lunatik\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <linux/module.h>\n#include <linux/mm.h>\n\n#include <lua.h>\n#include <lauxlib.h>\n#include <lualib.h>\n\n#include \"lunatik.h\"\n#include \"lunatik_sym.h\"\n\n/***\n* Shared global environment; scripts exchange objects (e.g. RCU tables) through it.\n* @field _ENV\n* @within lunatik\n*/\n\n#ifdef LUNATIK_RUNTIME\nlunatik_object_t *lunatik_env;\nEXPORT_SYMBOL(lunatik_env);\nEXPORT_SYMBOL(luaS_hash);\t/* required by luarcu */\n\nstatic inline void lunatik_setversion(lua_State *L)\n{\n\tlua_pushstring(L, LUNATIK_VERSION);\n\tlua_setglobal(L, \"_LUNATIK_VERSION\");\n}\n\n#define lunatik_cankrealloc(p, n, f)\t\\\n\t(((f) == GFP_ATOMIC || (n) <= PAGE_SIZE) && (!is_vmalloc_addr(p) || (p) == NULL))\n\n/***\n* Isolated Lua state running within the Linux kernel.\n* @type runtime\n*/\nstatic void *lunatik_alloc(void *ud, void *optr, size_t osize, size_t nsize)\n{\n\tif (nsize == 0) {\n\t\tkvfree(optr);\n\t\treturn NULL;\n\t}\n\n\tlunatik_object_t *runtime = (lunatik_object_t *)ud;\n\tgfp_t gfp = lunatik_gfp(runtime);\n\n\tif (lunatik_cankrealloc(optr, nsize, gfp))\n\t\treturn krealloc(optr, nsize, gfp);\n\n\tvoid *nptr = gfp == GFP_KERNEL ? kvmalloc(nsize, gfp) : kmalloc(nsize, gfp);\n\tif (nptr == NULL) /* if shrinking, it's safe to return optr */\n\t\treturn nsize <= osize ? optr : nptr;\n\telse if (optr != NULL) {\n\t\tmemcpy(nptr, optr, min(osize, nsize));\n\t\tkvfree(optr);\n\t}\n\treturn nptr;\n}\n\nstatic inline void lunatik_runerror(lua_State *L, const char *errmsg)\n{\n\tif (L)\n\t\tlua_pushstring(L, errmsg);\n\telse\n\t\tpr_err(\"%s\\n\", errmsg);\n}\n\nstatic void lunatik_releaseruntime(void *private)\n{\n\tlua_State *L = (lua_State *)private;\n\tlua_close(L);\n}\n\nint lunatik_stop(lunatik_object_t *runtime)\n{\n\tvoid *private;\n\n\tlunatik_lock(runtime);\n\tprivate = runtime->private;\n\truntime->private = NULL;\n\tlunatik_unlock(runtime);\n\n\tlunatik_releaseruntime(private);\n\treturn lunatik_putobject(runtime);\n}\nEXPORT_SYMBOL(lunatik_stop);\n\nstatic int lunatik_lruntime(lua_State *L);\n\nLUNATIK_PRIVATECHECKER(lunatik_check, lua_State *);\n\nstatic int lunatik_lcopyobjects(lua_State *L)\n{\n\tlua_State *Lfrom = (lua_State *)lua_touserdata(L, 1);\n\tint ixfrom = lua_tointeger(L, 2);\n\tint nobjects = lua_tointeger(L, 3);\n\tint i;\n\n\tfor (i = 0; i < nobjects; i++) {\n\t\tlunatik_object_t **pobject = lunatik_testobject(Lfrom, ixfrom + i);\n\n\t\tluaL_argcheck(L, pobject, i + 1, \"invalid object\");\n\t\tlunatik_pushobject(L, *pobject);\n\t}\n\treturn nobjects;\n}\n\nstatic inline int lunatik_copyobjects(lua_State *Lto, lua_State *Lfrom, int ixfrom, int nobjects)\n{\n\tlua_pushcfunction(Lto, lunatik_lcopyobjects);\n\tlua_pushlightuserdata(Lto, Lfrom);\n\tlua_pushinteger(Lto, ixfrom);\n\tlua_pushinteger(Lto, nobjects);\n\n\treturn lua_pcall(Lto, 3, nobjects, 0);\n}\n\nstatic inline int lunatik_resume(lua_State *Lto, lua_State *Lfrom, int nargs)\n{\n\tint nresults;\n\tint status = lua_resume(Lto, Lfrom, nargs, &nresults);\n\treturn status == LUA_OK || status == LUA_YIELD ? nresults : -1;\n}\n\n/***\n* Resumes a yielded runtime, analogous to `coroutine.resume`.\n* @function resume\n* @param ... values delivered to the script as return values of `coroutine.yield()`\n* @treturn vararg values passed to the next `coroutine.yield()`, or returned by the script\n* @raise if the runtime errors on resumption\n*/\nstatic int lunatik_lresume(lua_State *L)\n{\n\tlua_State *Lto = lunatik_check(L, 1);\n\tint nargs = lua_gettop(L) - 1;\n\tint nresults = 0;\n\n\tif (lunatik_copyobjects(Lto, L, 2, nargs) != LUA_OK || (nresults = lunatik_resume(Lto, L, nargs) < 0)) {\n\t\tlua_pushfstring(L, \"%s\\n\", lua_tostring(Lto, -1));\n\t\tlua_pop(Lto, 1); /* error message */\n\t\tlua_error(L);\n\t}\n\n\tint status = lunatik_copyobjects(L, Lto, nargs, nresults);\n\tlua_pop(Lto, nresults);\n\tif (status != LUA_OK)\n\t\tlua_error(L);\n\treturn nresults;\n}\n\nstatic const luaL_Reg lunatik_lib[] = {\n\t{\"runtime\", lunatik_lruntime},\n\t{NULL, NULL}\n};\n\nstatic const luaL_Reg lunatik_stub_lib[] = {\n\t{NULL, NULL}\n};\n\n/***\n* Stops the runtime and releases all associated kernel resources.\n* @function stop\n*/\nstatic const luaL_Reg lunatik_mt[] = {\n\t{\"__gc\", lunatik_deleteobject},\n\t{\"__close\", lunatik_closeobject},\n\t{\"stop\", lunatik_closeobject},\n\t{\"resume\", lunatik_lresume},\n\t{NULL, NULL}\n};\n\nstatic const lunatik_class_t lunatik_class = {\n\t.name = \"lunatik\",\n\t.methods = lunatik_mt,\n\t.release = lunatik_releaseruntime,\n\t.opt = LUNATIK_OPT_MONITOR | LUNATIK_OPT_EXTERNAL,\n};\n\n/* used for luaL_requiref() */\nint luaopen_lunatik(lua_State *L);\nint luaopen_lunatik_stub(lua_State *L);\n\nstatic inline void lunatik_setready(lua_State *L)\n{\n\tlua_pushboolean(L, true);\n\tlua_rawsetp(L, LUA_REGISTRYINDEX, L);\n}\n\nstatic int lunatik_runscript(lua_State *L)\n{\n\tconst char *script = lua_pushfstring(L, \"%s%s.lua\", LUA_ROOT, lua_touserdata(L, 1));\n\tint scriptix = lua_gettop(L);\n\n\tlunatik_setversion(L);\n\n\tif (!(lunatik_issoftirq(lunatik_toruntime(L)->opt))) {\n\t\tluaL_openlibs(L);\n\t\tluaL_requiref(L, \"lunatik\", luaopen_lunatik, 0);\n\t}\n\telse {\n\t\tluaL_openselectedlibs(L, ~LUA_IOLIBK, 0);\n\t\tluaL_requiref(L, \"lunatik\", luaopen_lunatik_stub, 0);\n\t}\n\n\tif (lunatik_env != NULL) {\n\t\tlunatik_pushobject(L, lunatik_env);\n\t\tlua_setfield(L, -2, \"_ENV\");\n\t}\n\tlua_pop(L, 1); /* lunatik library */\n\n\tif (lunatik_loadfile(L, script, NULL) != LUA_OK)\n\t\tlua_error(L);\n\n\tlua_call(L, 0, 1);\n\tlua_remove(L, scriptix);\n\tlunatik_setready(L);\n\treturn 1; /* callback */\n}\n\nstatic int lunatik_newruntime(lunatik_object_t **pruntime, lua_State *Lfrom, const char *script, lunatik_opt_t opt)\n{\n\tlunatik_object_t *runtime;\n\tlua_State *L;\n\n\tif ((L = luaL_newstate()) == NULL) {\n\t\tlunatik_runerror(Lfrom, \"failed to allocate Lua state\");\n\t\treturn -ENOMEM;\n\t}\n\n\tif ((runtime = kmalloc(sizeof(lunatik_object_t), GFP_KERNEL)) == NULL) {\n\t\tlunatik_runerror(Lfrom, \"failed to allocate runtime\");\n\t\tlua_close(L);\n\t\treturn -ENOMEM;\n\t}\n\n\tlunatik_setobject(runtime, &lunatik_class, opt);\n\tlunatik_toruntime(L) = runtime;\n\truntime->private = L;\n\n\truntime->gfp = GFP_KERNEL; /* might use kvmalloc while running in process */\n\tlua_setallocf(L, lunatik_alloc, runtime);\n\n\tlua_pushcfunction(L, lunatik_runscript);\n\tlua_pushlightuserdata(L, (void *)script);\n\tif (lua_pcall(L, 1, 1, 0) != LUA_OK) {\n\t\tlunatik_runerror(Lfrom, lua_tostring(L, -1));\n\t\tlunatik_lock(runtime);\n\t\truntime->private = NULL;\n\t\tlunatik_unlock(runtime);\n\t\tlua_close(L); /* hooks hold extra krefs; putobject alone won't reach 0 */\n\t\tlunatik_putobject(runtime);\n\t\treturn -ENOEXEC;\n\t}\n\n\tif (lunatik_issoftirq(opt))\n\t\truntime->gfp = GFP_ATOMIC;\n\n\t*pruntime = runtime;\n\treturn 0;\n}\n\nint lunatik_runtime(lunatik_object_t **pruntime, const char *script, lunatik_opt_t opt)\n{\n\treturn lunatik_newruntime(pruntime, NULL, script, opt);\n}\nEXPORT_SYMBOL(lunatik_runtime);\n\n/***\n* Creates a new Lunatik runtime executing the given script.\n* @function runtime\n* @tparam string script script name (e.g., `\"mymod\"` loads `/lib/modules/lua/mymod.lua`)\n* @tparam[opt=\"process\"] string context execution context: `\"process\"` (sleepable,\n*   GFP\\_KERNEL, mutex) or `\"softirq\"` (atomic, GFP\\_ATOMIC, spinlock).\n*   Use `\"softirq\"` for hooks that fire in interrupt context (netfilter, XDP).\n* @treturn runtime\n* @raise if allocation fails or the script errors on load\n* @within lunatik\n*/\nstatic int lunatik_lruntime(lua_State *L)\n{\n\tstatic const char *const contexts[] = {\"process\", \"softirq\", NULL};\n\tconst char *script = luaL_checkstring(L, 1);\n\tint context = luaL_checkoption(L, 2, \"process\", contexts);\n\tlunatik_opt_t opt = context == 1 ? LUNATIK_OPT_SOFTIRQ : LUNATIK_OPT_NONE;\n\n\tlunatik_object_t **pruntime = lunatik_newpobject(L, 1);\n\tif (lunatik_newruntime(pruntime, L, script, opt) != 0)\n\t\tlua_error(L);\n\tlunatik_setclass(L, &lunatik_class, true);\n\treturn 1;\n}\n\nstatic const lunatik_class_t *lunatik_classes[] = { &lunatik_class, NULL };\n\nLUNATIK_NEWLIB(lunatik, lunatik_lib, lunatik_classes, NULL);\nLUNATIK_NEWLIB(lunatik_stub, lunatik_stub_lib, NULL, NULL);\n#endif /* LUNATIK_RUNTIME */\n\nstatic int __init lunatik_init(void)\n{\n\treturn 0;\n}\n\nstatic void __exit lunatik_exit(void)\n{\n}\n\nmodule_init(lunatik_init);\nmodule_exit(lunatik_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ringzero.com.br>\");\n\n"
  },
  {
    "path": "lunatik_obj.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#define pr_fmt(fmt) KBUILD_MODNAME \": \" fmt\n#include <lua.h>\n#include <lauxlib.h>\n\n#include \"lunatik.h\"\n\n#ifdef LUNATIK_RUNTIME\n\n#define lunatik_ismetamethod(reg)          \\\n\t((!strncmp(reg->name, \"__\", 2)) ||     \\\n\t(reg)->func == lunatik_deleteobject || \\\n\t(reg)->func == lunatik_closeobject)\n\nlunatik_object_t *lunatik_newobject(lua_State *L, const lunatik_class_t *class, size_t size, lunatik_opt_t opt)\n{\n\tlunatik_object_t **pobject = lunatik_newpobject(L, 1);\n\tlunatik_object_t *object = lunatik_checkalloc(L, sizeof(lunatik_object_t));\n\n\t/* SOFTIRQ runtime requires a SOFTIRQ class */\n\tlunatik_checkclass(L, class);\n\n\tlunatik_setobject(object, class, opt);\n\tlunatik_setclass(L, class, lunatik_ismonitor(object->opt));\n\n\tobject->private = lunatik_isexternal(class->opt) ? NULL : lunatik_checkzalloc(L, size);\n\n\t*pobject = object;\n\treturn object;\n}\nEXPORT_SYMBOL(lunatik_newobject);\n\nlunatik_object_t *lunatik_createobject(const lunatik_class_t *class, size_t size, lunatik_opt_t opt)\n{\n\tgfp_t gfp = lunatik_issoftirq(opt | class->opt) ? GFP_ATOMIC : GFP_KERNEL;\n\tlunatik_object_t *object = (lunatik_object_t *)kzalloc(sizeof(lunatik_object_t), gfp);\n\n\tif (object == NULL)\n\t\treturn NULL;\n\n\tlunatik_setobject(object, class, opt);\n\tif ((object->private = kzalloc(size, gfp)) == NULL) {\n\t\tlunatik_putobject(object);\n\t\treturn NULL;\n\t}\n\treturn object;\n}\nEXPORT_SYMBOL(lunatik_createobject);\n\n\nvoid lunatik_cloneobject(lua_State *L, lunatik_object_t *object)\n{\n\tconst lunatik_class_t *class = object->class;\n\n\tif (lunatik_issingle(object->opt))\n\t\tluaL_error(L, \"'%s': %s\", class->name, LUNATIK_ERR_SINGLE);\n\n\tlunatik_require(L, class->name);\n\tlunatik_object_t **pobject = lunatik_newpobject(L, 1);\n\n\tlunatik_checkclass(L, class);\n\tlunatik_setclass(L, class, lunatik_ismonitor(object->opt));\n\t*pobject = object;\n}\nEXPORT_SYMBOL(lunatik_cloneobject);\n\nstatic inline void lunatik_releaseprivate(const lunatik_class_t *class, void *private)\n{\n\tvoid (*release)(void *) = class->release;\n\n\tif (release)\n\t\trelease(private);\n\tif (!lunatik_isexternal(class->opt))\n\t\tlunatik_free(private);\n}\n\nint lunatik_closeobject(lua_State *L)\n{\n\tlunatik_object_t *object = lunatik_checkobject(L, 1);\n\tvoid *private;\n\n\tlunatik_lock(object);\n\tprivate = object->private;\n\tobject->private = NULL;\n\tlunatik_unlock(object);\n\n\tif (private != NULL)\n\t\tlunatik_releaseprivate(object->class, private);\n\treturn 0;\n}\nEXPORT_SYMBOL(lunatik_closeobject);\n\nvoid lunatik_releaseobject(struct kref *kref)\n{\n\tlunatik_object_t *object = container_of(kref, lunatik_object_t, kref);\n\tvoid *private = object->private;\n\n\tif (private != NULL)\n\t\tlunatik_releaseprivate(object->class, private);\n\n\tlunatik_freelock(object);\n\tkfree(object);\n}\nEXPORT_SYMBOL(lunatik_releaseobject);\n\nint lunatik_deleteobject(lua_State *L)\n{\n\tlunatik_object_t **pobject = lunatik_checkpobject(L, 1);\n\tlunatik_object_t *object = *pobject;\n\n\tBUG_ON(!object);\n\tlunatik_putobject(object);\n\t*pobject = NULL;\n\treturn 0;\n}\nEXPORT_SYMBOL(lunatik_deleteobject);\n\ninline static void lunatik_fixerror(lua_State *L, const char *method)\n{\n\tif (method) {\n\t\tconst char *error = lua_tostring(L, -1);\n\t\tluaL_gsub(L, error, \"?\", method);\n\t\tlua_remove(L, -2); /* error */\n\t}\n\tlua_remove(L, -2); /* fixed error */\n\tlua_error(L);\n}\n\nstatic int lunatik_monitor(lua_State *L)\n{\n\tint ret, n = lua_gettop(L);\n\tlunatik_object_t *object = lunatik_checkobject(L, 1);\n\n\tlua_pushvalue(L, lua_upvalueindex(1)); /* method */\n\tlua_insert(L, 1); /* stack: method, object, args */\n\n\tlua_gc(L, LUA_GCSTOP);\n\tlunatik_lock(object);\n\tret = lua_pcall(L, n, LUA_MULTRET, 0);\n\tlunatik_unlock(object);\n\tlua_gc(L, LUA_GCRESTART);\n\n\tif (ret != LUA_OK) {\n\t\tconst char *method = lua_tostring(L, lua_upvalueindex(2));\n\t\tlunatik_fixerror(L, method);\n\t}\n\treturn lua_gettop(L);\n}\n\nvoid lunatik_monitorobject(lua_State *L, const lunatik_class_t *class)\n{\n\tconst luaL_Reg *reg;\n\tfor (reg = class->methods; reg->name != NULL; reg++) {\n\t\tif (!lunatik_ismetamethod(reg)) {\n\t\t\tlua_getfield(L, -1, reg->name);\n\t\t\tlua_pushstring(L, reg->name);\n\t\t\tlua_pushcclosure(L, lunatik_monitor, 2); /* stack: mt, method, method name*/\n\t\t\tlua_setfield(L, -2, reg->name);\n\t\t}\n\t}\n}\nEXPORT_SYMBOL(lunatik_monitorobject);\n\n#endif /* LUNATIK_RUNTIME */\n\n"
  },
  {
    "path": "lunatik_run.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2023-2024 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#include <linux/module.h>\n\n#include <lua.h>\n#include <lauxlib.h>\n#include <lualib.h>\n\n#include \"lunatik.h\"\n#include \"lib/luarcu.h\"\n\nstatic lunatik_object_t *runtime;\n\nstatic int __init lunatik_run_init(void)\n{\n\tint ret = 0;\n\n\tif ((lunatik_env = luarcu_newtable(LUARCU_DEFAULT_SIZE, LUNATIK_OPT_NONE)) == NULL)\n\t\treturn -ENOMEM;\n\n\tif ((ret = lunatik_runtime(&runtime, \"driver\", LUNATIK_OPT_NONE)) < 0) {\n\t\tpr_err(\"couldn't create driver runtime\\n\");\n\t\tlunatik_putobject(lunatik_env);\n\t}\n\n\treturn ret;\n}\n\nstatic void __exit lunatik_run_exit(void)\n{\n\tlunatik_putobject(lunatik_env);\n\tlunatik_stop(runtime);\n}\n\nmodule_init(lunatik_run_init);\nmodule_exit(lunatik_run_exit);\nMODULE_LICENSE(\"Dual MIT/GPL\");\nMODULE_AUTHOR(\"Lourival Vieira Neto <lourival.neto@ring-0.io>\");\n\n"
  },
  {
    "path": "lunatik_val.c",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#include \"lunatik.h\"\n\nvoid lunatik_checkvalue(lua_State *L, int ix, lunatik_value_t *value)\n{\n\tvalue->type = lua_type(L, ix);\n\tswitch (value->type) {\n\tcase LUA_TNIL:\n\t\tbreak;\n\tcase LUA_TBOOLEAN:\n\t\tvalue->boolean = lua_toboolean(L, ix);\n\t\tbreak;\n\tcase LUA_TNUMBER:\n\t\tvalue->integer = lua_tointeger(L, ix);\n\t\tbreak;\n\tcase LUA_TUSERDATA:\n\t\tvalue->object = lunatik_checkobject(L, ix);\n\t\tif (lunatik_issingle(value->object->opt))\n\t\t\tluaL_argerror(L, ix, LUNATIK_ERR_SINGLE);\n\t\tbreak;\n\tdefault:\n\t\tluaL_argerror(L, ix, \"unsupported type\");\n\t\tbreak;\n\t}\n}\nEXPORT_SYMBOL(lunatik_checkvalue);\n\nstatic int lunatik_doclone(lua_State *L)\n{\n\tlunatik_object_t *object = (lunatik_object_t *)lua_touserdata(L, 1);\n\tlunatik_cloneobject(L, object);\n\treturn 1;\n}\n\nvoid lunatik_pushvalue(lua_State *L, lunatik_value_t *value)\n{\n\tswitch (value->type) {\n\tcase LUA_TNIL:\n\t\tlua_pushnil(L);\n\t\tbreak;\n\tcase LUA_TBOOLEAN:\n\t\tlua_pushboolean(L, value->boolean);\n\t\tbreak;\n\tcase LUA_TNUMBER:\n\t\tlua_pushinteger(L, value->integer);\n\t\tbreak;\n\tcase LUA_TUSERDATA:\n\t\tlua_pushcfunction(L, lunatik_doclone);\n\t\tlua_pushlightuserdata(L, value->object);\n\t\tif (lua_pcall(L, 1, 1, 0) != LUA_OK) {\n\t\t\tlunatik_putobject(value->object);\n\t\t\tlua_error(L);\n\t\t}\n\t\tbreak;\n\t}\n}\nEXPORT_SYMBOL(lunatik_pushvalue);\n\n"
  },
  {
    "path": "lunatik_val.h",
    "content": "/*\n* SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n* SPDX-License-Identifier: MIT OR GPL-2.0-only\n*/\n\n#ifndef lunatik_val_h\n#define lunatik_val_h\n\ntypedef struct lunatik_value_s {\n\tint type;\n\tunion {\n\t\tint boolean;\n\t\tlua_Integer integer;\n\t\tlunatik_object_t *object;\n\t};\n} lunatik_value_t;\n\n#define lunatik_isuserdata(v)\t((v)->type == LUA_TUSERDATA)\n\nvoid lunatik_checkvalue(lua_State *L, int ix, lunatik_value_t *value);\nvoid lunatik_pushvalue(lua_State *L, lunatik_value_t *value);\n\n#endif\n\n"
  },
  {
    "path": "tests/README.md",
    "content": "# Lunatik Tests\n\nIntegration tests for lunatik kernel modules. Output follows\n[KTAP](https://docs.kernel.org/dev-tools/ktap.html) format.\n\n## Requirements\n\n- Lunatik modules loaded: `sudo lunatik load`\n- Lua scripts installed: `sudo make tests_install`\n- Root privileges\n\n## Running\n\nAll suites:\n\n```\nsudo bash tests/run.sh\n```\n\nIndividual suite:\n\n```\nsudo bash tests/monitor/run.sh\nsudo bash tests/thread/run.sh\nsudo bash tests/runtime/run.sh\n```\n\nIndividual test:\n\n```\nsudo bash tests/monitor/gc.sh\nsudo bash tests/thread/shouldstop.sh\nsudo bash tests/thread/run_during_load.sh\nsudo bash tests/runtime/refcnt_leak.sh\nsudo bash tests/runtime/resume_shared.sh\nsudo bash tests/runtime/resume_mailbox.sh\n```\n\n## Suites\n\n### monitor\n\nRegression tests for `lunatik_monitor` (spinlock + GC interaction).\n\n- **gc**: GC running under spinlock triggers \"scheduling while atomic\".\n  A spawned thread uses a `sleep=false` fifo from a `sleep=true` runtime;\n  `f:pop()` allocates inside `spin_lock_bh`, forcing GC that finalizes a\n  dropped AF_PACKET socket.\n\n### thread\n\nRegression tests for `luathread`.\n\n- **shouldstop**: `thread.shouldstop()` must return `false` in a `run`\n  (non-kthread) context without crashing, and `true` when stop is requested\n  in a `spawn` (kthread) context.\n\n- **run_during_load**: `runner.spawn()` called from a script's top-level\n  code (during module load) must error instead of hanging the kernel.\n\n### runtime\n\nRegression tests for `lunatik_newruntime`.\n\n- **refcnt_leak**: module use-count leak when a script errors after a\n  successful `netfilter.register()` call.\n\n  `require(\"netfilter\")` creates an LSTRMEM string in the Lua state via\n  `__symbol_get(\"luaopen_netfilter\")`.  Each `netfilter.register()` call\n  increments the runtime kref (via `lunatik_getobject`), so after one\n  successful register the kref is 2.  If the script then errors,\n  `lunatik_newruntime`'s error path calls `lunatik_putobject(runtime)`\n  (kref 2→1), but never reaches 0 — `lua_close()` is never called.\n  The LSTRMEM string is never freed, `symbol_put_addr()` is never invoked,\n  and luanetfilter's use-count stays elevated.  `lunatik reload` then fails\n  with \"Module luanetfilter is in use\".\n\n  Fix: in the error path, set `runtime->private = NULL` under the spinlock\n  (so any in-flight hook sees NULL and bails), call `lua_close(L)` explicitly\n  (GC runs, hook finalizer fires: `nf_unregister_net_hook` +\n  `lunatik_putobject` kref 2→1, LSTRMEM freed → `symbol_put_addr` restores\n  refcnt), then `lunatik_putobject(runtime)` kref 1→0 → `kfree`.\n\n- **resume_shared**: `runtime:resume()` must correctly pass shared (monitored)\n  objects across runtime boundaries. Pushes a value into a shared `fifo`,\n  passes it to a sub-runtime via `resume()`, and asserts the value can be\n  popped.\n\n- **resume_mailbox**: `completion` objects must be passable via\n  `runtime:resume()` to enable the mailbox pattern. Passes a `fifo` and\n  `completion` to a sub-runtime that sends a message; the main runtime\n  receives and asserts the value.\n\n"
  },
  {
    "path": "tests/crypto/aead.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025-2026 jperon <cataclop@hotmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\nlocal aead = require(\"crypto\").aead\nlocal util = require(\"util\")\nlocal test = util.test\nlocal hex2bin = util.hex2bin\nlocal bin2hex = util.bin2hex\n\ntest(\"AEAD AES-128-GCM encrypt\", function()\n\tlocal c = aead(\"gcm(aes)\")\n\tc:setkey\"0123456789abcdef\"\n\tc:setauthsize(16)\n\n\tlocal expected = hex2bin\"95be1ddc3dd13cdd2d8ffcc391561ade661d5b696ede5a918e\"\n\t-- The encrypt method returns ciphertext_with_tag, tag_length. We only need the first one for this assertion.\n\tlocal result = c:encrypt(\"abcdefghijkl\", \"plaintext\", \"0123456789abcdef\")\n\tassert(result == expected, \"Expected: \" .. bin2hex(expected) .. \", got: \" .. bin2hex(result))\nend)\n\ntest(\"AEAD AES-128-GCM decrypt\", function()\n\tlocal c = aead(\"gcm(aes)\")\n\tc:setkey\"0123456789abcdef\"\n\tc:setauthsize(16)\n\n\tlocal ciphertext = hex2bin\"95be1ddc3dd13cdd2d8ffcc391561ade661d5b696ede5a918e\"\n\tlocal expected = \"plaintext\"\n\tlocal result = c:decrypt(\"abcdefghijkl\", ciphertext, \"0123456789abcdef\")\n\t-- We use bin2hex for the error message, as the result is a binary string which may not be printable.\n\tassert(result == expected, \"Expected: \" .. bin2hex(expected) .. \", got: \" .. bin2hex(result))\nend)\n\ntest(\"AEAD AES-128-GCM ivsize and authsize\", function()\n\tlocal c = aead(\"gcm(aes)\")\n\tassert(c:ivsize() == 12, \"IV size for AES-128-GCM should be 12 bytes\")\n\tc:setauthsize(16)\n\tassert(c:authsize() == 16, \"Auth size for AES-128-GCM should be 16 bytes\")\nend)\n\ntest(\"AEAD AES-128-GCM setkey with invalid key length\", function()\n\tlocal c = aead(\"gcm(aes)\")\n\tlocal status, err = pcall(c.setkey, c, \"0123456789abcde\") -- 15 bytes, invalid for AES-128\n\tassert(not status, \"setkey with invalid key length should fail\")\n\tassert(err == \"EINVAL\", \"Error code should be 'EINVAL', got: \" .. err)\nend)\n\ntest(\"AEAD AES-128-GCM setauthsize with invalid tag length\", function()\n\tlocal c = aead(\"gcm(aes)\")\n\tlocal status, err = pcall(c.setauthsize, c, 11) -- 11 bytes, invalid for GCM (must be 4, 8, 12, 13, 14, 15, 16)\n\tassert(not status, \"setauthsize with invalid tag length should fail\")\n\tassert(err == \"EINVAL\", \"Error code should be 'EINVAL', got: \" .. err)\nend)\n\ntest(\"AEAD AES-128-GCM encrypt with incorrect IV length\", function()\n\tlocal c = aead(\"gcm(aes)\")\n\tc:setkey\"0123456789abcdef\"\n\tc:setauthsize(16)\n\tlocal status, err = pcall(c.encrypt, c, \"abcdefghijk\", \"plaintext\") -- 11 bytes, incorrect IV length\n\tassert(not status, \"encrypt with incorrect IV length should fail\")\n\tassert(err == \"EINVAL\", \"Error code should be 'EINVAL', got: \" .. err)\nend)\n\ntest(\"AEAD AES-128-GCM decrypt with incorrect IV length\", function()\n\tlocal c = aead(\"gcm(aes)\")\n\tc:setkey\"0123456789abcdef\"\n\tc:setauthsize(16)\n\tlocal ciphertext = hex2bin\"95be1ddc3dd13cdd2d8ffcc391561ade661d5b696ede5a918e\"\n\tlocal status, err = pcall(c.decrypt, c, \"abcdefghijk\", ciphertext) -- 11 bytes, incorrect IV length\n\tassert(not status, \"decrypt with incorrect IV length should fail\")\n\tassert(err == \"EINVAL\", \"Error code should be 'EINVAL', got: \" .. err)\nend)\n\ntest(\"AEAD AES-128-GCM decrypt with input data too short for tag\", function()\n\tlocal c = aead(\"gcm(aes)\")\n\tc:setkey\"0123456789abcdef\"\n\tc:setauthsize(16)\n\tlocal short_ciphertext = hex2bin\"95be1ddc3dd13cdd2d8ffcc391561ade661d5b696ede5a918\" -- Missing last byte of tag\n\tlocal status, err = pcall(c.decrypt, c, \"abcdefghijkl\", short_ciphertext, \"0123456789abcdef\")\n\tassert(not status, \"decrypt with input data too short for tag should fail\")\n\tassert(err == \"EBADMSG\", \"Error code should be 'EBADMSG', got: \" .. err)\nend)\n\ntest(\"AEAD AES-128-GCM decrypt with authentication failure\", function()\n\tlocal c = aead(\"gcm(aes)\")\n\tc:setkey\"0123456789abcdef\"\n\tc:setauthsize(16)\n\tlocal tampered_ciphertext = hex2bin\"95be1ddc3dd13cdd2d8ffcc391561ade661d5b696ede5a918f\" -- Last byte of tag tampered\n\tlocal status, err = pcall(c.decrypt, c, \"abcdefghijkl\", tampered_ciphertext)\n\tassert(not status, \"decrypt with tampered data should fail\")\n\tassert(err == \"EBADMSG\", \"Error code should be 'EBADMSG', got: \" .. err)\nend)\n\n"
  },
  {
    "path": "tests/crypto/comp.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025 jperon <cataclop@hotmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal comp = require(\"crypto\").comp\nlocal test = require(\"util\").test\n\ntest(\"COMP compress empty string (error)\", function()\n\tlocal c = comp\"lz4\"\n\tlocal status, err = pcall(c.compress, c, \"\", 0)\n\tassert(not status, \"Compressing empty string should return an error\")\n\tassert(err:find\"out of bounds\", \"Error should indicate out of bounds, got: \" .. tostring(err))\nend)\n\ntest(\"COMP decompress empty string (error)\", function()\n\tlocal c = comp\"lz4\"\n\tlocal status, err = pcall(c.decompress, c, \"\", 0)\n\tassert(not status, \"Decompressing empty string should return an error\")\n\tassert(err:find\"out of bounds\", \"Error should indicate out of bounds, got: \" .. tostring(err))\nend)\n\ntest(\"COMP compress\", function()\n\tlocal c = comp\"lz4\"\n\tlocal original_data = string.rep(\"abcdefghijklmnopqrstuvwxyz\", 100) .. string.rep(\"A\", 500) .. string.rep(\"B\", 500)\n\tlocal compressed = c:compress(original_data, #original_data * 2) -- Allow for some overhead\n\tassert(type(compressed) == \"string\", \"Compressed output should be a string\")\n\tassert(#compressed < #original_data, \"Compressed data should be smaller than original\")\nend)\n\ntest(\"COMP decompress\", function()\n\tlocal c = comp\"lz4\"\n\tlocal original_data = string.rep(\"abcdefghijklmnopqrstuvwxyz\", 100) .. string.rep(\"A\", 500) .. string.rep(\"B\", 500)\n\tlocal compressed = c:compress(original_data, #original_data * 2) -- Allow for some overhead\n\tlocal decompressed = c:decompress(compressed, #original_data)\n\tassert(type(decompressed) == \"string\", \"Decompressed output should be a string\")\n\tassert(decompressed == original_data, \"Decompressed data content mismatch\")\nend)\n\ntest(\"COMP decompress with larger buffer\", function()\n\tlocal c = comp\"lz4\"\n\tlocal original_data = string.rep(\"abcdefghijklmnopqrstuvwxyz\", 100) .. string.rep(\"A\", 500) .. string.rep(\"B\", 500)\n\tlocal compressed = c:compress(original_data, #original_data * 2) -- Allow for some overhead\n\tlocal decompressed = c:decompress(compressed, #original_data + 10)\n\tassert(decompressed == original_data, \"Decompression with larger buffer failed\")\nend)\n\ntest(\"COMP decompress with too small buffer (expect error)\", function()\n\tlocal c = comp\"lz4\"\n\tlocal original_data = string.rep(\"abcdefghijklmnopqrstuvwxyz\", 100) .. string.rep(\"A\", 500) .. string.rep(\"B\", 500)\n\tlocal compressed = c:compress(original_data, #original_data * 2) -- Allow for some overhead\n\tlocal status, err = pcall(c.decompress, c, compressed, #original_data - 1)\n\tassert(not status, \"Decompression with too small buffer should fail\")\n\tassert(err == \"EINVAL\", \"Error code should be 'EINVAL', got: \" .. err)\nend)\n\n"
  },
  {
    "path": "tests/crypto/hkdf.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025 jperon <cataclop@hotmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal new = require(\"crypto.hkdf\").new\nlocal util = require(\"util\")\nlocal test = util.test\nlocal hex2bin = util.hex2bin\n\ntest(\"HKDF test vectors from RFC 5869, Appendix A\", function()\n\tlocal h = new\"sha256\"\n\n\tlocal expected = hex2bin\"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865\"\n\tlocal result = h:hkdf(\n\t\thex2bin\"000102030405060708090a0b0c\",\n\t\thex2bin\"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b\",\n\t\thex2bin\"f0f1f2f3f4f5f6f7f8f9\",\n\t\t42\n\t)\n\tassert(result == expected, \"RFC5869 1 mismatch\")\n\n\texpected = hex2bin\"b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87\"\n\tresult = h:hkdf(\n\t\thex2bin\"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf\",\n\t\thex2bin\"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f\",\n\t\thex2bin\"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff\",\n\t\t82\n\t)\n\tassert(result == expected, \"RFC5869 2 mismatch\")\n\n\texpected = hex2bin\"8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8\"\n\tresult = h:hkdf(\n\t\t\"\",\n\t\thex2bin\"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b\",\n\t\t\"\",\n\t\t42\n\t)\n\tassert(result == expected, \"RFC5869 3 mismatch\")\nend)\n\ntest(\"HKDF-Expand-Label examples from https://quic.xargs.org/#client-initial-keys-calc\", function()\n\tlocal h = new\"sha256\"\n\n\tlocal function tls13_expand_label(prk, label, context, length)\n\t\tlocal hkdf_label_info = string.pack(\">Hs1s1\", length, \"tls13 \" .. label, context)\n\t\treturn h:expand(prk, hkdf_label_info, length)\n\tend\n\n\tlocal expected = hex2bin\"f016bb2dc9976dea2726c4e61e738a1e3680a2487591dc76b2aee2ed759822f6\"\n\tlocal result = h:extract(\n\t\thex2bin\"38762cf7f55934b34d179ae6a4c80cadccbb7f0a\",\n\t\thex2bin\"0001020304050607\"\n\t)\n\tassert(result == expected, \"HKDF-Expand-Label init_secret mismatch\")\n\n\texpected = hex2bin\"47c6a638d4968595cc20b7c8bc5fbfbfd02d7c17cc67fa548c043ecb547b0eaa\" -- This is the PRK from the previous step\n\tresult = tls13_expand_label(result, \"client in\", \"\", 32)\n\tassert(result == expected, \"HKDF-Expand-Label csecret mismatch\")\n\tlocal csecret = result\n\n\texpected = hex2bin\"b14b918124fda5c8d79847602fa3520b\"\n\tresult = tls13_expand_label(csecret, \"quic key\", \"\", 16)\n\tassert(result == expected, \"HKDF-Expand-Label client_init_key mismatch\")\n\n\texpected = hex2bin\"ddbc15dea80925a55686a7df\"\n\tresult = tls13_expand_label(csecret, \"quic iv\", \"\", 12)\n\tassert(result == expected, \"HKDF-Expand-Label client_init_iv mismatch\")\nend)\n\n"
  },
  {
    "path": "tests/crypto/rng.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025 jperon <cataclop@hotmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\nlocal rng = require(\"crypto\").rng\nlocal test = require\"util\".test\n\ntest(\"RNG generate 32 bytes\", function()\n\tlocal r = rng\"stdrng\"\n\tassert(r, \"Failed to create RNG TFM object\")\n\tlocal random = r:generate(32)\n\tassert(type(random) == \"string\")\n\tassert(#random == 32)\nend)\n\ntest(\"RNG generate 0 bytes (error)\", function()\n\tlocal r = rng\"stdrng\"\n\tlocal status, err = pcall(r.generate, r, 0)\n\tassert(not status, \"r:generate(0) must return an error\")\n\tassert(err:find\"out of bounds\", \"Error for 0 bytes should indicate 'out of bounds', got: \" .. tostring(err))\nend)\n\ntest(\"RNG reset without seed\", function()\n\tlocal r = rng\"stdrng\"\n\tlocal status, err = pcall(r.reset, r)\n\tassert(status, \"rng:reset() should not error: \" .. tostring(err))\n\tlocal random = r:generate(16)\n\tassert(type(random) == \"string\")\n\tassert(#random == 16)\nend)\n\ntest(\"RNG reset with seed\", function()\n\tlocal r = rng\"stdrng\"\n\tlocal status, err = pcall(r.reset, r, \"new_seed_material\")\n\tassert(status, \"rng:reset('new_seed_material') should not error: \" .. tostring(err))\n\tlocal random = r:generate(16)\n\tassert(#random == 16)\nend)\n\ntest(\"RNG info\", function()\n\tlocal r = rng\"stdrng\"\n\tlocal info = r:info()\n\tassert(type(info) == \"table\", \"info should return a table\")\n\tassert(type(info.driver_name) == \"string\", \"info.driver_name should be a string\")\n\tassert(type(info.seedsize) == \"number\", \"info.seedsize should be a number\")\nend)\n\ntest(\"RNG getbytes 0 bytes (error)\", function()\n\tlocal r = rng\"stdrng\"\n\tlocal status, err = pcall(r.getbytes, r, 0)\n\tassert(not status, \"rng:getbytes(0) must return an error\")\n\tassert(err:find\"out of bounds\", \"Error for 0 bytes should indicate 'out of bounds', got: \" .. tostring(err))\nend)\n\ntest(\"RNG getbytes (16 bytes)\", function()\n\tlocal r = rng\"stdrng\"\n\tlocal bytes16 = r:getbytes(16)\n\tassert(type(bytes16) == \"string\", \"getbytes(16) should return a string\")\n\tassert(#bytes16 == 16, \"getbytes(16) should return 16 bytes\")\nend)\n\ntest(\"RNG getbytes (32 bytes)\", function()\n\tlocal r = rng\"stdrng\"\n\tlocal bytes32 = r:getbytes(32)\n\tassert(type(bytes32) == \"string\", \"getbytes(32) should return a string\")\n\tassert(#bytes32 == 32, \"getbytes(32) should return 32 bytes\")\nend)\n\ntest(\"RNG getbytes (different from previous)\", function()\n\tlocal r = rng\"stdrng\"\n\tlocal bytes16 = r:getbytes(16)\n\tlocal bytes32 = r:getbytes(32)\n\tassert(bytes16 ~= bytes32, \"Consecutive getbytes calls should produce different results (highly probable)\")\nend)\n\n"
  },
  {
    "path": "tests/crypto/run.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Runs all crypto tests and reports aggregated KTAP results.\n#\n# Usage: sudo bash tests/crypto/run.sh\n\nDIR=\"$(dirname \"$(readlink -f \"$0\")\")\"\n\nsource \"$DIR/../lib.sh\"\n\nTESTS=\"shash skcipher aead rng hkdf comp\"\nTOTAL=$(echo $TESTS | wc -w)\n\nktap_header\nktap_plan $TOTAL\n\nfor t in $TESTS; do\n\tmark_dmesg\n\tif ! lunatik run \"tests/crypto/$t\" 2>/dev/null; then\n\t\tktap_fail \"crypto/$t: script execution failed\"\n\t\tcontinue\n\tfi\n\terrs=$(dmesg | tail -n +$((DMESG_LINE+1)) | grep -iE \"^[^:]+: FAIL\t|\\.lua:[0-9]+:\" || true)\n\tif [ -z \"$errs\" ]; then\n\t\tktap_pass \"crypto/$t\"\n\telse\n\t\tktap_fail \"crypto/$t\"\n\t\twhile IFS= read -r line; do\n\t\t\techo \"# $line\"\n\t\tdone <<< \"$errs\"\n\tfi\ndone\n\nktap_totals\n[ $KTAP_FAIL -eq 0 ]\n\n"
  },
  {
    "path": "tests/crypto/shash.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025 jperon <cataclop@hotmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal shash = require(\"crypto\").shash\nlocal util = require(\"util\")\nlocal test = util.test\nlocal hex2bin = util.hex2bin\n\ntest(\"crypto_shash.new and digestsize\", function()\n\tlocal hasher = shash(\"sha256\")\n\tassert(hasher, \"Failed to create sha256 hasher\")\n\tassert(hasher:digestsize() == 32, \"SHA256 digest size should be 32 bytes\")\n\n\tlocal hmac_hasher = shash(\"hmac(sha256)\")\n\tassert(hmac_hasher, \"Failed to create hmac(sha256) hasher\")\n\tassert(hmac_hasher:digestsize() == 32, \"HMAC(SHA256) digest size should be 32 bytes\")\nend)\n\ntest(\"crypto_shash:digest (single-shot)\", function()\n\tlocal hasher = shash(\"sha256\")\n\tlocal data = \"The quick brown fox jumps over the lazy dog\"\n\tlocal expected_digest = hex2bin(\"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592\")\n\tlocal digest = hasher:digest(data)\n\tassert(digest == expected_digest, \"SHA256 digest mismatch\")\n\n\tlocal hmac_hasher = shash(\"hmac(sha256)\")\n\tlocal key = \"key\"\n\tlocal hmac_data = \"The quick brown fox jumps over the lazy dog\"\n\tlocal expected_hmac_digest = hex2bin(\"f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8\")\n\thmac_hasher:setkey(key)\n\tlocal hmac_digest = hmac_hasher:digest(hmac_data)\n\tassert(hmac_digest == expected_hmac_digest, \"HMAC(SHA256) digest mismatch\")\nend)\n\ntest(\"crypto_shash:init, update, final (multi-part)\", function()\n\tlocal hasher = shash(\"sha256\")\n\tlocal data1 = \"The quick brown \"\n\tlocal data2 = \"fox jumps over \"\n\tlocal data3 = \"the lazy dog\"\n\tlocal expected_digest = hex2bin(\"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592\")\n\n\thasher:init()\n\thasher:update(data1)\n\thasher:update(data2)\n\thasher:update(data3)\n\tlocal digest = hasher:final()\n\tassert(digest == expected_digest, \"Multi-part SHA256 digest mismatch\")\nend)\n\ntest(\"crypto_shash:finup\", function()\n\tlocal hasher = shash(\"sha256\")\n\tlocal data1 = \"The quick brown \"\n\tlocal data2 = \"fox jumps over the lazy dog\"\n\tlocal expected_digest = hex2bin(\"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592\")\n\n\thasher:init()\n\thasher:update(data1)\n\tlocal digest = hasher:finup(data2)\n\tassert(digest == expected_digest, \"Finup SHA256 digest mismatch\")\nend)\n\ntest(\"crypto_shash:export and import functionality\", function()\n\tlocal hasher1 = shash(\"sha256\")\n\tlocal hasher2 = shash(\"sha256\")\n\n\tlocal data1 = \"Hello, \"\n\tlocal data2 = \"world!\"\n\tlocal full_data = data1 .. data2\n\n\t-- Test with hasher1: init, update, export\n\thasher1:init()\n\thasher1:update(data1)\n\tlocal exported_state = hasher1:export()\n\n\t-- Test with hasher2: init, import, update, final\n\thasher2:init()\n\thasher2:import(exported_state)\n\thasher2:update(data2)\n\tlocal digest2 = hasher2:final()\n\n\t-- Compute full digest with a third hasher for comparison\n\tlocal hasher_full = shash(\"sha256\")\n\tlocal digest_full = hasher_full:digest(full_data)\n\n\tassert(digest2 == digest_full, \"Digests do not match after export/import\")\n\n\t-- Test with hasher1: finup (after export)\n\tlocal digest1_finup = hasher1:finup(data2)\n\tassert(digest1_finup == digest_full, \"Finup digest after export does not match full digest\")\n\n\t-- Test with hasher1: final (after export)\n\tlocal hasher3 = shash(\"sha256\")\n\thasher3:init()\n\thasher3:update(data1)\n\tlocal exported_state_2 = hasher3:export()\n\tlocal digest3 = hasher3:final() -- Finalize after export, should be digest of data1\n\tlocal hasher_data1 = shash(\"sha256\")\n\tlocal digest_data1 = hasher_data1:digest(data1)\n\tassert(digest3 == digest_data1, \"Final digest after export does not match digest of data1\")\n\n\t-- Test with hasher2: import again and finalize\n\tlocal hasher4 = shash(\"sha256\")\n\thasher4:init()\n\thasher4:import(exported_state_2)\n\tlocal digest4 = hasher4:final()\n\tassert(digest4 == digest_data1, \"Imported state final digest does not match digest of data1\")\nend)\n\n"
  },
  {
    "path": "tests/crypto/skcipher.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025-2026 jperon <cataclop@hotmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal skcipher = require(\"crypto\").skcipher\nlocal util = require(\"util\")\nlocal test = util.test\nlocal hex2bin = util.hex2bin\n\ntest(\"SKCIPHER AES-128-CBC encrypt\", function()\n\tlocal c = skcipher(\"cbc(aes)\")\n\t-- The plaintext must be a multiple of the block size (16 bytes for AES-128)\n\t-- If the plaintext is not a multiple of the block size, it should be padded.\n\tlocal plaintext = \"This is a test!!\"\n\tlocal ciphertext = hex2bin\"d05e07d91a4b4cd10951f8cf195f27b5\"\n\tc:setkey\"0123456789abcdef\"\n\n\tlocal result = c:encrypt(\"fedcba9876543210\", plaintext)\n\tassert(result == ciphertext, \"Cipher text mismatch\")\nend)\n\ntest(\"SKCIPHER AES-128-CBC decrypt\", function()\n\tlocal c = skcipher(\"cbc(aes)\")\n\tlocal plaintext = \"This is a test!!\"\n\tlocal ciphertext = hex2bin\"d05e07d91a4b4cd10951f8cf195f27b5\"\n\tc:setkey\"0123456789abcdef\"\n\n\tlocal result = c:decrypt(\"fedcba9876543210\", ciphertext)\n\tassert(result == plaintext, \"Plain text mismatch\")\nend)\n\ntest(\"SKCIPHER AES-128-CBC ivsize and blocksize\", function()\n\tlocal c = skcipher(\"cbc(aes)\")\n\tassert(c:ivsize() == 16, \"IV size for AES-128-CBC should be 16 bytes\")\n\tassert(c:blocksize() == 16, \"Block size for AES-128-CBC should be 16 bytes\")\nend)\n\ntest(\"SKCIPHER AES-128-CBC setkey with invalid key length\", function()\n\tlocal c = skcipher(\"cbc(aes)\")\n\tlocal status, err = pcall(c.setkey, c, \"0123456789abcde\") -- 15 bytes, invalid for AES-128\n\tassert(not status, \"setkey with invalid key length should fail\")\n\tassert(err == \"EINVAL\", \"Error code should be 'EINVAL', got: \" .. err)\nend)\n\ntest(\"SKCIPHER AES-128-CBC encrypt with incorrect IV length\", function()\n\tlocal c = skcipher(\"cbc(aes)\")\n\tlocal plaintext = \"This is a test!!\"\n\tc:setkey\"0123456789abcdef\"\n\tlocal status, err = pcall(c.encrypt, c, \"fedcba987654321\", plaintext) -- 15 bytes, incorrect IV length\n\tassert(not status, \"encrypt with incorrect IV length should fail\")\n\tassert(err == \"EINVAL\", \"Error code should be 'EINVAL', got: \" .. err)\nend)\n\ntest(\"SKCIPHER AES-128-CBC decrypt with incorrect IV length\", function()\n\tlocal c = skcipher(\"cbc(aes)\")\n\tlocal ciphertext = hex2bin\"d05e07d91a4b4cd10951f8cf195f27b5\"\n\tc:setkey\"0123456789abcdef\"\n\tlocal status, err = pcall(c.decrypt, c, \"fedcba987654321\", ciphertext) -- 15 bytes, incorrect IV length\n\tassert(not status, \"decrypt with incorrect IV length should fail\")\n\tassert(err == \"EINVAL\", \"Error code should be 'EINVAL', got: \" .. err)\nend)\n\ntest(\"SKCIPHER AES-128-CBC encrypt with data not multiple of blocksize\", function()\n\tlocal c = skcipher(\"cbc(aes)\")\n\tlocal plaintext = \"This is a test!!!\" -- 17 bytes, not multiple of 16\n\tc:setkey\"0123456789abcdef\"\n\tlocal status, err = pcall(c.encrypt, c, \"fedcba9876543210\", plaintext)\n\tassert(not status, \"encrypt with data not multiple of blocksize should fail\")\n\tassert(err == \"EINVAL\", \"Error code should be 'EINVAL', got: \" .. err)\nend)\n\ntest(\"SKCIPHER AES-128-CBC decrypt with data not multiple of blocksize\", function()\n\tlocal c = skcipher(\"cbc(aes)\")\n\tlocal ciphertext = hex2bin\"d05e07d91a4b4cd10951f8cf195f27b5\" .. \"00\" -- 17 bytes, not multiple of 16\n\tc:setkey\"0123456789abcdef\"\n\tlocal status, err = pcall(c.decrypt, c, \"fedcba9876543210\", ciphertext)\n\tassert(not status, \"decrypt with data not multiple of blocksize should fail\")\n\tassert(err == \"EINVAL\", \"Error code should be 'EINVAL', got: \" .. err)\nend)\n\n"
  },
  {
    "path": "tests/io/softirq.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Kernel-side script for the io softirq guard test (see test.sh).\n\nassert(io == nil, \"io must not be available in softirq runtime\")\nprint(\"io: softirq guard ok\")\n\n"
  },
  {
    "path": "tests/io/test.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Kernel-side script for the io test (see test.sh).\n\nlocal TMPFILE = \"/tmp/lunatik_io_test\"\nlocal CONTENT = \"hello lunatik\\n\"\n\n-- io.type on non-file values\nassert(not io.type(\"x\"),  \"io.type(string) should fail\")\nassert(not io.type(42),   \"io.type(number) should fail\")\nassert(not io.type(nil),  \"io.type(nil) should fail\")\n\n-- open non-existent file\nlocal f, err = io.open(\"/nonexistent/lunatik_io_test\", \"r\")\nassert(f == nil,            \"open non-existent: expected nil\")\nassert(type(err) == \"string\", \"open non-existent: expected error string\")\n\n-- open with unsupported mode raises an error\nlocal ok2 = pcall(io.open, \"/proc/version\", \"x\")\nassert(not ok2, \"open with invalid mode should raise\")\n\n-- open /proc/version for reading\nlocal f3 = assert(io.open(\"/proc/version\", \"r\"))\nassert(io.type(f3) == \"file\", \"io.type on open file\")\n\n-- read(\"l\"): one line without newline\nlocal line = f3:read(\"l\")\nassert(type(line) == \"string\" and #line > 0, \"read('l'): expected non-empty string\")\nassert(line:sub(-1) ~= \"\\n\",                 \"read('l'): must not include newline\")\n\n-- read past EOF returns nil\nassert(f3:read(\"l\") == nil, \"read past EOF returns nil\")\n\n-- close and check type\nf3:close()\nassert(io.type(f3) == \"closed file\", \"io.type after close\")\n\n-- read on closed file must raise\nlocal ok, _ = pcall(function() f3:read(\"l\") end)\nassert(not ok, \"read on closed file must raise\")\n\n-- write on closed file must raise\nlocal ok3 = pcall(function() f3:write(\"x\") end)\nassert(not ok3, \"write on closed file must raise\")\n\n-- read(\"L\"): line with newline\nlocal f4 = assert(io.open(\"/proc/version\", \"r\"))\nlocal lineL = f4:read(\"L\")\nassert(type(lineL) == \"string\", \"read('L'): expected string\")\nassert(lineL:sub(-1) == \"\\n\", \"read('L'): must include newline\")\nf4:close()\n\n-- read(\"a\"): read entire file\nlocal f5 = assert(io.open(\"/proc/version\", \"r\"))\nlocal all = f5:read(\"a\")\nassert(type(all) == \"string\" and #all > 0, \"read('a'): expected non-empty string\")\nf5:close()\n\n-- io.lines: iterate /proc/version\nlocal nlines = 0\nfor l in io.lines(\"/proc/version\") do\n\tassert(type(l) == \"string\", \"io.lines: expected string per line\")\n\tnlines = nlines + 1\nend\nassert(nlines > 0, \"io.lines: expected at least one line\")\n\n-- write, read back, append, seek, read by count\nlocal fw = assert(io.open(TMPFILE, \"w\"))\nassert(fw:write(CONTENT),        \"write: expected truthy\")\nassert(fw:flush(),               \"flush: expected truthy\")\nfw:close()\n\nlocal fr = assert(io.open(TMPFILE, \"r\"))\nlocal got = fr:read(\"a\")\nassert(got == CONTENT, \"read-back: got \" .. tostring(got))\nfr:close()\n\nlocal fa = assert(io.open(TMPFILE, \"a\"))\nfa:write(CONTENT)\nfa:close()\n\nlocal fr2 = assert(io.open(TMPFILE, \"r\"))\nlocal got2 = fr2:read(\"a\")\nassert(got2 == CONTENT .. CONTENT, \"append: got \" .. tostring(got2))\nfr2:close()\n\n-- seek and read by count\nlocal fs = assert(io.open(TMPFILE, \"r\"))\nlocal size = fs:seek(\"end\", 0)\nassert(size == #CONTENT * 2, \"seek('end'): size mismatch\")\nfs:seek(\"set\", 0)\nlocal chunk = fs:read(5)\nassert(chunk == \"hello\", \"read(5): got \" .. tostring(chunk))\nfs:seek(\"cur\", 0)\n\n-- file:lines iterator\nfs:seek(\"set\", 0)\nlocal lcount = 0\nfor l in fs:lines() do\n\tassert(type(l) == \"string\", \"file:lines: expected string\")\n\tlcount = lcount + 1\nend\nassert(lcount == 2, \"file:lines: expected 2 lines, got \" .. tostring(lcount))\nfs:close()\n\n-- open with \"r+\" (read-write): write then read back\nlocal frw = assert(io.open(TMPFILE, \"r+\"))\nfrw:seek(\"set\", 0)\nfrw:write(\"HELLO\")\nfrw:seek(\"set\", 0)\nlocal rw = frw:read(5)\nassert(rw == \"HELLO\", \"r+: read back got \" .. tostring(rw))\nfrw:close()\n\nprint(\"io: all tests passed\")\n\n"
  },
  {
    "path": "tests/io/test.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Tests for the io library: open, read, write, seek, lines, type, edge cases.\n#\n# Usage: sudo bash tests/io/test.sh\n\nSCRIPT=\"tests/io/test\"\nSCRIPT_SOFTIRQ=\"tests/io/softirq\"\nTMPFILE=\"/tmp/lunatik_io_test\"\n\nsource \"$(dirname \"$(readlink -f \"$0\")\")/../lib.sh\"\n\ncleanup() {\n\trm -f \"$TMPFILE\"\n}\ntrap cleanup EXIT\ncleanup\n\nktap_header\nktap_plan 2\n\nmark_dmesg\nrun_script \"$SCRIPT\"\ncheck_dmesg || { ktap_totals; exit 1; }\n\nktap_pass \"io: open/read/write/seek/lines/type and edge cases\"\n\nmark_dmesg\nrun_script \"$SCRIPT_SOFTIRQ\" softirq\ncheck_dmesg || { ktap_totals; exit 1; }\nktap_pass \"io: not available in softirq runtime\"\n\nktap_totals\n\n"
  },
  {
    "path": "tests/lib.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# KTAP helpers and lunatik test utilities.\n# Source this file from each test script.\n\nKTAP_COUNT=0\nKTAP_PASS=0\nKTAP_FAIL=0\n\nktap_header() { echo \"KTAP version 1\"; }\nktap_plan()   { echo \"1..$1\"; }\nktap_pass()   { KTAP_COUNT=$((KTAP_COUNT+1)); KTAP_PASS=$((KTAP_PASS+1)); echo \"ok $KTAP_COUNT $*\"; }\nktap_fail()   { KTAP_COUNT=$((KTAP_COUNT+1)); KTAP_FAIL=$((KTAP_FAIL+1)); echo \"not ok $KTAP_COUNT $*\"; }\nktap_totals() { echo \"# Totals: pass:$KTAP_PASS fail:$KTAP_FAIL skip:0\"; }\n\nDMESG_LINE=0\nmark_dmesg()  { DMESG_LINE=$(dmesg | wc -l); }\ncheck_dmesg() {\n\tlocal errs\n\terrs=$(dmesg | tail -n +$((DMESG_LINE+1)) | grep -E \"\\.lua:[0-9]+:\" || true)\n\t[ -z \"$errs\" ] && return 0\n\tktap_fail \"no Lua errors in kernel\"\n\techo \"# $errs\"\n\treturn 1\n}\n\nrun_script() {\n\tlocal output\n\toutput=$(lunatik run \"$@\")\n\techo \"$output\" | grep -qE \"\\.lua:[0-9]+:\" || return 0\n\tktap_fail \"Lua error in script\"\n\techo \"# $output\"\n\tktap_totals\n\texit 1\n}\n\n# Each test script must define cleanup().\n# fail <description> stops the script, calls cleanup, and exits non-zero.\nfail() {\n\tktap_fail \"$*\"\n\techo \"# FAIL: $*\" >&2\n\tlunatik stop \"${SCRIPT:-}\" 2>/dev/null\n\tcleanup 2>/dev/null || true\n\tktap_totals\n\texit 1\n}\n\n"
  },
  {
    "path": "tests/monitor/gc.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Kernel-side script for the GC-under-spinlock regression test (see gc.sh).\n--\n\nlocal socket = require(\"socket\")\nlocal fifo   = require(\"fifo\")\nlocal linux  = require(\"linux\")\nlocal thread = require(\"thread\")\n\ncollectgarbage(\"generational\")\ncollectgarbage(\"param\", \"minormul\", 0)\n\nlocal f = fifo.new(256)\n\nreturn function()\n\twhile not thread.shouldstop() do\n\t\tcollectgarbage(\"stop\")\n\t\tf:push(string.rep(\"x\", 256))\n\n\t\tfor i = 1, 10 do\n\t\t\tsocket.new(socket.af.PACKET, socket.sock.RAW, 0)\n\t\tend\n\n\t\tcollectgarbage(\"restart\")\n\t\tf:pop(256)\n\t\tlinux.schedule(1)\n\tend\nend\n\n"
  },
  {
    "path": "tests/monitor/gc.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Regression test for GC-under-spinlock in lunatik_monitor (PR #459).\n# A spawned thread uses a sleep=false fifo from a sleep=true runtime.\n# lunatik_monitor holds spin_lock_bh for fifo methods; f:pop() allocates\n# a long Lua string inside the monitor, triggering GC that finalizes a\n# dropped AF_PACKET socket. Without the fix (lua_gc stop/restart around\n# the spinlock), mutex_lock sleeps under spin_lock_bh and the kernel\n# panics with \"BUG: scheduling while atomic\".\n#\n# Usage: sudo bash tests/monitor/gc.sh\n\nSCRIPT=\"tests/monitor/gc\"\nSLEEP=1\n\nsource \"$(dirname \"$(readlink -f \"$0\")\")/../lib.sh\"\n\ncleanup() { lunatik stop \"$SCRIPT\" 2>/dev/null; }\ntrap cleanup EXIT\ncleanup\n\nktap_header\nktap_plan 1\n\nmark_dmesg\nmark_ts=$(awk '{print $1}' /proc/uptime)\n\nlunatik spawn \"$SCRIPT\"\nsleep $SLEEP\nlunatik stop \"$SCRIPT\"\n\ncheck_dmesg || exit 1\n# scan dmesg since mark_ts for \"scheduling while atomic\" (GC-under-spinlock panic)\nsched=$(dmesg | awk -v ts=\"$mark_ts\" \\\n\t'match($0, /\\[[ ]*([0-9]+\\.[0-9]+)/, a) && a[1]+0 >= ts+0' | \\\n\tgrep \"scheduling while atomic\" || true)\n[ -n \"$sched\" ] && fail \"GC ran under spinlock: scheduling while atomic\"\nktap_pass \"GC did not run under spinlock\"\n\nktap_totals\n\n"
  },
  {
    "path": "tests/monitor/run.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Runs all monitor tests and reports aggregated KTAP results.\n#\n# Usage: sudo bash tests/monitor/run.sh\n\nDIR=\"$(dirname \"$(readlink -f \"$0\")\")\"\nFAILED=0\n\nSEP=\"\"\nfor t in \"$DIR\"/gc.sh; do\n\techo \"${SEP}# --- $(basename \"$t\") ---\"\n\tSEP=$'\\n'\n\tbash \"$t\" || FAILED=$((FAILED+1))\ndone\n\nexit $FAILED\n\n"
  },
  {
    "path": "tests/rcu/map_sync.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025-2026 jperon <cataclop@hotmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\n-- Usage:\n-- > lunatik spawn tests/rcu/map_sync\n--\n-- It should output a random number about every second.\n--\n-- To stop it:\n-- > lunatik stop tests/rcu/map_sync ; lunatik stop tests/rcu/map_sync_clean\n\nlocal lunatik = require \"lunatik\"\nlocal linux = require \"linux\"\nlocal rcu = require \"rcu\"\nlocal runner = require \"lunatik.runner\"\nlocal thread = require \"thread\"\n\nlocal function milliseconds()\n\treturn linux.time() / 1000000\nend\n\nreturn function()\n\tlunatik._ENV.whitelist = rcu.table(1024)\n\trunner.spawn \"tests/rcu/map_sync_clean\"\n\n\tlocal whitelist = lunatik._ENV.whitelist\n\tlocal start = milliseconds()\n\tlocal last_print = start\n\tlocal now = start\n\n\twhile (not thread.shouldstop()) and (now - start < 60000) do\n\t\tnow = milliseconds()\n\t\tlocal entry = whitelist[now - linux.random(1, 1000)]\n\n\t\tif entry and now - last_print > 1000 then\n\t\t\tlast_print = now\n\t\t\tprint(string.format(\"map_sync: reader found entry written %dms ago\", now - entry:getnumber(0)))\n\t\tend\n\tend\n\n\tlunatik._ENV.whitelist = nil\nend\n\n"
  },
  {
    "path": "tests/rcu/map_sync.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Concurrency test: verifies rcu.map() is safe when called while another\n# kthread simultaneously modifies the table.\n#\n# Usage: sudo bash tests/rcu/map_sync.sh\n\nDIR=\"$(dirname \"$(readlink -f \"$0\")\")\"\n\nsource \"$DIR/../lib.sh\"\n\nSLEEP=5\n\ncleanup() {\n\tlunatik stop tests/rcu/map_sync_clean 2>/dev/null\n\tlunatik stop tests/rcu/map_sync       2>/dev/null\n}\ntrap cleanup EXIT\ncleanup\n\nktap_header\nktap_plan 1\n\nmark_dmesg\necho \"# spawning map_sync (reader) and map_sync_clean (writer) kthreads...\"\nlunatik spawn tests/rcu/map_sync\necho \"# running concurrently for ${SLEEP}s (timestamps from reader appear in dmesg)...\"\nsleep $SLEEP\necho \"# stopping kthreads...\"\ncleanup\n\nif check_dmesg; then\n\tktap_pass \"rcu/map_sync\"\nfi\n\nktap_totals\n[ $KTAP_FAIL -eq 0 ]\n\n"
  },
  {
    "path": "tests/rcu/map_sync_clean.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2025 jperon <cataclop@hotmail.com>\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal lunatik = require \"lunatik\"\nlocal linux = require \"linux\"\nlocal rcu = require \"rcu\"\nlocal data = require \"data\"\nlocal thread = require \"thread\"\n\nlocal function milliseconds()\n\treturn linux.time() / 1000000\nend\n\nreturn function()\n\tlocal whitelist = lunatik._ENV.whitelist\n\tlocal start = milliseconds()\n\tlocal now = start\n\n\twhile (not thread.shouldstop()) and (now - start < 60000) do\n\t\tnow = milliseconds()\n\t\tlocal d = whitelist[now] or data.new(32)\n\t\td:setnumber(0, now)\n\t\twhitelist[now] = d\n\n\t\trcu.map(whitelist, function(k, v)\n\t\t\tif now > v:getnumber(0) + 500 then\n\t\t\t\twhitelist[k] = nil\n\t\t\tend\n\t\tend)\n\tend\n\nend\n\n"
  },
  {
    "path": "tests/rcu/map_values.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n\nlocal rcu = require \"rcu\"\nlocal data = require \"data\"\nlocal test = require(\"util\").test\n\ntest(\"rcu.map iterates boolean values\", function()\n\tlocal t = rcu.table(4)\n\tt[\"yes\"] = true\n\tt[\"no\"] = false\n\tlocal results = {}\n\trcu.map(t, function(k, v) results[k] = v end)\n\tassert(results[\"yes\"] == true, \"expected true, got: \" .. tostring(results[\"yes\"]))\n\tassert(results[\"no\"] == false, \"expected false, got: \" .. tostring(results[\"no\"]))\nend)\n\ntest(\"rcu.map iterates integer values\", function()\n\tlocal t = rcu.table(4)\n\tt[\"a\"] = 42\n\tt[\"b\"] = -7\n\tlocal results = {}\n\trcu.map(t, function(k, v) results[k] = v end)\n\tassert(results[\"a\"] == 42, \"expected 42, got: \" .. tostring(results[\"a\"]))\n\tassert(results[\"b\"] == -7, \"expected -7, got: \" .. tostring(results[\"b\"]))\nend)\n\ntest(\"rcu.map iterates userdata values (regression)\", function()\n\tlocal t = rcu.table(4)\n\tlocal d = data.new(8)\n\td:setnumber(0, 99)\n\tt[\"obj\"] = d\n\tlocal found = false\n\trcu.map(t, function(k, v)\n\t\tif k == \"obj\" then\n\t\t\tassert(v:getnumber(0) == 99, \"wrong value in userdata\")\n\t\t\tfound = true\n\t\tend\n\tend)\n\tassert(found, \"userdata entry not found by rcu.map\")\nend)\n\ntest(\"rcu.map iterates mixed types\", function()\n\tlocal t = rcu.table(4)\n\tt[\"flag\"] = true\n\tt[\"count\"] = 42\n\tlocal d = data.new(4)\n\tt[\"obj\"] = d\n\tlocal count = 0\n\trcu.map(t, function(k, v) count = count + 1 end)\n\tassert(count == 3, \"expected 3 entries, got: \" .. count)\nend)\n\ntest(\"rcu.map skips nil (deleted) entries\", function()\n\tlocal t = rcu.table(4)\n\tt[\"x\"] = 1\n\tt[\"y\"] = 2\n\tt[\"x\"] = nil  -- delete\n\tlocal count = 0\n\trcu.map(t, function(k, v) count = count + 1 end)\n\tassert(count == 1, \"expected 1 entry after deletion, got: \" .. count)\nend)\n\n"
  },
  {
    "path": "tests/rcu/run.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Runs rcu regression tests and reports aggregated KTAP results.\n#\n# Usage: sudo bash tests/rcu/run.sh\n\nDIR=\"$(dirname \"$(readlink -f \"$0\")\")\"\n\nsource \"$DIR/../lib.sh\"\n\nTESTS=\"map_values\"\nTOTAL=$(echo $TESTS | wc -w)\n\nktap_header\nktap_plan $TOTAL\n\nfor t in $TESTS; do\n\tmark_dmesg\n\tif ! lunatik run \"tests/rcu/$t\" 2>/dev/null; then\n\t\tktap_fail \"rcu/$t: script execution failed\"\n\t\tcontinue\n\tfi\n\terrs=$(dmesg | tail -n +$((DMESG_LINE+1)) | grep -iE \"^[^:]+: FAIL\t|\\.lua:[0-9]+:\" || true)\n\tif [ -z \"$errs\" ]; then\n\t\tktap_pass \"rcu/$t\"\n\telse\n\t\tktap_fail \"rcu/$t\"\n\t\twhile IFS= read -r line; do\n\t\t\techo \"# $line\"\n\t\tdone <<< \"$errs\"\n\tfi\ndone\n\nktap_totals\nRESULT=$?\n\necho \"\"\nbash \"$DIR/map_sync.sh\" || RESULT=1\nexit $RESULT\n\n"
  },
  {
    "path": "tests/run.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Runs all lunatik test suites.\n#\n# Usage: sudo bash tests/run.sh\n\nDIR=\"$(dirname \"$(readlink -f \"$0\")\")\"\nTOTAL_PASS=0\nTOTAL_FAIL=0\nTOTAL_SKIP=0\n\nrun_suite() {\n\tlocal output\n\toutput=$(bash \"$1\")\n\tlocal ret=$?\n\techo \"$output\"\n\twhile IFS= read -r totals; do\n\t\tTOTAL_PASS=$((TOTAL_PASS + $(echo \"$totals\" | grep -oP 'pass:\\K[0-9]+' || echo 0)))\n\t\tTOTAL_FAIL=$((TOTAL_FAIL + $(echo \"$totals\" | grep -oP 'fail:\\K[0-9]+' || echo 0)))\n\t\tTOTAL_SKIP=$((TOTAL_SKIP + $(echo \"$totals\" | grep -oP 'skip:\\K[0-9]+' || echo 0)))\n\tdone < <(echo \"$output\" | grep \"^# Totals:\")\n\treturn $ret\n}\n\nrun_suite \"$DIR/monitor/run.sh\"\nrun_suite \"$DIR/thread/run.sh\"\nrun_suite \"$DIR/runtime/run.sh\"\nrun_suite \"$DIR/socket/run.sh\"\nrun_suite \"$DIR/rcu/run.sh\"\nrun_suite \"$DIR/crypto/run.sh\"\nrun_suite \"$DIR/io/test.sh\"\n\necho \"\"\necho \"# Grand Totals: pass:$TOTAL_PASS fail:$TOTAL_FAIL skip:$TOTAL_SKIP\"\n[ $TOTAL_FAIL -eq 0 ]\n\n"
  },
  {
    "path": "tests/runtime/opt_guards.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Kernel-side script for the opt_guards regression tests (see opt_guards.sh).\n\nlocal lunatik = require(\"lunatik\")\nlocal data     = require(\"data\")\nlocal rcu      = require(\"rcu\")\n\nlocal function assert_error(fn, pattern)\n\tlocal ok, err = pcall(fn)\n\tassert(not ok, \"expected error but got none\")\n\tassert(err:find(pattern), \"unexpected error: \" .. tostring(err))\nend\n\nlocal function assert_ok(fn)\n\tlocal ok, err = pcall(fn)\n\tassert(ok, \"unexpected error: \" .. tostring(err))\nend\n\nlocal recv = lunatik.runtime(\"tests/runtime/opt_guards_recv\")\n\n-- SINGLE via resume: must be rejected (use a disposable runtime — SINGLE error kills the coroutine)\nassert_error(function()\n\tlocal tmp = lunatik.runtime(\"tests/runtime/opt_guards_recv\")\n\ttmp:resume(data.new(4, \"single\"))\nend, \"cannot share SINGLE object\")\n\n-- SINGLE via _ENV: must be rejected\nassert_error(function()\n\tlunatik._ENV[\"opt_guard_test\"] = data.new(4, \"single\")\nend, \"cannot share SINGLE object\")\n\n-- MONITOR via resume: must succeed\nassert_ok(function()\n\tlocal d = data.new(4)\n\trecv:resume(d)\nend)\n\n-- MONITOR explicit mode via resume: must succeed\nassert_ok(function()\n\tlocal d = data.new(4, \"shared\")\n\trecv:resume(d)\nend)\n\n-- MONITOR via _ENV: must succeed\nassert_ok(function()\n\tlocal d = data.new(4)\n\tlunatik._ENV[\"opt_guard_monitor\"] = d\n\tlunatik._ENV[\"opt_guard_monitor\"] = nil\nend)\n\n-- NONE via resume: must succeed\nassert_ok(function()\n\tlocal t = rcu.table(4)\n\trecv:resume(t)\nend)\n\n-- NONE via _ENV: must succeed\nassert_ok(function()\n\tlocal t = rcu.table(4)\n\tlunatik._ENV[\"opt_guard_none\"] = t\n\tlunatik._ENV[\"opt_guard_none\"] = nil\nend)\n\n-- MONITOR object passed via resume multiple times: refcount must remain stable\nassert_ok(function()\n\tlocal d = data.new(4)\n\trecv:resume(d)\n\trecv:resume(d)\n\trecv:resume(d)\nend)\n\n-- Invalid mode: must error\nassert_error(function()\n\tdata.new(4, \"invalid\")\nend, \"invalid option\")\n\n"
  },
  {
    "path": "tests/runtime/opt_guards.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Tests lunatik_opt_t guards:\n#   - SINGLE objects are rejected by resume() and _ENV[\"key\"] = obj\n#   - MONITOR/NONE objects pass through resume() without error\n#\n# Usage: sudo bash tests/runtime/opt_guards.sh\n\nSCRIPT=\"tests/runtime/opt_guards\"\n\nsource \"$(dirname \"$(readlink -f \"$0\")\")/../lib.sh\"\n\ncleanup() { lunatik stop \"$SCRIPT\" 2>/dev/null; }\ntrap cleanup EXIT\ncleanup\n\nktap_header\nktap_plan 1\n\nmark_dmesg\n\nrun_script \"$SCRIPT\"\n\ncheck_dmesg || { ktap_totals; exit 1; }\nktap_pass \"opt guards: SINGLE rejected, MONITOR/NONE accepted\"\n\nktap_totals\n\n"
  },
  {
    "path": "tests/runtime/opt_guards_recv.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Receiver sub-script for the opt_guards test; loops with yield to stay\n-- alive across multiple resume() calls.\n\nlocal function recv(obj)\n\twhile true do\n\t\tassert(obj ~= nil, \"expected a shared object\")\n\t\tobj = coroutine.yield()\n\tend\nend\n\nreturn recv\n\n"
  },
  {
    "path": "tests/runtime/opt_skb_single.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Kernel-side script for the opt_skb_single regression test (see opt_skb_single.sh).\n\nlocal netfilter = require(\"netfilter\")\nlocal family    = netfilter.family\nlocal action    = netfilter.action\nlocal hooks     = netfilter.inet_hooks\nlocal priority  = netfilter.ip_priority\nlocal lunatik   = require(\"lunatik\")\n\nlocal triggered = false\n\nlocal function hook(skb)\n\tif triggered then\n\t\treturn action.ACCEPT\n\tend\n\ttriggered = true\n\n\tlocal ok, err = pcall(function()\n\t\tlunatik._ENV[\"opt_skb_single\"] = skb\n\tend)\n\tassert(not ok and err:find(\"cannot share SINGLE object\"),\n\t\t\"expected SINGLE rejection for skb: \" .. tostring(err))\n\n\tlocal ok2, err2 = pcall(function()\n\t\tlunatik._ENV[\"opt_data_single\"] = skb:data()\n\tend)\n\tassert(not ok2 and err2:find(\"cannot share SINGLE object\"),\n\t\t\"expected SINGLE rejection for skb:data(): \" .. tostring(err2))\n\n\treturn action.ACCEPT\nend\n\nnetfilter.register{\n\thook     = hook,\n\tpf       = family.INET,\n\thooknum  = hooks.LOCAL_OUT,\n\tpriority = priority.FILTER,\n}\n\n"
  },
  {
    "path": "tests/runtime/opt_skb_single.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Verifies that skb and skb:data() are SINGLE objects that cannot be stored\n# in _ENV. Registers a LOCAL_OUT netfilter hook, triggers it via loopback\n# ping, and asserts that both _ENV assignments fail with \"cannot share\n# SINGLE object\".\n#\n# Usage: sudo bash tests/runtime/opt_skb_single.sh\n\nSCRIPT=\"tests/runtime/opt_skb_single\"\nMODULE=\"luanetfilter\"\n\nsource \"$(dirname \"$(readlink -f \"$0\")\")/../lib.sh\"\n\ncleanup() { lunatik stop \"$SCRIPT\" 2>/dev/null; }\ntrap cleanup EXIT\ncleanup\n\nktap_header\nktap_plan 1\n\ncat /sys/module/$MODULE/refcnt > /dev/null 2>&1 || {\n\techo \"# SKIP: $MODULE not loaded\"\n\tktap_totals\n\texit 0\n}\n\nmark_dmesg\n\nrun_script \"$SCRIPT\" softirq\n\nping -c 1 -W 1 127.0.0.1 > /dev/null 2>&1 || true\n\ncheck_dmesg || { ktap_totals; exit 1; }\nktap_pass \"skb and skb:data() correctly rejected as SINGLE from _ENV\"\n\nktap_totals\n\n"
  },
  {
    "path": "tests/runtime/rcu_shared.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Driver script for the rcu_shared regression test (see rcu_shared.sh).\n\nlocal lunatik = require(\"lunatik\")\nlocal rcu     = require(\"rcu\")\n\nlocal rt = lunatik.runtime(\"tests/runtime/rcu_shared_recv\")\n\nlocal t = rcu.table(4)\nt[\"answer\"] = 42\nlunatik._ENV[\"rcu_shared_test\"] = t\n\nrt:resume()\n\nlunatik._ENV[\"rcu_shared_test\"] = nil\n\n"
  },
  {
    "path": "tests/runtime/rcu_shared.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Regression test: rcu.table() objects must be clonable so they can be stored\n# in lunatik._ENV and retrieved by another runtime.\n#\n# Usage: sudo bash tests/runtime/rcu_shared.sh\n\nSCRIPT=\"tests/runtime/rcu_shared\"\nMODULE=\"luarcu\"\n\nsource \"$(dirname \"$(readlink -f \"$0\")\")/../lib.sh\"\n\ncleanup() { lunatik stop \"$SCRIPT\" 2>/dev/null; }\ntrap cleanup EXIT\ncleanup\n\nktap_header\nktap_plan 1\n\ncat /sys/module/$MODULE/refcnt > /dev/null 2>&1 || {\n\techo \"# SKIP: $MODULE not loaded\"\n\tktap_totals\n\texit 0\n}\n\nmark_dmesg\n\nrun_script \"$SCRIPT\"\n\ncheck_dmesg || { ktap_totals; exit 1; }\nktap_pass \"rcu.table() stored in _ENV and retrieved by another runtime\"\n\nktap_totals\n\n"
  },
  {
    "path": "tests/runtime/rcu_shared_recv.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Receiver sub-script for the rcu_shared regression test (see rcu_shared.sh).\n\nlocal lunatik = require(\"lunatik\")\n\nlocal function recv()\n\tlocal t = lunatik._ENV[\"rcu_shared_test\"]\n\tassert(t ~= nil, \"rcu_shared_test not found in _ENV\")\n\tassert(t[\"answer\"] == 42, \"unexpected value in shared rcu table\")\nend\n\nreturn recv\n\n"
  },
  {
    "path": "tests/runtime/refcnt_leak.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Kernel-side script for the runtime refcnt leak regression test (see refcnt_leak.sh).\n\nlocal netfilter = require(\"netfilter\")\nlocal family    = netfilter.family\nlocal action    = netfilter.action\nlocal hooks     = netfilter.inet_hooks\nlocal priority  = netfilter.ip_priority\n\nlocal function hook(skb)\n\treturn action.ACCEPT\nend\n\nnetfilter.register{\n\thook     = hook,\n\tpf       = family.INET,\n\thooknum  = hooks.FORWARD,\n\tpriority = priority.FILTER,\n\tmark     = 0,\n}\n\nerror(\"intentional error after first register\")\n\n"
  },
  {
    "path": "tests/runtime/refcnt_leak.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Regression test for module refcnt leak in lunatik_newruntime error path.\n#\n# A script calls require(\"netfilter\") and then netfilter.register() once\n# (successfully, runtime kref → 2), then errors.  Without the fix, the error\n# path never calls lua_close(), so the LSTRMEM string holding the\n# luanetfilter module reference is never freed and luanetfilter's use-count\n# stays elevated.  With the fix, lua_close() is called explicitly before\n# lunatik_putobject(), GC runs, the hook is finalized, and the use-count is\n# restored.\n#\n# Usage: sudo bash tests/runtime/refcnt_leak.sh\n\nSCRIPT=\"tests/runtime/refcnt_leak\"\nMODULE=\"luanetfilter\"\n\nsource \"$(dirname \"$(readlink -f \"$0\")\")/../lib.sh\"\n\nktap_header\nktap_plan 1\n\nbefore=$(cat /sys/module/$MODULE/refcnt 2>/dev/null) || {\n\techo \"# SKIP: $MODULE not loaded\"\n\tktap_totals\n\texit 0\n}\n\nmark_dmesg\n\n# run the script in a non-sleepable runtime (required for netfilter hooks);\n# it is expected to fail — ignore the error\nlunatik run \"$SCRIPT\" softirq 2>/dev/null || true\n\ncheck_dmesg || { ktap_totals; exit 1; }\n\nafter=$(cat /sys/module/$MODULE/refcnt 2>/dev/null)\n[ \"$before\" = \"$after\" ] || fail \"$MODULE refcnt leaked: $before -> $after (fix lunatik_newruntime error path)\"\n\nktap_pass \"$MODULE refcnt restored after failed script\"\n\nktap_totals\n\n"
  },
  {
    "path": "tests/runtime/resume_mailbox.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Driver script for the resume_mailbox regression test (see resume_mailbox.sh).\n-- Passes a mailbox fifo and completion via runtime:resume() to a sub-runtime\n-- that sends a message; the main runtime receives and asserts the value.\n\nlocal lunatik = require(\"lunatik\")\nlocal mailbox = require(\"mailbox\")\n\nlocal inbox = mailbox.inbox(64)\n\nlocal rt = lunatik.runtime(\"tests/runtime/resume_mailbox_send\")\nrt:resume(inbox.queue, inbox.event)\n\nlocal msg = inbox:receive()\nassert(msg == \"hello mailbox!\", msg)\n\n"
  },
  {
    "path": "tests/runtime/resume_mailbox.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Regression test: completion objects must be passable via runtime:resume()\n# to enable the mailbox pattern. Passes a fifo and completion to a sub-runtime\n# that sends a message; the main runtime receives and asserts the value.\n#\n# Usage: sudo bash tests/runtime/resume_mailbox.sh\n\nSCRIPT=\"tests/runtime/resume_mailbox\"\n\nsource \"$(dirname \"$(readlink -f \"$0\")\")/../lib.sh\"\n\ncleanup() { lunatik stop \"$SCRIPT\" 2>/dev/null; }\ntrap cleanup EXIT\ncleanup\n\nktap_header\nktap_plan 1\n\nfor mod in luafifo luacompletion; do\n\tcat /sys/module/$mod/refcnt > /dev/null 2>&1 || {\n\t\techo \"# SKIP: $mod not loaded\"\n\t\tktap_totals\n\t\texit 0\n\t}\ndone\n\nmark_dmesg\n\nrun_script \"$SCRIPT\"\n\ncheck_dmesg || { ktap_totals; exit 1; }\nktap_pass \"mailbox send/receive via resume succeeded\"\n\nktap_totals\n\n"
  },
  {
    "path": "tests/runtime/resume_mailbox_send.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Sender sub-script for the resume_mailbox regression test.\n-- Receives a fifo and completion via runtime:resume() and sends a message.\n\nlocal mailbox = require(\"mailbox\")\n\nlocal function send(f, c)\n\tlocal outbox = mailbox.outbox(f, c)\n\toutbox:send(\"hello mailbox!\")\nend\n\nreturn send\n\n"
  },
  {
    "path": "tests/runtime/resume_shared.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Driver script for the resume_shared regression test (see resume_shared.sh).\n-- Passes a shared (monitored) fifo object via runtime:resume().\n\nlocal lunatik = require(\"lunatik\")\nlocal fifo    = require(\"fifo\")\n\nlocal rt = lunatik.runtime(\"tests/runtime/resume_shared_recv\")\nlocal f  = fifo.new(13)\nf:push(\"hello kernel!\")\nrt:resume(f)\n\n"
  },
  {
    "path": "tests/runtime/resume_shared.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Regression test: runtime:resume() must correctly pass shared (monitored)\n# objects across runtime boundaries. Pushes a value into a shared fifo,\n# passes it to a sub-runtime via resume(), and asserts the value can be popped.\n#\n# Usage: sudo bash tests/runtime/resume_shared.sh\n\nSCRIPT=\"tests/runtime/resume_shared\"\nMODULE=\"luafifo\"\n\nsource \"$(dirname \"$(readlink -f \"$0\")\")/../lib.sh\"\n\ncleanup() { lunatik stop \"$SCRIPT\" 2>/dev/null; }\ntrap cleanup EXIT\ncleanup\n\nktap_header\nktap_plan 1\n\ncat /sys/module/$MODULE/refcnt > /dev/null 2>&1 || {\n\techo \"# SKIP: $MODULE not loaded\"\n\tktap_totals\n\texit 0\n}\n\nmark_dmesg\n\nrun_script \"$SCRIPT\"\n\ncheck_dmesg || { ktap_totals; exit 1; }\nktap_pass \"resume with shared fifo object succeeded\"\n\nktap_totals\n\n"
  },
  {
    "path": "tests/runtime/resume_shared_recv.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Receiver sub-script for the resume_shared regression test.\n-- Receives a fifo object passed via runtime:resume() and verifies it is valid.\n\nlocal function recv(f)\n\tassert(f:pop(13) == \"hello kernel!\")\nend\n\nreturn recv\n\n"
  },
  {
    "path": "tests/runtime/run.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Runs all runtime tests and reports aggregated KTAP results.\n#\n# Usage: sudo bash tests/runtime/run.sh\n\nDIR=\"$(dirname \"$(readlink -f \"$0\")\")\"\nFAILED=0\n\nSEP=\"\"\nfor t in \"$DIR\"/refcnt_leak.sh \"$DIR\"/resume_shared.sh \"$DIR\"/resume_mailbox.sh \"$DIR\"/rcu_shared.sh \"$DIR\"/opt_guards.sh \"$DIR\"/opt_skb_single.sh; do\n\techo \"${SEP}# --- $(basename \"$t\") ---\"\n\tSEP=$'\\n'\n\tbash \"$t\" || FAILED=$((FAILED+1))\ndone\n\nexit $FAILED\n\n"
  },
  {
    "path": "tests/socket/run.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Runs all socket tests and reports aggregated KTAP results.\n#\n# Usage: sudo bash tests/socket/run.sh\n\nDIR=\"$(dirname \"$(readlink -f \"$0\")\")\"\nFAILED=0\n\nSEP=\"\"\nfor t in \"$DIR\"/unix/run.sh; do\n\techo \"${SEP}# --- unix ---\"\n\tSEP=$'\\n'\n\tbash \"$t\" || FAILED=$((FAILED+1))\ndone\n\nexit $FAILED\n\n"
  },
  {
    "path": "tests/socket/unix/dgram.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Tests socket.unix DGRAM: sendto() using the server path stored at\n# construction time (no explicit path passed to sendto()).\n#\n# Usage: sudo bash tests/socket/unix/dgram.sh\n\nSCRIPT_SERVER=\"tests/socket/unix/dgram_server\"\nSCRIPT_CLIENT=\"tests/socket/unix/dgram_client\"\nSOCK=\"/tmp/lunatik_unix_dgram.sock\"\nMODULE=\"luasocket\"\nSLEEP=1\n\nsource \"$(dirname \"$(readlink -f \"$0\")\")/../../lib.sh\"\n\ncleanup() {\n\tlunatik stop \"$SCRIPT_SERVER\" 2>/dev/null\n\tlunatik stop \"$SCRIPT_CLIENT\" 2>/dev/null\n\trm -f \"$SOCK\"\n}\ntrap cleanup EXIT\ncleanup\n\nktap_header\nktap_plan 2\n\ncat /sys/module/$MODULE/refcnt > /dev/null 2>&1 || {\n\techo \"# SKIP: $MODULE not loaded\"\n\tktap_totals\n\texit 0\n}\n\nmark_dmesg\nlunatik spawn \"$SCRIPT_SERVER\"\nsleep $SLEEP\n\nrun_script \"$SCRIPT_CLIENT\"\nsleep $SLEEP\n\nlunatik stop \"$SCRIPT_SERVER\" 2>/dev/null\ncheck_dmesg || { ktap_totals; exit 1; }\n\nfound=$(dmesg | tail -n +$((DMESG_LINE+1)) | grep \"unix dgram: server ok\" || true)\n[ -z \"$found\" ] && fail \"server did not receive expected datagram\"\nktap_pass \"unix.dgram server: receivefrom with DONTWAIT\"\n\nfound=$(dmesg | tail -n +$((DMESG_LINE+1)) | grep \"unix dgram: client ok\" || true)\n[ -z \"$found\" ] && fail \"client did not complete successfully\"\nktap_pass \"unix.dgram client: sendto using stored path (no explicit address)\"\n\nktap_totals\n\n"
  },
  {
    "path": "tests/socket/unix/dgram_client.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- DGRAM client for the socket.unix DGRAM test (see dgram.sh).\n-- Creates a socket with the server path stored at construction, then calls\n-- sendto() without an explicit path to exercise the stored-path feature.\n\nlocal unix = require(\"socket.unix\")\n\nlocal SERVER_PATH = \"/tmp/lunatik_unix_dgram.sock\"\nlocal client      = unix.dgram(SERVER_PATH)\n\nclient:sendto(\"hello dgram\")   -- uses stored SERVER_PATH, no explicit path\nclient:close()\nprint(\"unix dgram: client ok\")\n\n"
  },
  {
    "path": "tests/socket/unix/dgram_server.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- DGRAM server for the socket.unix DGRAM test (see dgram.sh).\n-- Binds using the path stored at construction, receives one datagram via a\n-- DONTWAIT loop, asserts the expected message.\n\nlocal unix   = require(\"socket.unix\")\nlocal socket = require(\"socket\")\nlocal thread = require(\"thread\")\nlocal linux  = require(\"linux\")\n\nlocal DONTWAIT = socket.msg.DONTWAIT\nlocal PATH     = \"/tmp/lunatik_unix_dgram.sock\"\n\nlocal server = unix.dgram(PATH)\nserver:bind()\n\nreturn function()\n\twhile not thread.shouldstop() do\n\t\tlocal ok, msg = pcall(server.receivefrom, server, 64, DONTWAIT)\n\t\tif ok then\n\t\t\tassert(msg == \"hello dgram\", \"expected 'hello dgram', got: \" .. tostring(msg))\n\t\t\tserver:close()\n\t\t\tprint(\"unix dgram: server ok\")\n\t\t\twhile not thread.shouldstop() do\n\t\t\t\tlinux.schedule(10)\n\t\t\tend\n\t\t\treturn\n\t\telseif msg == \"EAGAIN\" then\n\t\t\tlinux.schedule(10)\n\t\telse\n\t\t\terror(msg)\n\t\tend\n\tend\n\tserver:close()\nend\n\n"
  },
  {
    "path": "tests/socket/unix/run.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Runs all socket/unix tests and reports aggregated KTAP results.\n#\n# Usage: sudo bash tests/socket/unix/run.sh\n\nDIR=\"$(dirname \"$(readlink -f \"$0\")\")\"\nFAILED=0\n\nSEP=\"\"\nfor t in \"$DIR\"/stream.sh \"$DIR\"/dgram.sh; do\n\techo \"${SEP}# --- $(basename \"$t\") ---\"\n\tSEP=$'\\n'\n\tbash \"$t\" || FAILED=$((FAILED+1))\ndone\n\nexit $FAILED\n\n"
  },
  {
    "path": "tests/socket/unix/stream.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Tests socket.unix STREAM: bind/listen/accept on the server side and\n# connect/send/receive on the client side, both using the path stored at\n# construction time (no explicit path passed to bind() or connect()).\n#\n# Usage: sudo bash tests/socket/unix/stream.sh\n\nSCRIPT_SERVER=\"tests/socket/unix/stream_server\"\nSCRIPT_CLIENT=\"tests/socket/unix/stream_client\"\nSOCK=\"/tmp/lunatik_unix_stream.sock\"\nMODULE=\"luasocket\"\nSLEEP=1\n\nsource \"$(dirname \"$(readlink -f \"$0\")\")/../../lib.sh\"\n\ncleanup() {\n\tlunatik stop \"$SCRIPT_SERVER\" 2>/dev/null\n\tlunatik stop \"$SCRIPT_CLIENT\" 2>/dev/null\n\trm -f \"$SOCK\"\n}\ntrap cleanup EXIT\ncleanup\n\nktap_header\nktap_plan 2\n\ncat /sys/module/$MODULE/refcnt > /dev/null 2>&1 || {\n\techo \"# SKIP: $MODULE not loaded\"\n\tktap_totals\n\texit 0\n}\n\nmark_dmesg\nlunatik spawn \"$SCRIPT_SERVER\"\nsleep $SLEEP\n\nrun_script \"$SCRIPT_CLIENT\"\nsleep $SLEEP\n\nlunatik stop \"$SCRIPT_SERVER\" 2>/dev/null\ncheck_dmesg || { ktap_totals; exit 1; }\n\nfound=$(dmesg | tail -n +$((DMESG_LINE+1)) | grep \"unix stream: server ok\" || true)\n[ -z \"$found\" ] && fail \"server did not receive expected message\"\nktap_pass \"unix.stream server: bind/listen/accept via stored path\"\n\nfound=$(dmesg | tail -n +$((DMESG_LINE+1)) | grep \"unix stream: client ok\" || true)\n[ -z \"$found\" ] && fail \"client did not complete successfully\"\nktap_pass \"unix.stream client: connect/send/receive via stored path\"\n\nktap_totals\n\n"
  },
  {
    "path": "tests/socket/unix/stream_client.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- STREAM client for the socket.unix STREAM test (see stream.sh).\n-- Connects using the path stored at construction (no explicit path to connect()),\n-- sends \"ping\", asserts \"pong\" reply.\n\nlocal unix = require(\"socket.unix\")\n\nlocal PATH   = \"/tmp/lunatik_unix_stream.sock\"\nlocal client = unix.stream(PATH)\n\nclient:connect()         -- uses stored PATH\nclient:send(\"ping\")\n\nlocal reply = client:receive(64)\nassert(reply == \"pong\", \"expected 'pong', got: \" .. tostring(reply))\n\nclient:close()\nprint(\"unix stream: client ok\")\n\n"
  },
  {
    "path": "tests/socket/unix/stream_server.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- STREAM server for the socket.unix STREAM test (see stream.sh).\n-- Binds and listens using the path stored at construction, accepts one\n-- connection via a NONBLOCK loop, receives \"ping\", replies \"pong\".\n\nlocal unix   = require(\"socket.unix\")\nlocal socket = require(\"socket\")\nlocal thread = require(\"thread\")\nlocal linux  = require(\"linux\")\n\nlocal NONBLOCK = socket.sock.NONBLOCK\nlocal PATH     = \"/tmp/lunatik_unix_stream.sock\"\n\nlocal server = unix.stream(PATH)\nserver:bind()\nserver:listen(1)\n\nreturn function()\n\twhile not thread.shouldstop() do\n\t\tlocal ok, session = pcall(server.accept, server, NONBLOCK)\n\t\tif ok then\n\t\t\tlocal msg = session:receive(64)\n\t\t\tassert(msg == \"ping\", \"expected 'ping', got: \" .. tostring(msg))\n\t\t\tsession:send(\"pong\")\n\t\t\tsession:close()\n\t\t\tserver:close()\n\t\t\tprint(\"unix stream: server ok\")\n\t\t\twhile not thread.shouldstop() do\n\t\t\t\tlinux.schedule(10)\n\t\t\tend\n\t\t\treturn\n\t\telseif session == \"EAGAIN\" then\n\t\t\tlinux.schedule(10)\n\t\telse\n\t\t\terror(session)\n\t\tend\n\tend\n\tserver:close()\nend\n\n"
  },
  {
    "path": "tests/thread/dummy.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Minimal kthread body used as spawn target in tests.\n--\n\nlocal thread = require(\"thread\")\nlocal linux  = require(\"linux\")\n\nreturn function()\n\twhile not thread.shouldstop() do\n\t\tlinux.schedule(100)\n\tend\nend\n\n"
  },
  {
    "path": "tests/thread/run.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Runs all thread tests and reports aggregated KTAP results.\n#\n# Usage: sudo bash tests/thread/run.sh\n\nDIR=\"$(dirname \"$(readlink -f \"$0\")\")\"\nFAILED=0\n\nSEP=\"\"\nfor t in \"$DIR\"/shouldstop.sh \"$DIR\"/run_during_load.sh; do\n\techo \"${SEP}# --- $(basename \"$t\") ---\"\n\tSEP=$'\\n'\n\tbash \"$t\" || FAILED=$((FAILED+1))\ndone\n\nexit $FAILED\n\n"
  },
  {
    "path": "tests/thread/run_during_load.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Kernel-side script for the spawn-during-module-load regression test (see run_during_load.sh).\n--\n\nlocal runner = require(\"lunatik.runner\")\n\nrunner.spawn(\"tests/thread/dummy\")\n\n"
  },
  {
    "path": "tests/thread/run_during_load.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Regression test: calling runner.spawn() during module load must not hang.\n#\n# Prior to the fix, thread.run() (invoked by runner.spawn) called kthread_run()\n# which blocks in wait_for_completion() during script load, hanging the system.\n# The fix guards thread.run() with lunatik_isready() and returns an error\n# instead.\n#\n# Usage: sudo bash tests/thread/run_during_load.sh\n\nSCRIPT=\"tests/thread/run_during_load\"\nTIMEOUT=5\n\nsource \"$(dirname \"$(readlink -f \"$0\")\")/../lib.sh\"\n\ncleanup() {\n\tlunatik stop \"$SCRIPT\"          2>/dev/null\n\tlunatik stop \"tests/thread/dummy\" 2>/dev/null\n}\ntrap cleanup EXIT\ncleanup\n\nktap_header\nktap_plan 1\n\nmark_dmesg\noutput=$(timeout $TIMEOUT lunatik run \"$SCRIPT\" 2>&1)\n[ $? -eq 124 ] && fail \"thread.run() hung during module load\"\necho \"$output\" | sed 's/^/# (expected) /'\n\necho \"$output\" | grep -q \"not allowed during module load\" || \\\n\tfail \"expected 'not allowed during module load' error not found\"\nktap_pass \"runner.spawn() during module load returns error instead of hanging\"\n\nktap_totals\n\n"
  },
  {
    "path": "tests/thread/shouldstop.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Kernel-side kthread body for the shouldstop() spawn context test (see shouldstop.sh).\n--\n\nlocal thread = require(\"thread\")\nlocal linux  = require(\"linux\")\n\nreturn function()\n\twhile not thread.shouldstop() do\n\t\tlinux.schedule(10)\n\tend\n\tprint(\"shouldstop: ok\")\nend\n\n"
  },
  {
    "path": "tests/thread/shouldstop.sh",
    "content": "#!/bin/bash\n#\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# Regression test for thread.shouldstop() guard against non-kthread context.\n#\n# Case 1 (run): shouldstop() must return false without crashing.\n#   Prior to the fix, kthread_should_stop() dereferenced a NULL pointer for\n#   regular processes, panicking the kernel.\n#\n# Case 2 (spawn): shouldstop() must return true when stop is requested.\n#\n# Usage: sudo bash tests/thread/shouldstop.sh\n\nSCRIPT_RUN=\"tests/thread/shouldstop_run\"\nSCRIPT_SPAWN=\"tests/thread/shouldstop\"\nSLEEP=1\n\nsource \"$(dirname \"$(readlink -f \"$0\")\")/../lib.sh\"\n\ncleanup() {\n\tlunatik stop \"$SCRIPT_RUN\"   2>/dev/null\n\tlunatik stop \"$SCRIPT_SPAWN\" 2>/dev/null\n}\ntrap cleanup EXIT\ncleanup\n\nktap_header\nktap_plan 2\n\n# Case 1: run context — must return false and not crash\nmark_dmesg\nrun_script \"$SCRIPT_RUN\"\ncheck_dmesg || { ktap_totals; exit 1; }\nktap_pass \"shouldstop() returns false in run context\"\n\n# Case 2: spawn context — must return true when stopped\nmark_dmesg\nlunatik spawn \"$SCRIPT_SPAWN\"\nsleep $SLEEP\nlunatik stop \"$SCRIPT_SPAWN\"\ncheck_dmesg || { ktap_totals; exit 1; }\nfound=$(dmesg | tail -n +$((DMESG_LINE+1)) | grep \"shouldstop: ok\" || true)\n[ -z \"$found\" ] && fail \"shouldstop() did not return true in spawn context\"\nktap_pass \"shouldstop() returns true in spawn context\"\n\nktap_totals\n\n"
  },
  {
    "path": "tests/thread/shouldstop_run.lua",
    "content": "--\n-- SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n-- SPDX-License-Identifier: MIT OR GPL-2.0-only\n--\n-- Kernel-side script for the shouldstop() run context test (see shouldstop.sh).\n--\n\nlocal thread = require(\"thread\")\n\nassert(thread.shouldstop() == false)\n\n"
  },
  {
    "path": "tools/Readme.md",
    "content": "# Additional tools\n\n## debian_kernel_postinst_lunatik.sh\n\nUnder Debian, copy this script into /etc/kernel/postinst.d/\nto have Lunatik installed on kernel upgrade:\n\n```sh\nsudo cp debian_kernel_postinst_lunatik.sh /etc/kernel/postinst.d/zz-update-lunatik\nsudo chmod +x /etc/kernel/postinst.d/zz-update-lunatik\n```\n\nTo check it works:\n\n```sh\nsudo dpkg-reconfigure linux-image-`uname -r`\n```\n"
  },
  {
    "path": "tools/debian_kernel_postinst_lunatik.sh",
    "content": "#!/usr/bin/env bash\n\nLUNATIK_DIR=\"/opt/lunatik\"\nXDP_DIR=\"/opt/xdp-tools\"\nKERNEL_RELEASE=\"$1\"\n# This ugly hack is needed because Ubuntu has a weird kernel package versionning.\nKERNEL_VERSION=$([ -e /proc/version_signature ] && grep -o '[0-9.]*$' /proc/version_signature || echo $KERNEL_RELEASE | grep -o '^[0-9.]*')\nKERNEL_VERSION_MAJOR=$(echo \"${KERNEL_VERSION}\" | grep -o '^[0-9]*')\nCPU_CORES=$(grep -m1 'cpu cores' /proc/cpuinfo | grep -o '[0-9]*$' || echo 1)\necho \"Installing Lunatik for kernel version ${KERNEL_VERSION} release {$KERNEL_RELEASE}\"\n\necho \"Checking git linux-headers-${KERNEL_RELEASE} lua5.4 clang llvm libelf-dev libpcap-dev pahole are installed...\"\ndpkg --get-selections | grep 'git\\s' | grep install &&\\\ndpkg --get-selections | grep \"linux-headers-${KERNEL_RELEASE}\\s\" | grep install &&\\\n#dpkg --get-selections | grep 'linux-tools-generic\\s' | grep install &&\\\ndpkg --get-selections | grep 'lua5.4\\s' | grep install &&\\\ndpkg --get-selections | grep 'clang\\s' | grep install &&\\\ndpkg --get-selections | grep 'llvm\\s' | grep install &&\\\ndpkg --get-selections | grep 'libelf-dev' | grep install &&\\\ndpkg --get-selections | grep 'libpcap-dev' | grep install &&\\\ndpkg --get-selections | grep 'pahole\\s' | grep install || exit 1\ncp /sys/kernel/btf/vmlinux \"/usr/lib/modules/${KERNEL_RELEASE}/build/\" || exit 1\n\necho \"Compiling and installing resolve_btfids from kernel sources\"\ncd /usr/local/src &&\\\n[ -d linux-\"${KERNEL_VERSION}\" ] || wget -O- \"https://cdn.kernel.org/pub/linux/kernel/v${KERNEL_VERSION_MAJOR}.x/linux-${KERNEL_VERSION}.tar.xz\" | tar xJf - &&\\\ncd \"linux-${KERNEL_VERSION}\"/tools/bpf/resolve_btfids/ &&\\\nmake -j\"${CPU_CORES}\" &&\\\nmkdir -p \"/usr/src/linux-headers-${KERNEL_RELEASE}/tools/bpf/resolve_btfids/\" &&\\\ncp resolve_btfids \"/usr/src/linux-headers-${KERNEL_RELEASE}/tools/bpf/resolve_btfids/\" || exit 1\necho \"Compiling and installing bpftool\"\ncd ../bpftool &&\\\nmake -j\"${CPU_CORES}\" && make install &&\\\nmv /usr/sbin/bpftool /usr/sbin/bpftool.orig ; ln -s /usr/local/sbin/bpftool /usr/sbin/bpftool || exit 1\n\necho \"Compiling and installing Lunatik\"\nif [ -d \"${LUNATIK_DIR}\" ]; then\n  cd \"${LUNATIK_DIR}\" && git pull --ff-only\n  make clean\nelse\n  git clone --recurse-submodules https://github.com/luainkernel/lunatik \"${LUNATIK_DIR}\"\n  cd \"${LUNATIK_DIR}\"\nfi\nmake -j\"${CPU_CORES}\" KERNEL_RELEASE=\"${KERNEL_RELEASE}\" && make KERNEL_RELEASE=\"${KERNEL_RELEASE}\" install || exit 1\n\necho \"Compiling and installing xdp-loader\" &&\\\nif [ -d \"${XDP_DIR}\" ]; then\n  cd \"${XDP_DIR}\" && git pull --ff-only\n  make clean\nelse\n  git clone --recurse-submodules https://github.com/xdp-project/xdp-tools \"${XDP_DIR}\"\nfi\ncd \"${XDP_DIR}\"/lib/libbpf/src && make && sudo DESTDIR=/ make install &&\\\ncd ../../../ && make clean && make -j\"${CPU_CORES}\" libxdp &&\\\ncd xdp-loader && make && sudo make install || exit 1\n\nrm -r /usr/local/src/\"linux-${KERNEL_VERSION}\"\n"
  },
  {
    "path": "tools/shade.sh",
    "content": "#!/bin/bash\n# SPDX-FileCopyrightText: (c) 2026 Ring Zero Desenvolvimento de Software LTDA\n# SPDX-License-Identifier: MIT OR GPL-2.0-only\n#\n# shade.sh — encrypt/decrypt tooling for Lunatik darken\n#\n# Usage:\n#   shade.sh darken [-t] [-s secret] <script.lua>   encrypt a Lua script\n#   shade.sh lighten [-t] <secret>                  generate light.lua from a secret\n#\n#   -t  use time-step salt for ephemeral key derivation (OTP)\n#   -s  reuse an existing secret (darken only)\n\nset -euo pipefail\n\ndie() { echo \"error: $1\" >&2; exit 1; }\n\nhex2bin() { sed 's/../\\\\x&/g' | xargs -0 printf '%b'; }\n\nhkdf_sha256() {\n\tlocal secret=\"$1\"\n\tlocal salt=\"${2:-$(printf '%064x' 0)}\"\n\tlocal prk=$(echo -n \"$secret\" | hex2bin | openssl dgst -sha256 -mac HMAC \\\n\t\t-macopt \"hexkey:${salt}\" -hex 2>/dev/null | sed 's/.*= //')\n\n\tlocal info=$(printf 'lunatik-darken' | xxd -p | tr -d '\\n')\n\techo -n \"${info}01\" | hex2bin | openssl dgst -sha256 -mac HMAC \\\n\t\t-macopt \"hexkey:${prk}\" -hex 2>/dev/null | sed 's/.*= //'\n}\n\nderive_key() {\n\tlocal secret=\"$1\"\n\tif $OTP; then\n\t\tlocal salt=$(printf '%016x' \"$(( $(date +%s) / 30 ))\")\n\t\thkdf_sha256 \"$secret\" \"$salt\"\n\telse\n\t\thkdf_sha256 \"$secret\"\n\tfi\n}\n\ncmd_darken() {\n\t[ $# -eq 1 ] || die \"usage: shade.sh darken [-t] [-s secret] <script.lua>\"\n\t[ -f \"$1\" ] || die \"file not found: $1\"\n\n\tlocal script=\"$1\"\n\tlocal dark=\"${script%.lua}.dark.lua\"\n\n\tlocal secret=\"${SECRET:-$(openssl rand -hex 32)}\"\n\tlocal iv=$(openssl rand -hex 16)\n\tlocal key=$(derive_key \"$secret\")\n\n\tlocal ct=$(openssl enc -aes-256-ctr -K \"$key\" -iv \"$iv\" -nosalt \\\n\t\t-in \"$script\" | xxd -p | tr -d '\\n')\n\n\tcat > \"$dark\" <<-EOF\n\tlocal lighten = require(\"lighten\")\n\treturn lighten.run(\"${ct}\", \"${iv}\")\n\tEOF\n\n\techo \"$secret\"\n}\n\ncmd_lighten() {\n\t[ $# -ge 1 ] || die \"usage: shade.sh lighten [-t] <secret>\"\n\n\tlocal secret=\"$1\"\n\t[ ${#secret} -eq 64 ] || die \"secret must be 64 hex characters (32 bytes)\"\n\n\tlocal key=$(derive_key \"$secret\")\n\n\tcat > light.lua <<-EOF\n\treturn \"${key}\"\n\tEOF\n}\n\n[ $# -ge 1 ] || die \"usage: shade.sh {darken|lighten} [-t] ...\"\n\nCMD=\"$1\"; shift\n\nOTP=false\nSECRET=\"\"\nwhile getopts \"ts:\" opt; do\n\tcase $opt in\n\t\tt) OTP=true ;;\n\t\ts) SECRET=\"$OPTARG\" ;;\n\tesac\ndone\nshift $((OPTIND - 1))\n\ncase \"$CMD\" in\n\tdarken)  cmd_darken \"$@\" ;;\n\tlighten) cmd_lighten \"$@\" ;;\n\t*)       die \"unknown command: $CMD\" ;;\nesac\n\n"
  }
]