/*****************************************************************************\
 * layout_file.c
 *****************************************************************************
 *  Copyright (C) 2002-2005 The Regents of the University of California.
 *  Produced at the Lawrence Livermore National Laboratory.
 *  Written by Dave Peterson <dsp@llnl.gov> <dave_peterson@pobox.com>.
 *  UCRL-CODE-2003-012
 *  All rights reserved.
 *
 *  This file is part of nvramtool, a utility for reading/writing coreboot
 *  parameters and displaying information from the coreboot table.
 *  For details, see http://coreboot.org/nvramtool.
 *
 *  Please also read the file DISCLAIMER which is included in this software
 *  distribution.
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License (as published by the
 *  Free Software Foundation) version 2, dated June 1991.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the terms and
 *  conditions of the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
\*****************************************************************************/

#include "common.h"
#include "layout_file.h"
#include "layout.h"
#include "cmos_lowlevel.h"
#include "reg_expr.h"

static void process_layout_file (FILE *f);
static void skip_past_start (FILE *f);
static int process_entry (FILE *f, int skip_add);
static int process_enum (FILE *f, int skip_add);
static void process_checksum_info (FILE *f);
static void skip_remaining_lines (FILE *f);
static void create_entry (cmos_entry_t *cmos_entry,
                          const char start_bit_str[], const char length_str[],
                          const char config_str[], const char config_id_str[],
                          const char name_str[]);
static void try_add_layout_file_entry (const cmos_entry_t *cmos_entry);
static void create_enum (cmos_enum_t *cmos_enum, const char id_str[],
                         const char value_str[], const char text_str[]);
static void try_add_cmos_enum (const cmos_enum_t *cmos_enum);
static void set_checksum_info (const char start_str[], const char end_str[],
                               const char index_str[]);
static char cmos_entry_char_value (cmos_entry_config_t config);
static int get_layout_file_line (FILE *f, char line[], int line_buf_size);
static unsigned string_to_unsigned (const char str[], const char str_name[]);
static unsigned long string_to_unsigned_long (const char str[],
                                              const char str_name[]);
static unsigned long do_string_to_unsigned_long (const char str[],
                                                 const char str_name[],
                                                 const char blurb[]);

/* matches either a blank line or a comment line */
static const char blank_or_comment_regex[] =
   /* a blank line */
   "(^[[:space:]]+$)"

   "|"  /* or ... */

   /* a line consisting of: optional whitespace followed by */
   "(^[[:space:]]*"
   /* a '#' character and optionally, additional characters */
   "#.*$)";

static regex_t blank_or_comment_expr;

/* matches the line in a CMOS layout file indicating the start of the
 * "entries" section.
 */
static const char start_entries_regex[] =
   /* optional whitespace */
   "^[[:space:]]*"
   /* followed by "entries" */
   "entries"
   /* followed by optional whitespace */
   "[[:space:]]*$";

static regex_t start_entries_expr;

/* matches the line in a CMOS layout file indicating the start of the
 * "enumerations" section
 */
static const char start_enums_regex[] =
   /* optional whitespace */
   "^[[:space:]]*"
   /* followed by "enumerations" */
   "enumerations"
   /* followed by optional whitespace */
   "[[:space:]]*$";

static regex_t start_enums_expr;

/* matches the line in a CMOS layout file indicating the start of the
 * "checksums" section
 */
static const char start_checksums_regex[] =
   /* optional whitespace */
   "^[[:space:]]*"
   /* followed by "checksums" */
   "checksums"
   /* followed by optional whitespace */
   "[[:space:]]*$";

static regex_t start_checksums_expr;

/* matches a line in a CMOS layout file specifying a CMOS entry */
static const char entries_line_regex[] =
   /* optional whitespace */
   "^[[:space:]]*"
   /* followed by a chunk of nonwhitespace for start-bit field */
   "([^[:space:]]+)"
   /* followed by one or more whitespace characters */
   "[[:space:]]+"
   /* followed by a chunk of nonwhitespace for length field */
   "([^[:space:]]+)"
   /* followed by one or more whitespace characters */
   "[[:space:]]+"
   /* followed by a chunk of nonwhitespace for config field */
   "([^[:space:]]+)"
   /* followed by one or more whitespace characters */
   "[[:space:]]+"
   /* followed by a chunk of nonwhitespace for config-ID field */
   "([^[:space:]]+)"
   /* followed by one or more whitespace characters */
   "[[:space:]]+"
   /* followed by a chunk of nonwhitespace for name field */
   "([^[:space:]]+)"
   /* followed by optional whitespace */
   "[[:space:]]*$";

static regex_t entries_line_expr;

/* matches a line in a CMOS layout file specifying a CMOS enumeration */
static const char enums_line_regex[] =
   /* optional whitespace */
   "^[[:space:]]*"
   /* followed by a chunk of nonwhitespace for ID field */
   "([^[:space:]]+)"
   /* followed by one or more whitespace characters */
   "[[:space:]]+"
   /* followed by a chunk of nonwhitespace for value field */
   "([^[:space:]]+)"
   /* followed by one or more whitespace characters */
   "[[:space:]]+"
   /* followed by a chunk of nonwhitespace for text field */
   "([^[:space:]]+)"
   /* followed by optional whitespace */
   "[[:space:]]*$";

static regex_t enums_line_expr;

/* matches the line in a CMOS layout file specifying CMOS checksum
 * information
 */
static const char checksum_line_regex[] =
   /* optional whitespace */
   "^[[:space:]]*"
   /* followed by "checksum" */
   "checksum"
   /* followed by one or more whitespace characters */
   "[[:space:]]+"
   /* followed by a chunk of nonwhitespace for first bit of summed area */
   "([^[:space:]]+)"
   /* followed by one or more whitespace characters */
   "[[:space:]]+"
   /* followed by a chunk of nonwhitespace for last bit of summed area */
   "([^[:space:]]+)"
   /* followed by one or more whitespace characters */
   "[[:space:]]+"
   /* followed by a chunk of nonwhitespace for checksum location bit */
   "([^[:space:]]+)"
   /* followed by optional whitespace */
   "[[:space:]]*$";

static regex_t checksum_line_expr;

static const int LINE_BUF_SIZE = 256;

static int line_num;

static const char *layout_filename = NULL;

/****************************************************************************
 * set_layout_filename
 *
 * Set the name of the file we will obtain CMOS layout information from.
 ****************************************************************************/
void set_layout_filename (const char filename[])
 { layout_filename = filename; }

/****************************************************************************
 * get_layout_from_file
 *
 * Read CMOS layout information from the user-specified CMOS layout file.
 ****************************************************************************/
void get_layout_from_file (void)
 { FILE *f;

   assert(layout_filename != NULL);

   if ((f = fopen(layout_filename, "r")) == NULL)
    { fprintf(stderr, "%s: Can not open CMOS layout file %s for reading: "
              "%s\n", prog_name, layout_filename, strerror(errno));
      exit(1);
    }

   process_layout_file(f);
   fclose(f);
 }

/****************************************************************************
 * write_cmos_layout
 *
 * Write CMOS layout information to file 'f'.  The output is written in the
 * format that CMOS layout files adhere to.
 ****************************************************************************/
void write_cmos_layout (FILE *f)
 { const cmos_entry_t *cmos_entry;
   const cmos_enum_t *cmos_enum;
   cmos_checksum_layout_t layout;

   fprintf(f, "entries\n");

   for (cmos_entry = first_cmos_entry();
        cmos_entry != NULL;
        cmos_entry = next_cmos_entry(cmos_entry))
      fprintf(f, "%u %u %c %u %s\n", cmos_entry->bit, cmos_entry->length,
              cmos_entry_char_value(cmos_entry->config),
              cmos_entry->config_id, cmos_entry->name);

   fprintf(f, "\nenumerations\n");

   for (cmos_enum = first_cmos_enum();
        cmos_enum != NULL;
        cmos_enum = next_cmos_enum(cmos_enum))
      fprintf(f, "%u %llu %s\n", cmos_enum->config_id, cmos_enum->value,
              cmos_enum->text);

   layout.summed_area_start = cmos_checksum_start;
   layout.summed_area_end = cmos_checksum_end;
   layout.checksum_at = cmos_checksum_index;
   checksum_layout_to_bits(&layout);
   fprintf(f, "\nchecksums\nchecksum %u %u %u\n", layout.summed_area_start,
           layout.summed_area_end, layout.checksum_at);
 }

/****************************************************************************
 * process_layout_file
 *
 * Read CMOS layout information from file 'f' and add it to our internal
 * repository.
 ****************************************************************************/
static void process_layout_file (FILE *f)
 { compile_reg_exprs(REG_EXTENDED | REG_NEWLINE, 7,
                     blank_or_comment_regex, &blank_or_comment_expr,
                     start_entries_regex, &start_entries_expr,
                     entries_line_regex, &entries_line_expr,
                     start_enums_regex, &start_enums_expr,
                     enums_line_regex, &enums_line_expr,
                     start_checksums_regex, &start_checksums_expr,
                     checksum_line_regex, &checksum_line_expr);
   line_num = 1;
   skip_past_start(f);

   /* Skip past all entries.  We will process these later when we make a
    * second pass through the file.
    */
   while (!process_entry(f, 1));

   /* Process all enums, adding them to our internal repository as we go. */

   if (process_enum(f, 0))
    { fprintf(stderr, "%s: Error: CMOS layout file contains no "
              "enumerations.\n", prog_name);
      exit(1);
    }

   while (!process_enum(f, 0));

   /* Go back to start of file. */
   line_num = 1;
   fseek(f, 0, SEEK_SET);

   skip_past_start(f);

   /* Process all entries, adding them to the repository as we go.  We must
    * add the entries after the enums, even though they appear in the layout
    * file before the enums.  This is because the entries are sanity checked
    * against the enums as they are added.
    */

   if (process_entry(f, 0))
    { fprintf(stderr, "%s: Error: CMOS layout file contains no entries.\n",
              prog_name);
      exit(1);
    }

   while (!process_entry(f, 0));

   /* Skip past all enums.  They have already been processed. */
   while (!process_enum(f, 1));

   /* Process CMOS checksum info. */
   process_checksum_info(f);

   /* See if there are any lines left to process.  If so, verify that they are
    * all either blank lines or comments.
    */
   skip_remaining_lines(f);

   free_reg_exprs(7, &blank_or_comment_expr, &start_entries_expr,
                  &entries_line_expr, &start_enums_expr,
                  &enums_line_expr, &start_checksums_expr,
                  &checksum_line_expr);
 }

/****************************************************************************
 * skip_past_start
 *
 * Skip past the line that marks the start of the "entries" section.
 ****************************************************************************/
static void skip_past_start (FILE *f)
 { char line[LINE_BUF_SIZE];

   for (; ; line_num++)
    { if (get_layout_file_line(f, line, LINE_BUF_SIZE))
       { fprintf(stderr,
                 "%s: \"entries\" line not found in CMOS layout file.\n",
                 prog_name);
         exit(1);
       }

      if (!regexec(&blank_or_comment_expr, line, 0, NULL, 0))
         continue;

      if (!regexec(&start_entries_expr, line, 0, NULL, 0))
         break;

      fprintf(stderr, "%s: Syntax error on line %d of CMOS layout file.  "
              "\"entries\" line expected.\n", prog_name, line_num);
      exit(1);
    }

   line_num++;
 }

/****************************************************************************
 * process_entry
 *
 * Get an entry from "entries" section of file and add it to our repository
 * of layout information.  Return 0 if an entry was found and processed.
 * Return 1 if there are no more entries.
 ****************************************************************************/
static int process_entry (FILE *f, int skip_add)
 { static const size_t N_MATCHES = 6;
   char line[LINE_BUF_SIZE];
   regmatch_t match[N_MATCHES];
   cmos_entry_t cmos_entry;
   int result;

   result = 1;

   for (; ; line_num++)
    { if (get_layout_file_line(f, line, LINE_BUF_SIZE))
       { fprintf(stderr,
                 "%s: Unexpected end of CMOS layout file reached while "
                 "reading \"entries\" section.\n", prog_name);
         exit(1);
       }

      if (!regexec(&blank_or_comment_expr, line, 0, NULL, 0))
         continue;

      if (regexec(&entries_line_expr, line, N_MATCHES, match, 0))
       { if (regexec(&start_enums_expr, line, 0, NULL, 0))
          { fprintf(stderr, "%s: Syntax error on line %d of CMOS layout "
                    "file.\n", prog_name, line_num);
            exit(1);
          }

         break;  /* start of enumerations reached: no more entries */
       }

      result = 0;  /* next layout entry found */

      if (skip_add)
         break;

      line[match[1].rm_eo] = '\0';
      line[match[2].rm_eo] = '\0';
      line[match[3].rm_eo] = '\0';
      line[match[4].rm_eo] = '\0';
      line[match[5].rm_eo] = '\0';
      create_entry(&cmos_entry, &line[match[1].rm_so], &line[match[2].rm_so],
                   &line[match[3].rm_so], &line[match[4].rm_so],
                   &line[match[5].rm_so]);
      try_add_layout_file_entry(&cmos_entry);
      break;
    }

   line_num++;
   return result;
 }

/****************************************************************************
 * process_enum
 *
 * Get an enuneration from "enumerations" section of file and add it to our
 * repository of layout information.  Return 0 if an enumeration was found
 * and processed.  Return 1 if there are no more enumerations.
 ****************************************************************************/
static int process_enum (FILE *f, int skip_add)
 { static const size_t N_MATCHES = 4;
   char line[LINE_BUF_SIZE];
   regmatch_t match[N_MATCHES];
   cmos_enum_t cmos_enum;
   int result;

   result = 1;

   for (; ; line_num++)
    { if (get_layout_file_line(f, line, LINE_BUF_SIZE))
       { fprintf(stderr,
                 "%s: Unexpected end of CMOS layout file reached while "
                 "reading \"enumerations\" section.\n", prog_name);
         exit(1);
       }

      if (!regexec(&blank_or_comment_expr, line, 0, NULL, 0))
         continue;

      if (regexec(&enums_line_expr, line, N_MATCHES, match, 0))
       { if (regexec(&start_checksums_expr, line, 0, NULL, 0))
          { fprintf(stderr, "%s: Syntax error on line %d of CMOS layout "
                    "file.\n", prog_name, line_num);
            exit(1);
          }

         break;  /* start of checksums reached: no more enumerations */
       }

      result = 0;  /* next layout enumeration found */

      if (skip_add)
         break;

      line[match[1].rm_eo] = '\0';
      line[match[2].rm_eo] = '\0';
      line[match[3].rm_eo] = '\0';
      create_enum(&cmos_enum, &line[match[1].rm_so], &line[match[2].rm_so],
                  &line[match[3].rm_so]);
      try_add_cmos_enum(&cmos_enum);
      break;
    }

   line_num++;
   return result;
 }

/****************************************************************************
 * process_checksum_info
 *
 * Get line conatining CMOS checksum information.
 ****************************************************************************/
static void process_checksum_info (FILE *f)
 { static const size_t N_MATCHES = 4;
   char line[LINE_BUF_SIZE];
   regmatch_t match[N_MATCHES];

   for (; ; line_num++)
    { if (get_layout_file_line(f, line, LINE_BUF_SIZE))
       { fprintf(stderr,
                 "%s: Unexpected end of CMOS layout file reached while "
                 "reading \"checksums\" section.\n", prog_name);
         exit(1);
       }

      if (!regexec(&blank_or_comment_expr, line, 0, NULL, 0))
         continue;

      if (regexec(&checksum_line_expr, line, N_MATCHES, match, 0))
       { fprintf(stderr, "%s: Syntax error on line %d of CMOS layout "
                 "file.  \"checksum\" line expected.\n", prog_name,
                 line_num);
         exit(1);
       }

      /* checksum line found */
      line[match[1].rm_eo] = '\0';
      line[match[2].rm_eo] = '\0';
      line[match[3].rm_eo] = '\0';
      set_checksum_info(&line[match[1].rm_so], &line[match[2].rm_so],
                        &line[match[3].rm_so]);
      break;
    }
 }

/****************************************************************************
 * skip_remaining_lines
 *
 * Get any remaining lines of unprocessed input.  Complain if we find a line
 * that contains anything other than comments and whitespace.
 ****************************************************************************/
static void skip_remaining_lines (FILE *f)
 { char line[LINE_BUF_SIZE];

   for (line_num++;
        get_layout_file_line(f, line, LINE_BUF_SIZE) == OK;
        line_num++)
    { if (regexec(&blank_or_comment_expr, line, 0, NULL, 0))
       { fprintf(stderr, "%s: Syntax error on line %d of CMOS layout file: "
                 "Only comments and/or whitespace allowed after "
                 "\"checksum\" line.\n", prog_name, line_num);
         exit(1);
       }
    }
 }

/****************************************************************************
 * create_entry
 *
 * Create a CMOS entry structure representing the given information.  Perform
 * sanity checking on input parameters.
 ****************************************************************************/
static void create_entry (cmos_entry_t *cmos_entry,
                          const char start_bit_str[], const char length_str[],
                          const char config_str[], const char config_id_str[],
                          const char name_str[])
 { cmos_entry->bit = string_to_unsigned(start_bit_str, "start-bit");
   cmos_entry->length = string_to_unsigned(length_str, "length");

   if (config_str[1] != '\0')
      goto bad_config_str;

   switch (config_str[0])
    { case 'e':
         cmos_entry->config = CMOS_ENTRY_ENUM;
         break;

      case 'h':
         cmos_entry->config = CMOS_ENTRY_HEX;
         break;

      case 's':
	 cmos_entry->config = CMOS_ENTRY_STRING;
	 break;

      case 'r':
         cmos_entry->config = CMOS_ENTRY_RESERVED;
         break;

      default:
         goto bad_config_str;
    }

   cmos_entry->config_id = string_to_unsigned(config_id_str, "config-ID");

   if (strlen(name_str) >= CMOS_MAX_NAME_LENGTH)
    { fprintf(stderr, "%s: Error on line %d of CMOS layout file: name too "
              "long (max length is %d).\n", prog_name, line_num,
              CMOS_MAX_NAME_LENGTH - 1);
      exit(1);
    }

   strcpy(cmos_entry->name, name_str);
   return;

bad_config_str:
   fprintf(stderr, "%s: Error on line %d of CMOS layout file: 'e', 'h', or "
           "'r' expected for config value.\n", prog_name, line_num);
   exit(1);
 }

/****************************************************************************
 * try_add_layout_file_entry
 *
 * Attempt to add the given CMOS entry to our internal repository.  Exit with
 * an error message on failure.
 ****************************************************************************/
static void try_add_layout_file_entry (const cmos_entry_t *cmos_entry)
 { const cmos_entry_t *conflict;

   switch (add_cmos_entry(cmos_entry, &conflict))
    { case OK:
         return;

      case CMOS_AREA_OUT_OF_RANGE:
         fprintf(stderr, "%s: Error on line %d of CMOS layout file.  Area "
                 "covered by entry %s is out of range.\n", prog_name,
                 line_num, cmos_entry->name);
         break;

      case CMOS_AREA_TOO_WIDE:
         fprintf(stderr, "%s: Error on line %d of CMOS layout file.  Area "
                 "covered by entry %s is too wide.\n", prog_name, line_num,
                 cmos_entry->name);
         break;

      case LAYOUT_ENTRY_OVERLAP:
         fprintf(stderr, "%s: Error on line %d of CMOS layout file.  Layouts "
                 "overlap for entries %s and %s.\n", prog_name, line_num,
                 cmos_entry->name, conflict->name);
         break;

      case LAYOUT_ENTRY_BAD_LENGTH:
         /* Silently ignore entries with zero length.  Although this should
          * never happen in practice, we should handle the case in a
          * reasonable manner just to be safe.
          */
         return;

      default:
         BUG();
    }

   exit(1);
 }

/****************************************************************************
 * create_enum
 *
 * Create a CMOS enumeration structure representing the given information.
 * Perform sanity checking on input parameters.
 ****************************************************************************/
static void create_enum (cmos_enum_t *cmos_enum, const char id_str[],
                         const char value_str[], const char text_str[])
 { cmos_enum->config_id = string_to_unsigned(id_str, "ID");
   cmos_enum->value = string_to_unsigned_long(value_str, "value");

   if (strlen(text_str) >= CMOS_MAX_TEXT_LENGTH)
    { fprintf(stderr, "%s: Error on line %d of CMOS layout file: text too "
              "long (max length is %d).\n", prog_name, line_num,
              CMOS_MAX_TEXT_LENGTH - 1);
      exit(1);
    }

   strcpy(cmos_enum->text, text_str);
 }

/****************************************************************************
 * try_add_cmos_enum
 *
 * Attempt to add the given CMOS enum to our internal repository.  Exit with
 * an error message on failure.
 ****************************************************************************/
static void try_add_cmos_enum (const cmos_enum_t *cmos_enum)
 { switch (add_cmos_enum(cmos_enum))
    { case OK:
         return;

      case LAYOUT_DUPLICATE_ENUM:
         fprintf(stderr, "%s: Error on line %d of CMOS layout file: "
                 "Enumeration found with duplicate ID/value combination.\n",
                 prog_name, line_num);
         break;

      default:
         BUG();
    }

   exit(1);
 }

/****************************************************************************
 * set_checksum_info
 *
 * Set CMOS checksum information according to input parameters and perform
 * sanity checking on input parameters.
 ****************************************************************************/
static void set_checksum_info (const char start_str[], const char end_str[],
                               const char index_str[])
 { cmos_checksum_layout_t layout;

   /* These are bit positions that we want to convert to byte positions. */
   layout.summed_area_start =
         string_to_unsigned(start_str, "CMOS checksummed area start");
   layout.summed_area_end =
         string_to_unsigned(end_str, "CMOS checksummed area end");
   layout.checksum_at =
         string_to_unsigned(index_str, "CMOS checksum location");

   switch (checksum_layout_to_bytes(&layout))
    { case OK:
         break;

      case LAYOUT_SUMMED_AREA_START_NOT_ALIGNED:
         fprintf(stderr, "%s: Error on line %d of CMOS layout file.  CMOS "
                 "checksummed area start is not byte-aligned.\n", prog_name,
                 line_num);
         goto fail;

      case LAYOUT_SUMMED_AREA_END_NOT_ALIGNED:
         fprintf(stderr, "%s: Error on line %d of CMOS layout file.  CMOS "
                 "checksummed area end is not byte-aligned.\n", prog_name,
                 line_num);
         goto fail;

      case LAYOUT_CHECKSUM_LOCATION_NOT_ALIGNED:
         fprintf(stderr, "%s: Error on line %d of CMOS layout file.  CMOS "
                 "checksum location is not byte-aligned.\n", prog_name,
                 line_num);
         goto fail;

      case LAYOUT_INVALID_SUMMED_AREA:
         fprintf(stderr, "%s: Error on line %d of CMOS layout file.  CMOS "
                 "checksummed area end must be greater than CMOS checksummed "
                 "area start.\n", prog_name, line_num);
         goto fail;

      case LAYOUT_CHECKSUM_OVERLAPS_SUMMED_AREA:
         fprintf(stderr, "%s: Error on line %d of CMOS layout file.  CMOS "
                 "checksum overlaps checksummed area.\n", prog_name,
                 line_num);
         goto fail;

      case LAYOUT_SUMMED_AREA_OUT_OF_RANGE:
         fprintf(stderr, "%s: Error on line %d of CMOS layout file.  CMOS "
                 "checksummed area out of range.\n", prog_name, line_num);
         goto fail;

      case LAYOUT_CHECKSUM_LOCATION_OUT_OF_RANGE:
         fprintf(stderr, "%s: Error on line %d of CMOS layout file.  CMOS "
                 "checksum location out of range.\n", prog_name, line_num);
         goto fail;

      default:
         BUG();
    }

   cmos_checksum_start = layout.summed_area_start;
   cmos_checksum_end = layout.summed_area_end;
   cmos_checksum_index = layout.checksum_at;
   return;

fail:
   exit(1);
 }

/****************************************************************************
 * cmos_entry_char_value
 *
 * Return the character representation of 'config'.
 ****************************************************************************/
static char cmos_entry_char_value (cmos_entry_config_t config)
 { switch (config)
    { case CMOS_ENTRY_ENUM:
         return 'e';

      case CMOS_ENTRY_HEX:
         return 'h';

      case CMOS_ENTRY_RESERVED:
         return 'r';

      case CMOS_ENTRY_STRING:
         return 's';

      default:
         BUG();
    }

   return 0;  /* not reached */
 }

/****************************************************************************
 * get_layout_file_line
 *
 * Get a line of input from file 'f'.  Store result in 'line' which is an
 * array of 'line_buf_size' bytes.  Return OK on success or an error code on
 * failure.
 ****************************************************************************/
static int get_layout_file_line (FILE *f, char line[], int line_buf_size)
 { switch (get_line_from_file(f, line, line_buf_size))
    { case OK:
         return OK;

      case LINE_EOF:
         return LINE_EOF;

      case LINE_TOO_LONG:
         fprintf(stderr, "%s: Error on line %d of CMOS layout file: Maximum "
                 "line length exceeded.  Max is %d characters.\n", prog_name,
                 line_num, line_buf_size - 2);
         break;
    }

   exit(1);
   return 1;  /* keep compiler happy */
 }

/****************************************************************************
 * string_to_unsigned
 *
 * Convert the string 'str' to an unsigned and return the result.
 ****************************************************************************/
static unsigned string_to_unsigned (const char str[], const char str_name[])
 { unsigned long n;
   unsigned z;

   n = do_string_to_unsigned_long(str, str_name, "");

   if ((z = (unsigned) n) != n)
    { /* This could happen on an architecture in which sizeof(unsigned) <
       * sizeof(unsigned long).
       */
      fprintf(stderr, "%s: Error on line %d of CMOS layout file: %s value is "
              "out of range.\n", prog_name, line_num, str_name);
      exit(1);
    }

   return z;
 }

/****************************************************************************
 * string_to_unsigned_long
 *
 * Convert the string 'str' to an unsigned long and return the result.
 ****************************************************************************/
static unsigned long string_to_unsigned_long (const char str[],
                                              const char str_name[])
 { return do_string_to_unsigned_long(str, str_name, " long"); }

/****************************************************************************
 * do_string_to_unsigned_long
 *
 * Convert the string 'str' to an unsigned long and return the result.  Exit
 * with an appropriate error message on failure.
 ****************************************************************************/
static unsigned long do_string_to_unsigned_long (const char str[],
                                                 const char str_name[],
                                                 const char blurb[])
 { unsigned long n;
   char *p;

   n = strtoul(str, &p, 0);

   if (*p != '\0')
    { fprintf(stderr, "%s: Error on line %d of CMOS layout file: %s is not a "
              "valid unsigned%s integer.\n", prog_name,
              line_num, str_name, blurb);
      exit(1);
    }

   return n;
 }