]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Feature] Backport pack/unpack routines from Lua 5.3
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 23 Aug 2016 14:38:54 +0000 (15:38 +0100)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 23 Aug 2016 14:38:54 +0000 (15:38 +0100)
src/lua/lua_util.c

index f0ae8ab1a4478aa917f1acd2145e18506ea3859e..a26982b6205061eabbe69c47489d8eb26af05ee9 100644 (file)
@@ -324,7 +324,7 @@ LUA_FUNCTION_DEF (util, create_file);
  */
 LUA_FUNCTION_DEF (util, close_file);
 
-/**
+/***
  * @function util.random_hex(size)
  * Returns random hex string of the specified size
  *
@@ -333,6 +333,79 @@ LUA_FUNCTION_DEF (util, close_file);
  */
 LUA_FUNCTION_DEF (util, random_hex);
 
+/***
+ * @function util.pack(fmt, ...)
+ *
+ * Backport of Lua 5.3 `string.pack` function:
+ * Returns a binary string containing the values v1, v2, etc. packed (that is,
+ * serialized in binary form) according to the format string `fmt`
+ * A format string is a sequence of conversion options. The conversion
+ * options are as follows:
+ *    * <: sets little endian
+ *    * >: sets big endian
+ *    * =: sets native endian
+ *    * ![n]: sets maximum alignment to n (default is native alignment)
+ *    * b: a signed byte (char)
+ *    * B: an unsigned byte (char)
+ *    * h: a signed short (native size)
+ *    * H: an unsigned short (native size)
+ *    * l: a signed long (native size)
+ *    * L: an unsigned long (native size)
+ *    * j: a lua_Integer
+ *    * J: a lua_Unsigned
+ *    * T: a size_t (native size)
+ *    * i[n]: a signed int with n bytes (default is native size)
+ *    * I[n]: an unsigned int with n bytes (default is native size)
+ *    * f: a float (native size)
+ *    * d: a double (native size)
+ *    * n: a lua_Number
+ *    * cn: a fixed-sized string with n bytes
+ *    * z: a zero-terminated string
+ *    * s[n]: a string preceded by its length coded as an unsigned integer with
+ *    * n bytes (default is a size_t)
+ *    * x: one byte of padding
+ *    * Xop: an empty item that aligns according to option op (which is otherwise ignored)
+ *    * ' ': (empty space) ignored
+ *
+ * (A "[n]" means an optional integral numeral.) Except for padding, spaces,
+ * and configurations (options "xX <=>!"), each option corresponds to an
+ * argument (in string.pack) or a result (in string.unpack).
+ *
+ * For options "!n", "sn", "in", and "In", n can be any integer between 1 and
+ * All integral options check overflows; string.pack checks whether the given
+ * value fits in the given size; string.unpack checks whether the read value
+ * fits in a Lua integer.
+ *
+ * Any format string starts as if prefixed by "!1=", that is, with maximum
+ * alignment of 1 (no alignment) and native endianness.
+ *
+ * Alignment works as follows: For each option, the format gets extra padding
+ * until the data starts at an offset that is a multiple of the minimum
+ * between the option size and the maximum alignment; this minimum must be a
+ * power of 2. Options "c" and "z" are not aligned; option "s" follows the
+ * alignment of its starting integer.
+ *
+ * All padding is filled with zeros by string.pack (and ignored by unpack).
+ */
+LUA_FUNCTION_DEF (util, pack);
+
+/***
+ * @function util.packsize(fmt)
+ *
+ * Returns size of the packed binary string returned for the same `fmt` argument
+ * by @see util.pack
+ */
+LUA_FUNCTION_DEF (util, packsize);
+
+/***
+ * @function util.unpack(fmt, s [, pos])
+ * Unpacks string `s` according to the format string `fmt` as described in
+ * @see util.pack
+ *
+ * @returns {multiple} list of unpacked values according to `fmt`
+ */
+LUA_FUNCTION_DEF (util, unpack);
+
 static const struct luaL_reg utillib_f[] = {
        LUA_INTERFACE_DEF (util, create_event_base),
        LUA_INTERFACE_DEF (util, load_rspamd_config),
@@ -368,6 +441,9 @@ static const struct luaL_reg utillib_f[] = {
        LUA_INTERFACE_DEF (util, create_file),
        LUA_INTERFACE_DEF (util, close_file),
        LUA_INTERFACE_DEF (util, random_hex),
+       LUA_INTERFACE_DEF (util, pack),
+       LUA_INTERFACE_DEF (util, unpack),
+       LUA_INTERFACE_DEF (util, packsize),
        {NULL, NULL}
 };
 
@@ -1500,6 +1576,718 @@ lua_util_random_hex (lua_State *L)
        return 1;
 }
 
+/* Backport from Lua 5.3 */
+
+/******************************************************************************
+* Copyright (C) 1994-2016 Lua.org, PUC-Rio.
+*
+* Permission is hereby granted, free of charge, to any person obtaining
+* a copy of this software and associated documentation files (the
+* "Software"), to deal in the Software without restriction, including
+* without limitation the rights to use, copy, modify, merge, publish,
+* distribute, sublicense, and/or sell copies of the Software, and to
+* permit persons to whom the Software is furnished to do so, subject to
+* the following conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+******************************************************************************/
+
+/*
+** {======================================================
+** PACK/UNPACK
+** =======================================================
+*/
+
+
+/* value used for padding */
+#if !defined(LUA_PACKPADBYTE)
+#define LUA_PACKPADBYTE        0x00
+#endif
+
+/* maximum size for the binary representation of an integer */
+#define MAXINTSIZE    16
+
+/* number of bits in a character */
+#define NB    CHAR_BIT
+
+/* mask for one character (NB 1's) */
+#define MC    ((1 << NB) - 1)
+
+/* size of a lua_Integer */
+#define SZINT    ((int)sizeof(lua_Integer))
+
+#define MAX_SIZET    ((size_t)(~(size_t)0))
+
+#define MAXSIZE  \
+       (sizeof(size_t) < sizeof(int) ? MAX_SIZET : (size_t)(INT_MAX))
+
+
+/* dummy union to get native endianness */
+static const union {
+       int dummy;
+       char little;  /* true if machine is little endian */
+} nativeendian = {1};
+
+
+/* dummy structure to get native alignment requirements */
+struct cD {
+       char c;
+       union {
+               double d;
+               void *p;
+               lua_Integer i;
+               lua_Number n;
+       } u;
+};
+
+#define MAXALIGN    (offsetof(struct cD, u))
+
+/*
+** Union for serializing floats
+*/
+typedef union Ftypes {
+       float f;
+       double d;
+       lua_Number n;
+       char buff[5 * sizeof (lua_Number)];  /* enough for any float type */
+} Ftypes;
+
+
+/*
+** information to pack/unpack stuff
+*/
+typedef struct Header {
+       lua_State *L;
+       int islittle;
+       int maxalign;
+} Header;
+
+/*
+** options for pack/unpack
+*/
+typedef enum KOption {
+       Kint,        /* signed integers */
+       Kuint,    /* unsigned integers */
+       Kfloat,    /* floating-point numbers */
+       Kchar,    /* fixed-length strings */
+       Kstring,    /* strings with prefixed length */
+       Kzstr,    /* zero-terminated strings */
+       Kpadding,    /* padding */
+       Kpaddalign,    /* padding for alignment */
+       Knop        /* no-op (configuration or spaces) */
+} KOption;
+
+#if LUA_VERSION_NUM < 503
+#define lua_Unsigned size_t
+
+typedef struct luaL_Buffer_53 {
+       luaL_Buffer b; /* make incorrect code crash! */
+       char *ptr;
+       size_t nelems;
+       size_t capacity;
+       lua_State *L2;
+} luaL_Buffer_53;
+
+#define luaL_Buffer luaL_Buffer_53
+#define COMPAT53_PREFIX lua
+#undef COMPAT53_API
+#if defined(__GNUC__) || defined(__clang__)
+# define COMPAT53_API __attribute__((__unused__)) static
+#else
+# define COMPAT53_API static
+#endif
+
+#define COMPAT53_CONCAT_HELPER(a, b) a##b
+#define COMPAT53_CONCAT(a, b) COMPAT53_CONCAT_HELPER(a, b)
+
+#define luaL_buffinit COMPAT53_CONCAT(COMPAT53_PREFIX, _buffinit_53)
+COMPAT53_API void luaL_buffinit (lua_State *L, luaL_Buffer_53 *B);
+#define luaL_prepbuffsize COMPAT53_CONCAT(COMPAT53_PREFIX, _prepbufsize_53)
+COMPAT53_API char *luaL_prepbuffsize (luaL_Buffer_53 *B, size_t s);
+#define luaL_addlstring COMPAT53_CONCAT(COMPAT53_PREFIX, _addlstring_53)
+COMPAT53_API void luaL_addlstring (luaL_Buffer_53 *B, const char *s, size_t l);
+#define luaL_addvalue COMPAT53_CONCAT(COMPAT53_PREFIX, _addvalue_53)
+COMPAT53_API void luaL_addvalue (luaL_Buffer_53 *B);
+#define luaL_pushresult COMPAT53_CONCAT(COMPAT53_PREFIX, _pushresult_53)
+COMPAT53_API void luaL_pushresult (luaL_Buffer_53 *B);
+#undef luaL_buffinitsize
+#define luaL_buffinitsize(L, B, s) \
+  (luaL_buffinit(L, B), luaL_prepbuffsize(B, s))
+
+#undef luaL_prepbuffer
+#define luaL_prepbuffer(B) \
+  luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
+
+#undef luaL_addchar
+#define luaL_addchar(B, c) \
+  ((void)((B)->nelems < (B)->capacity || luaL_prepbuffsize(B, 1)), \
+   ((B)->ptr[(B)->nelems++] = (c)))
+
+#undef luaL_addsize
+#define luaL_addsize(B, s) \
+  ((B)->nelems += (s))
+
+#undef luaL_addstring
+#define luaL_addstring(B, s) \
+  luaL_addlstring(B, s, strlen(s))
+
+#undef luaL_pushresultsize
+#define luaL_pushresultsize(B, s) \
+  (luaL_addsize(B, s), luaL_pushresult(B))
+
+COMPAT53_API void
+luaL_buffinit (lua_State *L, luaL_Buffer_53 *B)
+{
+       /* make it crash if used via pointer to a 5.1-style luaL_Buffer */
+       B->b.p = NULL;
+       B->b.L = NULL;
+       B->b.lvl = 0;
+       /* reuse the buffer from the 5.1-style luaL_Buffer though! */
+       B->ptr = B->b.buffer;
+       B->capacity = LUAL_BUFFERSIZE;
+       B->nelems = 0;
+       B->L2 = L;
+}
+
+
+COMPAT53_API char *
+luaL_prepbuffsize (luaL_Buffer_53 *B, size_t s)
+{
+       if (B->capacity - B->nelems < s) { /* needs to grow */
+               char *newptr = NULL;
+               size_t newcap = B->capacity * 2;
+               if (newcap - B->nelems < s)
+                       newcap = B->nelems + s;
+               if (newcap < B->capacity) /* overflow */
+                       luaL_error (B->L2, "buffer too large");
+               newptr = (char *) lua_newuserdata (B->L2, newcap);
+               memcpy(newptr, B->ptr, B->nelems);
+               if (B->ptr != B->b.buffer)
+                       lua_replace (B->L2, -2); /* remove old buffer */
+               B->ptr = newptr;
+               B->capacity = newcap;
+       }
+       return B->ptr + B->nelems;
+}
+
+
+COMPAT53_API void
+luaL_addlstring (luaL_Buffer_53 *B, const char *s, size_t l)
+{
+       memcpy(luaL_prepbuffsize (B, l), s, l);
+       luaL_addsize(B, l);
+}
+
+
+COMPAT53_API void
+luaL_addvalue (luaL_Buffer_53 *B)
+{
+       size_t len = 0;
+       const char *s = lua_tolstring (B->L2, -1, &len);
+       if (!s)
+               luaL_error (B->L2, "cannot convert value to string");
+       if (B->ptr != B->b.buffer)
+               lua_insert (B->L2, -2); /* userdata buffer must be at stack top */
+       luaL_addlstring (B, s, len);
+       lua_remove (B->L2, B->ptr != B->b.buffer ? -2 : -1);
+}
+
+
+COMPAT53_API void
+luaL_pushresult (luaL_Buffer_53 *B)
+{
+       lua_pushlstring (B->L2, B->ptr, B->nelems);
+       if (B->ptr != B->b.buffer)
+               lua_replace (B->L2, -2); /* remove userdata buffer */
+}
+
+#endif
+
+/*
+** Read an integer numeral from string 'fmt' or return 'df' if
+** there is no numeral
+*/
+static int
+digit (int c)
+{
+       return '0' <= c && c <= '9';
+}
+
+static int
+getnum (const char **fmt, int df)
+{
+       if (!digit (**fmt))  /* no number? */
+               return df;  /* return default value */
+       else {
+               int a = 0;
+               do {
+                       a = a * 10 + (*((*fmt)++) - '0');
+               } while (digit (**fmt) && a <= ((int) MAXSIZE - 9) / 10);
+               return a;
+       }
+}
+
+
+/*
+** Read an integer numeral and raises an error if it is larger
+** than the maximum size for integers.
+*/
+static int
+getnumlimit (Header *h, const char **fmt, int df)
+{
+       int sz = getnum (fmt, df);
+       if (sz > MAXINTSIZE || sz <= 0)
+               luaL_error (h->L, "integral size (%d) out of limits [1,%d]",
+                               sz, MAXINTSIZE);
+       return sz;
+}
+
+
+/*
+** Initialize Header
+*/
+static void
+initheader (lua_State *L, Header *h)
+{
+       h->L = L;
+       h->islittle = nativeendian.little;
+       h->maxalign = 1;
+}
+
+
+/*
+** Read and classify next option. 'size' is filled with option's size.
+*/
+static KOption
+getoption (Header *h, const char **fmt, int *size)
+{
+       int opt = *((*fmt)++);
+       *size = 0;  /* default */
+       switch (opt) {
+       case 'b':
+               *size = sizeof (char);
+               return Kint;
+       case 'B':
+               *size = sizeof (char);
+               return Kuint;
+       case 'h':
+               *size = sizeof (short);
+               return Kint;
+       case 'H':
+               *size = sizeof (short);
+               return Kuint;
+       case 'l':
+               *size = sizeof (long);
+               return Kint;
+       case 'L':
+               *size = sizeof (long);
+               return Kuint;
+       case 'j':
+               *size = sizeof (lua_Integer);
+               return Kint;
+       case 'J':
+               *size = sizeof (lua_Integer);
+               return Kuint;
+       case 'T':
+               *size = sizeof (size_t);
+               return Kuint;
+       case 'f':
+               *size = sizeof (float);
+               return Kfloat;
+       case 'd':
+               *size = sizeof (double);
+               return Kfloat;
+       case 'n':
+               *size = sizeof (lua_Number);
+               return Kfloat;
+       case 'i':
+               *size = getnumlimit (h, fmt, sizeof (int));
+               return Kint;
+       case 'I':
+               *size = getnumlimit (h, fmt, sizeof (int));
+               return Kuint;
+       case 's':
+               *size = getnumlimit (h, fmt, sizeof (size_t));
+               return Kstring;
+       case 'c':
+               *size = getnum (fmt, -1);
+               if (*size == -1)
+                       luaL_error (h->L, "missing size for format option 'c'");
+               return Kchar;
+       case 'z':
+               return Kzstr;
+       case 'x':
+               *size = 1;
+               return Kpadding;
+       case 'X':
+               return Kpaddalign;
+       case ' ':
+               break;
+       case '<':
+               h->islittle = 1;
+               break;
+       case '>':
+               h->islittle = 0;
+               break;
+       case '=':
+               h->islittle = nativeendian.little;
+               break;
+       case '!':
+               h->maxalign = getnumlimit (h, fmt, MAXALIGN);
+               break;
+       default:
+               luaL_error (h->L, "invalid format option '%c'", opt);
+       }
+       return Knop;
+}
+
+
+/*
+** Read, classify, and fill other details about the next option.
+** 'psize' is filled with option's size, 'notoalign' with its
+** alignment requirements.
+** Local variable 'size' gets the size to be aligned. (Kpadal option
+** always gets its full alignment, other options are limited by
+** the maximum alignment ('maxalign'). Kchar option needs no alignment
+** despite its size.
+*/
+static KOption
+getdetails (Header *h, size_t totalsize,
+               const char **fmt, int *psize, int *ntoalign)
+{
+       KOption opt = getoption (h, fmt, psize);
+       int align = *psize;  /* usually, alignment follows size */
+       if (opt == Kpaddalign) {  /* 'X' gets alignment from following option */
+               if (**fmt == '\0' || getoption (h, fmt, &align) == Kchar || align == 0)
+                       luaL_argerror (h->L, 1, "invalid next option for option 'X'");
+       }
+       if (align <= 1 || opt == Kchar)  /* need no alignment? */
+               *ntoalign = 0;
+       else {
+               if (align > h->maxalign)  /* enforce maximum alignment */
+                       align = h->maxalign;
+               if ((align & (align - 1)) != 0)  /* is 'align' not a power of 2? */
+                       luaL_argerror (h->L, 1, "format asks for alignment not power of 2");
+               *ntoalign = (align - (int) (totalsize & (align - 1))) & (align - 1);
+       }
+       return opt;
+}
+
+
+/*
+** Pack integer 'n' with 'size' bytes and 'islittle' endianness.
+** The final 'if' handles the case when 'size' is larger than
+** the size of a Lua integer, correcting the extra sign-extension
+** bytes if necessary (by default they would be zeros).
+*/
+static void
+packint (luaL_Buffer *b, lua_Unsigned n,
+               int islittle, int size, int neg)
+{
+       char *buff = luaL_prepbuffsize (b, size);
+       int i;
+       buff[islittle ? 0 : size - 1] = (char) (n & MC);  /* first byte */
+       for (i = 1; i < size; i++) {
+               n >>= NB;
+               buff[islittle ? i : size - 1 - i] = (char) (n & MC);
+       }
+       if (neg && size > SZINT) {  /* negative number need sign extension? */
+               for (i = SZINT; i < size; i++)  /* correct extra bytes */
+                       buff[islittle ? i : size - 1 - i] = (char) MC;
+       }
+       luaL_addsize(b, size);  /* add result to buffer */
+}
+
+
+/*
+** Copy 'size' bytes from 'src' to 'dest', correcting endianness if
+** given 'islittle' is different from native endianness.
+*/
+static void
+copywithendian (volatile char *dest, volatile const char *src,
+               int size, int islittle)
+{
+       if (islittle == nativeendian.little) {
+               while (size-- != 0)
+                       *(dest++) = *(src++);
+       }
+       else {
+               dest += size - 1;
+               while (size-- != 0)
+                       *(dest--) = *(src++);
+       }
+}
+
+
+static int
+lua_util_pack (lua_State *L)
+{
+       luaL_Buffer b;
+       Header h;
+       const char *fmt = luaL_checkstring(L, 1);  /* format string */
+       int arg = 1;  /* current argument to pack */
+       size_t totalsize = 0;  /* accumulate total size of result */
+       initheader (L, &h);
+       lua_pushnil (L);  /* mark to separate arguments from string buffer */
+       luaL_buffinit (L, &b);
+
+       while (*fmt != '\0') {
+               int size, ntoalign;
+               KOption opt = getdetails (&h, totalsize, &fmt, &size, &ntoalign);
+               totalsize += ntoalign + size;
+               while (ntoalign-- > 0)
+                       luaL_addchar(&b, LUA_PACKPADBYTE);  /* fill alignment */
+               arg++;
+               switch (opt) {
+               case Kint: {  /* signed integers */
+                       lua_Integer n = luaL_checkinteger (L, arg);
+                       if (size < SZINT) {  /* need overflow check? */
+                               lua_Integer lim = (lua_Integer) 1 << ((size * NB) - 1);
+                               luaL_argcheck(L, -lim <= n && n < lim, arg, "integer overflow");
+                       }
+                       packint (&b, (lua_Unsigned) n, h.islittle, size, (n < 0));
+                       break;
+               }
+               case Kuint: {  /* unsigned integers */
+                       lua_Integer n = luaL_checkinteger (L, arg);
+                       if (size < SZINT)  /* need overflow check? */
+                               luaL_argcheck(L,
+                                               (lua_Unsigned) n < ((lua_Unsigned) 1 << (size * NB)),
+                                               arg,
+                                               "unsigned overflow");
+                       packint (&b, (lua_Unsigned) n, h.islittle, size, 0);
+                       break;
+               }
+               case Kfloat: {  /* floating-point options */
+                       volatile Ftypes u;
+                       char *buff = luaL_prepbuffsize (&b, size);
+                       lua_Number n = luaL_checknumber (L, arg);  /* get argument */
+                       if (size == sizeof (u.f))
+                               u.f = (float) n;  /* copy it into 'u' */
+                       else if (size == sizeof (u.d))
+                               u.d = (double) n;
+                       else
+                               u.n = n;
+                       /* move 'u' to final result, correcting endianness if needed */
+                       copywithendian (buff, u.buff, size, h.islittle);
+                       luaL_addsize(&b, size);
+                       break;
+               }
+               case Kchar: {  /* fixed-size string */
+                       size_t len;
+                       const char *s = luaL_checklstring (L, arg, &len);
+                       if ((size_t) size <=
+                                       len)  /* string larger than (or equal to) needed? */
+                               luaL_addlstring (&b,
+                                               s,
+                                               size);  /* truncate string to asked size */
+                       else {  /* string smaller than needed */
+                               luaL_addlstring (&b, s, len);  /* add it all */
+                               while (len++ < (size_t) size)  /* pad extra space */
+                                       luaL_addchar(&b, LUA_PACKPADBYTE);
+                       }
+                       break;
+               }
+               case Kstring: {  /* strings with length count */
+                       size_t len;
+                       const char *s = luaL_checklstring (L, arg, &len);
+                       luaL_argcheck(L, size >= (int) sizeof (size_t) ||
+                                       len < ((size_t) 1 << (size * NB)),
+                                       arg, "string length does not fit in given size");
+                       packint (&b,
+                                       (lua_Unsigned) len,
+                                       h.islittle,
+                                       size,
+                                       0);  /* pack length */
+                       luaL_addlstring (&b, s, len);
+                       totalsize += len;
+                       break;
+               }
+               case Kzstr: {  /* zero-terminated string */
+                       size_t len;
+                       const char *s = luaL_checklstring (L, arg, &len);
+                       luaL_argcheck(L, strlen (s) == len, arg, "string contains zeros");
+                       luaL_addlstring (&b, s, len);
+                       luaL_addchar(&b, '\0');  /* add zero at the end */
+                       totalsize += len + 1;
+                       break;
+               }
+               case Kpadding:
+                       luaL_addchar(&b, LUA_PACKPADBYTE);  /* FALLTHROUGH */
+               case Kpaddalign:
+               case Knop:
+                       arg--;  /* undo increment */
+                       break;
+               }
+       }
+       luaL_pushresult (&b);
+       return 1;
+}
+
+
+static int
+lua_util_packsize (lua_State *L)
+{
+       Header h;
+       const char *fmt = luaL_checkstring(L, 1);  /* format string */
+       size_t totalsize = 0;  /* accumulate total size of result */
+       initheader (L, &h);
+       while (*fmt != '\0') {
+               int size, ntoalign;
+               KOption opt = getdetails (&h, totalsize, &fmt, &size, &ntoalign);
+               size += ntoalign;  /* total space used by option */
+               luaL_argcheck(L, totalsize <= MAXSIZE - size, 1,
+                               "format result too large");
+               totalsize += size;
+               switch (opt) {
+               case Kstring:  /* strings with length count */
+               case Kzstr:    /* zero-terminated string */
+                       luaL_argerror (L, 1, "variable-length format");
+                       /* call never return, but to avoid warnings: *//* FALLTHROUGH */
+               default:
+                       break;
+               }
+       }
+       lua_pushinteger (L, (lua_Integer) totalsize);
+       return 1;
+}
+
+
+/*
+** Unpack an integer with 'size' bytes and 'islittle' endianness.
+** If size is smaller than the size of a Lua integer and integer
+** is signed, must do sign extension (propagating the sign to the
+** higher bits); if size is larger than the size of a Lua integer,
+** it must check the unread bytes to see whether they do not cause an
+** overflow.
+*/
+static lua_Integer
+unpackint (lua_State *L, const char *str,
+               int islittle, int size, int issigned)
+{
+       lua_Unsigned res = 0;
+       int i;
+       int limit = (size <= SZINT) ? size : SZINT;
+       for (i = limit - 1; i >= 0; i--) {
+               res <<= NB;
+               res |= (lua_Unsigned) (
+               unsigned char)str[islittle ? i : size - 1 - i];
+       }
+       if (size < SZINT) {  /* real size smaller than lua_Integer? */
+               if (issigned) {  /* needs sign extension? */
+                       lua_Unsigned mask = (lua_Unsigned) 1 << (size * NB - 1);
+                       res = ((res ^ mask) - mask);  /* do sign extension */
+               }
+       }
+       else if (size > SZINT) {  /* must check unread bytes */
+               int mask = (!issigned || (lua_Integer) res >= 0) ? 0 : MC;
+               for (i = limit; i < size; i++) {
+                       if ((unsigned char) str[islittle ? i : size - 1 - i] != mask)
+                               luaL_error (L,
+                                               "%d-byte integer does not fit into Lua Integer",
+                                               size);
+               }
+       }
+       return (lua_Integer) res;
+}
+
+static lua_Integer
+posrelat (lua_Integer pos, size_t len)
+{
+       if (pos >= 0)
+               return pos;
+       else if (0u - (size_t) pos > len)
+               return 0;
+       else
+               return (lua_Integer) len + pos + 1;
+}
+
+static int
+lua_util_unpack (lua_State *L)
+{
+       Header h;
+       const char *fmt = luaL_checkstring(L, 1);
+       size_t ld;
+       const char *data = luaL_checklstring (L, 2, &ld);
+       size_t pos = (size_t) posrelat (luaL_optinteger (L, 3, 1), ld) - 1;
+       int n = 0;  /* number of results */
+       luaL_argcheck(L, pos <= ld, 3, "initial position out of string");
+       initheader (L, &h);
+       while (*fmt != '\0') {
+               int size, ntoalign;
+               KOption opt = getdetails (&h, pos, &fmt, &size, &ntoalign);
+               if ((size_t) ntoalign + size > ~pos || pos + ntoalign + size > ld)
+                       luaL_argerror (L, 2, "data string too short");
+               pos += ntoalign;  /* skip alignment */
+               /* stack space for item + next position */
+               luaL_checkstack (L, 2, "too many results");
+               n++;
+               switch (opt) {
+               case Kint:
+               case Kuint: {
+                       lua_Integer res = unpackint (L, data + pos, h.islittle, size,
+                                       (opt == Kint));
+                       lua_pushinteger (L, res);
+                       break;
+               }
+               case Kfloat: {
+                       volatile Ftypes u;
+                       lua_Number num;
+                       copywithendian (u.buff, data + pos, size, h.islittle);
+                       if (size == sizeof (u.f))
+                               num = (lua_Number) u.f;
+                       else if (size == sizeof (u.d))
+                               num = (lua_Number) u.d;
+                       else
+                               num = u.n;
+                       lua_pushnumber (L, num);
+                       break;
+               }
+               case Kchar: {
+                       lua_pushlstring (L, data + pos, size);
+                       break;
+               }
+               case Kstring: {
+                       size_t len = (size_t) unpackint (L,
+                                       data + pos,
+                                       h.islittle,
+                                       size,
+                                       0);
+                       luaL_argcheck(L,
+                                       pos + len + size <= ld,
+                                       2,
+                                       "data string too short");
+                       lua_pushlstring (L, data + pos + size, len);
+                       pos += len;  /* skip string */
+                       break;
+               }
+               case Kzstr: {
+                       size_t len = (int) strlen (data + pos);
+                       lua_pushlstring (L, data + pos, len);
+                       pos += len + 1;  /* skip string plus final '\0' */
+                       break;
+               }
+               case Kpaddalign:
+               case Kpadding:
+               case Knop:
+                       n--;  /* undo increment */
+                       break;
+               }
+               pos += size;
+       }
+       lua_pushinteger (L, pos + 1);  /* next position */
+       return n + 1;
+}
+
 static gint
 lua_load_util (lua_State * L)
 {