/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│ │vi: set et ft=c ts=8 sw=8 fenc=utf-8 :vi│ └─────────────────────────────────────────────────────────────────────────────*/ #include "libc/fmt/conv.h" #include "libc/mem/mem.h" #include "libc/sock/sock.h" #include "libc/str/str.h" /* clang-format off */ /* $OpenBSD: tables.c,v 1.4 2017/08/17 19:27:09 tedu Exp $ */ /* tables.c - tables serialization code * * Copyright (c) 1990 The Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Vern Paxson. * * The United States Government has rights in this work pursuant * to contract no. DE-AC03-76SF00098 between the United States * Department of Energy and the University of California. * * This file is part of flex. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE. */ #include "flexdef.h" #include "tables.h" /** Convert size_t to t_flag. * @param n in {1,2,4} * @return YYTD_DATA*. */ #define BYTES2TFLAG(n)\ (((n) == sizeof(flex_int8_t))\ ? YYTD_DATA8\ :(((n)== sizeof(flex_int16_t))\ ? YYTD_DATA16\ : YYTD_DATA32)) /** Clear YYTD_DATA* bit flags * @return the flag with the YYTD_DATA* bits cleared */ #define TFLAGS_CLRDATA(flg) ((flg) & ~(YYTD_DATA8 | YYTD_DATA16 | YYTD_DATA32)) int yytbl_write32 (struct yytbl_writer *wr, flex_uint32_t v); int yytbl_write16 (struct yytbl_writer *wr, flex_uint16_t v); int yytbl_write8 (struct yytbl_writer *wr, flex_uint8_t v); int yytbl_writen (struct yytbl_writer *wr, void *v, flex_int32_t len); static flex_int32_t yytbl_data_geti (const struct yytbl_data *tbl, int i); /** Initialize the table writer. * @param wr an uninitialized writer * @param the output file * @return 0 on success */ int yytbl_writer_init (struct yytbl_writer *wr, FILE * out) { wr->out = out; wr->total_written = 0; return 0; } /** Initialize a table header. * @param th The uninitialized structure * @param version_str the version string * @param name the name of this table set */ int yytbl_hdr_init (struct yytbl_hdr *th, const char *version_str, const char *name) { memset (th, 0, sizeof (struct yytbl_hdr)); th->th_magic = YYTBL_MAGIC; th->th_hsize = 14 + strlen (version_str) + 1 + strlen (name) + 1; th->th_hsize += yypad64 (th->th_hsize); th->th_ssize = 0; // Not known at this point. th->th_flags = 0; th->th_version = copy_string (version_str); th->th_name = copy_string (name); return 0; } /** Allocate and initialize a table data structure. * @param tbl a pointer to an uninitialized table * @param id the table identifier * @return 0 on success */ int yytbl_data_init (struct yytbl_data *td, enum yytbl_id id) { memset (td, 0, sizeof (struct yytbl_data)); td->td_id = id; td->td_flags = YYTD_DATA32; return 0; } /** Clean up table and data array. * @param td will be destroyed * @return 0 on success */ int yytbl_data_destroy (struct yytbl_data *td) { free(td->td_data); td->td_data = 0; free (td); return 0; } /** Write enough padding to bring the file pointer to a 64-bit boundary. */ static int yytbl_write_pad64 (struct yytbl_writer *wr) { int pad, bwritten = 0; pad = yypad64 (wr->total_written); while (pad-- > 0) if (yytbl_write8 (wr, 0) < 0) return -1; else bwritten++; return bwritten; } /** write the header. * @param out the output stream * @param th table header to be written * @return -1 on error, or bytes written on success. */ int yytbl_hdr_fwrite (struct yytbl_writer *wr, const struct yytbl_hdr *th) { int sz, rv; int bwritten = 0; if (yytbl_write32 (wr, th->th_magic) < 0 || yytbl_write32 (wr, th->th_hsize) < 0) flex_die (_("th_magic|th_hsize write32 failed")); bwritten += 8; if (fgetpos (wr->out, &(wr->th_ssize_pos)) != 0) flex_die (_("fgetpos failed")); if (yytbl_write32 (wr, th->th_ssize) < 0 || yytbl_write16 (wr, th->th_flags) < 0) flex_die (_("th_ssize|th_flags write failed")); bwritten += 6; sz = strlen (th->th_version) + 1; if ((rv = yytbl_writen (wr, th->th_version, sz)) != sz) flex_die (_("th_version writen failed")); bwritten += rv; sz = strlen (th->th_name) + 1; if ((rv = yytbl_writen (wr, th->th_name, sz)) != sz) flex_die (_("th_name writen failed")); bwritten += rv; /* add padding */ if ((rv = yytbl_write_pad64 (wr)) < 0) flex_die (_("pad64 failed")); bwritten += rv; /* Sanity check */ if (bwritten != (int) th->th_hsize) flex_die (_("pad64 failed")); return bwritten; } /** Write this table. * @param out the file writer * @param td table data to be written * @return -1 on error, or bytes written on success. */ int yytbl_data_fwrite (struct yytbl_writer *wr, struct yytbl_data *td) { int rv; flex_int32_t bwritten = 0; flex_int32_t i, total_len; fpos_t pos; if ((rv = yytbl_write16 (wr, td->td_id)) < 0) return -1; bwritten += rv; if ((rv = yytbl_write16 (wr, td->td_flags)) < 0) return -1; bwritten += rv; if ((rv = yytbl_write32 (wr, td->td_hilen)) < 0) return -1; bwritten += rv; if ((rv = yytbl_write32 (wr, td->td_lolen)) < 0) return -1; bwritten += rv; total_len = yytbl_calc_total_len (td); for (i = 0; i < total_len; i++) { switch (YYTDFLAGS2BYTES (td->td_flags)) { case sizeof (flex_int8_t): rv = yytbl_write8 (wr, yytbl_data_geti (td, i)); break; case sizeof (flex_int16_t): rv = yytbl_write16 (wr, yytbl_data_geti (td, i)); break; case sizeof (flex_int32_t): rv = yytbl_write32 (wr, yytbl_data_geti (td, i)); break; default: flex_die (_("invalid td_flags detected")); } if (rv < 0) { flex_die (_("error while writing tables")); return -1; } bwritten += rv; } /* Sanity check */ if (bwritten != (int) (12 + total_len * YYTDFLAGS2BYTES (td->td_flags))) { flex_die (_("insanity detected")); return -1; } /* add padding */ if ((rv = yytbl_write_pad64 (wr)) < 0) { flex_die (_("pad64 failed")); return -1; } bwritten += rv; /* Now go back and update the th_hsize member */ if (fgetpos (wr->out, &pos) != 0 || fsetpos (wr->out, &(wr->th_ssize_pos)) != 0 || yytbl_write32 (wr, wr->total_written) < 0 || fsetpos (wr->out, &pos)) { flex_die (_("get|set|fwrite32 failed")); return -1; } else /* Don't count the int we just wrote. */ wr->total_written -= sizeof (flex_int32_t); return bwritten; } /** Write n bytes. * @param wr the table writer * @param v data to be written * @param len number of bytes * @return -1 on error. number of bytes written on success. */ int yytbl_writen (struct yytbl_writer *wr, void *v, flex_int32_t len) { int rv; rv = fwrite (v, 1, len, wr->out); if (rv != len) return -1; wr->total_written += len; return len; } /** Write four bytes in network byte order * @param wr the table writer * @param v a dword in host byte order * @return -1 on error. number of bytes written on success. */ int yytbl_write32 (struct yytbl_writer *wr, flex_uint32_t v) { flex_uint32_t vnet; size_t bytes, rv; vnet = htonl (v); bytes = sizeof (flex_uint32_t); rv = fwrite (&vnet, bytes, 1, wr->out); if (rv != 1) return -1; wr->total_written += bytes; return bytes; } /** Write two bytes in network byte order. * @param wr the table writer * @param v a word in host byte order * @return -1 on error. number of bytes written on success. */ int yytbl_write16 (struct yytbl_writer *wr, flex_uint16_t v) { flex_uint16_t vnet; size_t bytes, rv; vnet = htons (v); bytes = sizeof (flex_uint16_t); rv = fwrite (&vnet, bytes, 1, wr->out); if (rv != 1) return -1; wr->total_written += bytes; return bytes; } /** Write a byte. * @param wr the table writer * @param v the value to be written * @return -1 on error. number of bytes written on success. */ int yytbl_write8 (struct yytbl_writer *wr, flex_uint8_t v) { size_t bytes, rv; bytes = sizeof (flex_uint8_t); rv = fwrite (&v, bytes, 1, wr->out); if (rv != 1) return -1; wr->total_written += bytes; return bytes; } /** Extract data element [i] from array data tables treated as a single flat array of integers. * Be careful for 2-dimensional arrays or for YYTD_ID_TRANSITION, which is an array * of structs. * @param tbl data table * @param i index into array. * @return data[i] */ static flex_int32_t yytbl_data_geti (const struct yytbl_data *tbl, int i) { switch (YYTDFLAGS2BYTES (tbl->td_flags)) { case sizeof (flex_int8_t): return ((flex_int8_t *) (tbl->td_data))[i]; case sizeof (flex_int16_t): return ((flex_int16_t *) (tbl->td_data))[i]; case sizeof (flex_int32_t): return ((flex_int32_t *) (tbl->td_data))[i]; default: flex_die (_("invalid td_flags detected")); break; } return 0; } /** Set data element [i] in array data tables treated as a single flat array of integers. * Be careful for 2-dimensional arrays or for YYTD_ID_TRANSITION, which is an array * of structs. * @param tbl data table * @param i index into array. * @param newval new value for data[i] */ static void yytbl_data_seti (const struct yytbl_data *tbl, int i, flex_int32_t newval) { switch (YYTDFLAGS2BYTES (tbl->td_flags)) { case sizeof (flex_int8_t): ((flex_int8_t *) (tbl->td_data))[i] = (flex_int8_t) newval; break; case sizeof (flex_int16_t): ((flex_int16_t *) (tbl->td_data))[i] = (flex_int16_t) newval; break; case sizeof (flex_int32_t): ((flex_int32_t *) (tbl->td_data))[i] = (flex_int32_t) newval; break; default: flex_die (_("invalid td_flags detected")); break; } } /** Calculate the number of bytes needed to hold the largest * absolute value in this data array. * @param tbl the data table * @return sizeof(n) where n in {flex_int8_t, flex_int16_t, flex_int32_t} */ static size_t min_int_size (struct yytbl_data *tbl) { flex_uint32_t i, total_len; flex_int32_t max = 0; total_len = yytbl_calc_total_len (tbl); for (i = 0; i < total_len; i++) { flex_int32_t n; n = abs (yytbl_data_geti (tbl, i)); if (n > max) max = n; } if (max <= INT8_MAX) return sizeof (flex_int8_t); else if (max <= INT16_MAX) return sizeof (flex_int16_t); else return sizeof (flex_int32_t); } /** Transform data to smallest possible of (int32, int16, int8). * For example, we may have generated an int32 array due to user options * (e.g., %option align), but if the maximum value in that array * is 80 (for example), then we can serialize it with only 1 byte per int. * This is NOT the same as compressed DFA tables. We're just trying * to save storage space here. * * @param tbl the table to be compressed */ void yytbl_data_compress (struct yytbl_data *tbl) { flex_int32_t i, newsz, total_len; struct yytbl_data newtbl; yytbl_data_init (&newtbl, tbl->td_id); newtbl.td_hilen = tbl->td_hilen; newtbl.td_lolen = tbl->td_lolen; newtbl.td_flags = tbl->td_flags; newsz = min_int_size (tbl); if (newsz == (int) YYTDFLAGS2BYTES (tbl->td_flags)) /* No change in this table needed. */ return; if (newsz > (int) YYTDFLAGS2BYTES (tbl->td_flags)) { flex_die (_("detected negative compression")); return; } total_len = yytbl_calc_total_len (tbl); newtbl.td_data = calloc (total_len, newsz); newtbl.td_flags = TFLAGS_CLRDATA (newtbl.td_flags) | BYTES2TFLAG (newsz); for (i = 0; i < total_len; i++) { flex_int32_t g; g = yytbl_data_geti (tbl, i); yytbl_data_seti (&newtbl, i, g); } /* Now copy over the old table */ free (tbl->td_data); *tbl = newtbl; }