code:
/* nodes.c -- how to get an Info file and node.
   $Id$

   Copyright 1993, 1998, 1999, 2000, 2002, 2003, 2004, 2006, 2007,
   2008, 2009, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Free Software
   Foundation, Inc.

   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, either version 3 of the License, or
   (at your option) any later version.

   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
   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, see <http://www.gnu.org/licenses/>.

   Originally written by Brian Fox. */

#include "info.h"

#include "nodes.h"
#include "search.h"
#include "filesys.h"
#include "info-utils.h"
#include "tag.h"
#include "man.h"
#include "variables.h"

/* Global variables.  */

/* When non-zero, this is a string describing the recent file error. */
char *info_recent_file_error = NULL;

/* The list of already loaded files. */
FILE_BUFFER **info_loaded_files = NULL;

/* Number of loaded files. */
size_t info_loaded_files_index = 0;

/* The number of slots currently allocated to LOADED_FILES. */
size_t info_loaded_files_slots = 0;

/* Functions for tag table creation and destruction. */

static void build_tag_table (FILE_BUFFER *file_buffer);
static void get_nodes_of_tags_table (FILE_BUFFER *file_buffer,
    SEARCH_BINDING *buffer_binding);
static void get_tags_of_indirect_tags_table (FILE_BUFFER *file_buffer,
    SEARCH_BINDING *indirect_binding, SEARCH_BINDING *tags_binding);
static void free_file_buffer_tags (FILE_BUFFER *file_buffer);
static void free_info_tag (TAG *tag);

/* Grovel FILE_BUFFER->contents finding tags and nodes, and filling in the
   various slots.  This can also be used to rebuild a tag or node table. */
void
build_tags_and_nodes (FILE_BUFFER *file_buffer)
{
  SEARCH_BINDING binding;
  long position;
  long tags_table_begin, tags_table_end;

  free_file_buffer_tags (file_buffer);
  file_buffer->flags &= ~N_HasTagsTable;

  /* See if there is a tags table in this info file. */
  binding.buffer = file_buffer->contents;
  binding.start = file_buffer->filesize;
  binding.end = binding.start - 1000;
  if (binding.end < 0)
    binding.end = 0;
  binding.flags = S_FoldCase;

  position = find_file_section (&binding, TAGS_TABLE_END_LABEL);
  if (position == -1)
    goto no_tags_table;

  /* If there is a tag table, find the start of it, and grovel over it
     extracting tag information. */

  /* Remember the end of the tags table. */
  if (position == 0)
    goto no_tags_table;
  else
    tags_table_end = position - 1;

  /* Locate the start of the tags table. */
  binding.start = tags_table_end;
  binding.end = 0;
  position = find_file_section (&binding, TAGS_TABLE_BEG_LABEL);
  if (position == -1)
    goto no_tags_table;

  /* The file contains a valid tags table.  Fill the FILE_BUFFER's
     tags member. */
  file_buffer->flags |= N_HasTagsTable;
  tags_table_begin = position;

  position += skip_node_separator (file_buffer->contents + position);
  position += strlen (TAGS_TABLE_BEG_LABEL);
  position += strspn (file_buffer->contents + position, "\r\n");
  if (!looking_at_line (TAGS_TABLE_IS_INDIRECT_LABEL,
                        file_buffer->contents + position))
    {
      /* If this isn't an indirect tags table, just remember the nodes
         described locally in this tags table. */
      binding.start = tags_table_begin;
      binding.end = tags_table_end;
      get_nodes_of_tags_table (file_buffer, &binding);
    }
  else
    {
      /* This is an indirect tags table.  Find the indirect table
         preceding the tags table. */
      SEARCH_BINDING indirect;

      indirect.start = tags_table_begin;
      indirect.end = 0;
      indirect.buffer = file_buffer->contents;
      indirect.flags = S_FoldCase;

      position = find_file_section (&indirect, INDIRECT_TABLE_LABEL);
      if (position == -1)
        /* This file is malformed.  Give up. */
        return;

      /* Skip "Indirect:" line. */
      position += strlen (INDIRECT_TABLE_LABEL);
      position += strspn (file_buffer->contents + position, "\r\n");

      indirect.start = position;
      indirect.end = tags_table_begin;

      binding.start = tags_table_begin;
      binding.end = tags_table_end;
      get_tags_of_indirect_tags_table (file_buffer, &indirect, &binding);
    }
  return;

no_tags_table:
  /* This file doesn't have a tag table. */
  build_tag_table (file_buffer);
}

/* Set fields on new tag table entry. */
static void
init_file_buffer_tag (FILE_BUFFER *fb, TAG *entry)
{
  if (fb->flags & N_HasTagsTable)
    {
      entry->flags |= N_HasTagsTable;
      entry->filename = fb->fullpath;

      if (fb->flags & N_TagsIndirect)
        entry->flags |= N_TagsIndirect;
    }
}

/* Search through FILE_BUFFER->contents building an array of NODE *,
   one entry per each node present in the file.  Store the tags in
   FILE_BUFFER->tags, and the number of allocated slots in
   FILE_BUFFER->tags_slots. */
static void
build_tag_table (FILE_BUFFER *file_buffer)
{
  long nodestart;
  size_t tags_index = 0;
  SEARCH_BINDING binding;

  binding.buffer = file_buffer->contents;
  binding.start = 0;
  binding.end = file_buffer->filesize;
  binding.flags = S_FoldCase;

  while ((nodestart = find_node_separator (&binding)) != -1)
    {
      int start;
      char *nodeline;
      TAG *entry;
      int anchor = 0;

      /* Skip past the characters just found. */
      binding.start = nodestart;
      binding.start += skip_node_separator (binding.buffer + binding.start);

      /* Move to the start of the line defining the node. */
      nodeline = binding.buffer + binding.start;

      /* Find "Node:" */
      start = string_in_line (INFO_NODE_LABEL, nodeline);
      /* No Node:.  Maybe it's a Ref:.  */
      if (start == -1)
        {
          start = string_in_line (INFO_REF_LABEL, nodeline);
          if (start != -1)
            anchor = 1;
        }

      /* If not there, this is not the start of a node. */
      if (start == -1)
        continue;

      /* Find the start of the nodename. */
      start += skip_whitespace (nodeline + start);

      /* Record nodename and nodestart. */
      entry = info_create_tag ();
      read_quoted_string (nodeline + start, ",\n\r\t", 0, &entry->nodename);
      if (!entry->nodename || !*entry->nodename)
        {
          free (entry);
          continue;
        }
      entry->nodestart = nodestart;

      init_file_buffer_tag (file_buffer, entry);

      if (anchor)
        entry->cache.nodelen = 0;
      else
        /* Record that the length is unknown. */
        entry->cache.nodelen = -1;

      entry->filename = file_buffer->fullpath;

      /* Add this tag to the array of tag structures in this FILE_BUFFER. */
      add_pointer_to_array (entry, tags_index, file_buffer->tags,
                            file_buffer->tags_slots, 100);
    }
}

/* Build and save the array of nodes in FILE_BUFFER by searching through the
   contents of BUFFER_BINDING for a tags table, and groveling the contents. */
static void
get_nodes_of_tags_table (FILE_BUFFER *file_buffer,
    SEARCH_BINDING *buffer_binding)
{
  int name_offset;
  SEARCH_BINDING s;
  long position;
  size_t tags_index = 0;

  /* Copy buffer_binding */
  s = *buffer_binding;

  /* Find the start of the tags table. */
  position = buffer_binding->start;

  /* If none, we're all done. */
  if (position == -1)
    return;

  /* Move to one character before the start of the actual table. */
  s.start = position;
  s.start += skip_node_separator (s.buffer + s.start);
  s.start += strlen (TAGS_TABLE_BEG_LABEL);

  /* The tag table consists of lines containing node names and positions.
     Do each line until we find one that doesn't contain a node name. */
  while (search_forward ("\n", &s, &position) == search_success)
    {
      TAG *entry;
      char *nodedef;
      unsigned p;
      int anchor = 0;

      /* Prepare to skip this line. */
      s.start = position;
      s.start++;

      /* Skip past informative "(Indirect)" tags table line. */
      if (!tags_index && looking_at (TAGS_TABLE_IS_INDIRECT_LABEL, &s))
        continue;

      /* Find the label preceding the node name. */
      name_offset = string_in_line (INFO_NODE_LABEL, s.buffer + s.start);

      /* If no node label, maybe it's an anchor.  */
      if (name_offset == -1)
        {
          name_offset = string_in_line (INFO_REF_LABEL, s.buffer + s.start);
          if (name_offset != -1)
            anchor = 1;
        }

      /* If not there, not a defining line, so we must be out of the
         tags table.  */
      if (name_offset == -1)
        break;

      entry = info_create_tag ();

      init_file_buffer_tag (file_buffer, entry);

      /* Find the beginning of the node definition. */
      s.start += name_offset;
      nodedef = s.buffer + s.start;
      nodedef += skip_whitespace (nodedef);

      /* Move past the node's name in this tag to the TAGSEP character. */
      for (p = 0; nodedef[p] && nodedef[p] != INFO_TAGSEP; p++)
        ;
      if (nodedef[p] != INFO_TAGSEP)
        continue;

      entry->nodename = xmalloc (p + 1);
      strncpy (entry->nodename, nodedef, p);
      entry->nodename[p] = 0;
      p++;
      entry->nodestart = atol (nodedef + p);

      /* If a node, we don't know the length yet, but if it's an
         anchor, the length is 0. */
      entry->cache.nodelen = anchor ? 0 : -1;

      /* The filename of this node is currently known as the same as the
         name of this file. */
      entry->filename = file_buffer->fullpath;

      /* Add this node structure to the array of node structures in this
         FILE_BUFFER. */
      add_pointer_to_array (entry, tags_index, file_buffer->tags,
                            file_buffer->tags_slots, 100);
    }
}

/* Remember in FILE_BUFFER the nodenames, subfilenames, and offsets within the
   subfiles of every node which appears in the tags table at TAGS_BINDING.  The
   indirect files list is at INDIRECT_BINDING. */
static void
get_tags_of_indirect_tags_table (FILE_BUFFER *file_buffer,
    SEARCH_BINDING *indirect_binding, SEARCH_BINDING *tags_binding)
{
  int i;

  /* A structure used only in `get_tags_of_indirect_tags_table' to hold onto
     an intermediate value. */
  typedef struct {
    char *filename;
    long first_byte;
  } SUBFILE;

  SUBFILE **subfiles = NULL;
  size_t subfiles_index = 0, subfiles_slots = 0;
  TAG *entry;

  /* Remember that tags table was indirect. */
  file_buffer->flags |= N_TagsIndirect;

  /* First get the list of tags from the tags table.  Then lookup the
     associated file in the indirect list for each tag, and update it. */
  get_nodes_of_tags_table (file_buffer, tags_binding);
  if (!file_buffer->tags)
    return;

  /* We have the list of tags in file_buffer->tags.  Get the list of
     subfiles from the indirect table. */
  {
    char *start, *end, *line;
    SUBFILE *subfile;

    start = indirect_binding->buffer + indirect_binding->start;
    end = indirect_binding->buffer + indirect_binding->end;
    line = start;

    while (line < end)
      {
        int colon;

        colon = string_in_line (":", line);

        if (colon == -1)
          break;

        subfile = xmalloc (sizeof (SUBFILE));
        subfile->filename = xmalloc (colon);
        strncpy (subfile->filename, line, colon - 1);
        subfile->filename[colon - 1] = 0;
        subfile->first_byte = (long) atol (line + colon);

        add_pointer_to_array (subfile, subfiles_index, subfiles,
                              subfiles_slots, 10);

        while (*line++ != '\n');
      }
  }

  /* If we have successfully built the indirect files table, then
     merge the information in the two tables. */
  if (!subfiles)
    {
      free_file_buffer_tags (file_buffer);
      return;
    }
  else
    {
      int tags_index;
      long header_length;
      SEARCH_BINDING binding;

      /* Find the length of the header of the file containing the indirect
         tags table.  This header appears at the start of every file.  We
         want the absolute position of each node within each subfile, so
         we subtract the start of the containing subfile from the logical
         position of the node, and then add the length of the header in. */
      binding.buffer = file_buffer->contents;
      binding.start = 0;
      binding.end = file_buffer->filesize;
      binding.flags = S_FoldCase;

      header_length = find_node_separator (&binding);
      if (header_length == -1)
        header_length = 0;

      /* Build the file buffer's list of subfiles. */
      {
        char *containing_dir = xstrdup (file_buffer->fullpath);
        char *temp = filename_non_directory (containing_dir);
        int len_containing_dir;

        if (temp > containing_dir)
          {
            if (HAVE_DRIVE (file_buffer->fullpath) &&
                temp == containing_dir + 2)
              {
                /* Avoid converting "d:foo" into "d:/foo" below.  */
                *temp = '.';
                temp += 2;
              }
            temp[-1] = 0;
          }

        len_containing_dir = strlen (containing_dir);

        for (i = 0; subfiles[i]; i++);

        file_buffer->subfiles = xmalloc ((1 + i) * sizeof (char *));

        for (i = 0; subfiles[i]; i++)
          {
            char *fullpath;

            fullpath = xmalloc
              (2 + strlen (subfiles[i]->filename) + len_containing_dir);

            sprintf (fullpath, "%s/%s",
                     containing_dir, subfiles[i]->filename);

            file_buffer->subfiles[i] = fullpath;
          }
        file_buffer->subfiles[i] = NULL;
        free (containing_dir);
      }

      /* For each node in the file's tags table, remember the starting
         position. */
      for (tags_index = 0; (entry = file_buffer->tags[tags_index]);
           tags_index++)
        {
          for (i = 0;
               subfiles[i] && entry->nodestart >= subfiles[i]->first_byte;
               i++);

          /* If the Info file containing the indirect tags table is
             malformed, then give up. */
          if (!i)
            {
              /* The Info file containing the indirect tags table is
                 malformed.  Give up. */
              for (i = 0; subfiles[i]; i++)
                {
                  free (subfiles[i]->filename);
                  free (subfiles[i]);
                  free (file_buffer->subfiles[i]);
                }
              file_buffer->subfiles = NULL;
              free_file_buffer_tags (file_buffer);
              return;
            }

          /* SUBFILES[i] is the index of the first subfile whose logical
             first byte is greater than the logical offset of this node's
             starting position.  This means that the subfile directly
             preceding this one is the one containing the node. */

          entry->filename = file_buffer->subfiles[i - 1];
          entry->nodestart -= subfiles[i - 1]->first_byte;
          entry->nodestart += header_length;
        }
    }

  /* Free the structures assigned to SUBFILES.  Free the names as well
     as the structures themselves, then finally, the array. */
  for (i = 0; subfiles[i]; i++)
    {
      free (subfiles[i]->filename);
      free (subfiles[i]);
    }
  free (subfiles);
}

/* Free the tags (if any) associated with FILE_BUFFER. */
static void
free_file_buffer_tags (FILE_BUFFER *file_buffer)
{
  int i;

  if (file_buffer->tags)
    {
      TAG *tag;

      for (i = 0; (tag = file_buffer->tags[i]); i++)
        free_info_tag (tag);

      free (file_buffer->tags);
      file_buffer->tags = NULL;
      file_buffer->tags_slots = 0;
    }

  if (file_buffer->subfiles)
    {
      for (i = 0; file_buffer->subfiles[i]; i++)
        free (file_buffer->subfiles[i]);

      free (file_buffer->subfiles);
      file_buffer->subfiles = NULL;
    }
}

/* Free the data associated with TAG, as well as TAG itself. */
static void
free_info_tag (TAG *tag)
{
  free (tag->nodename);

  /* We don't free tag->filename, because that filename is part of the
     subfiles list for the containing FILE_BUFFER.  free_info_tags ()
     will free the subfiles when it is appropriate. */

  free (tag);
}

/* Functions for retrieving files. */

static FILE_BUFFER *info_load_file (char *fullpath, int get_tags);
static void get_file_character_encoding (FILE_BUFFER *fb);
static void forget_info_file (FILE_BUFFER *file_buffer);
static void info_reload_file_buffer_contents (FILE_BUFFER *fb);

/* Try to find a file in our list of already loaded files. */
FILE_BUFFER *
check_loaded_file (char *filename)
{
  int is_fullpath, i;
  FILE_BUFFER *file_buffer;

  /* If full path to the file has been given, we must find it exactly. */
  is_fullpath = IS_ABSOLUTE (filename)
                || filename[0] == '.' && IS_SLASH(filename[1]);

  if (info_loaded_files)
    {
      for (i = 0; (file_buffer = info_loaded_files[i]); i++)
        if (   (FILENAME_CMP (filename, file_buffer->fullpath) == 0)
            || (!is_fullpath
                 && (FILENAME_CMP (filename, file_buffer->filename) == 0)))
          {
            struct stat new_info, *old_info;

            old_info = &file_buffer->finfo;
            if (   stat (file_buffer->fullpath, &new_info) == -1
                || new_info.st_size != old_info->st_size
                || new_info.st_mtime != old_info->st_mtime)
              {
                /* The file has changed.  Forget that we ever had loaded it
                   in the first place. */
                forget_info_file (file_buffer);
                break;
              }

            /* The info file exists, and has not changed since the last
               time it was loaded.  If the caller requested a nodes list
               for this file, and there isn't one here, build the nodes
               for this file_buffer.  In any case, return the file_buffer
               object. */
            if (!file_buffer->contents)
              {
                /* The file's contents have been gc'ed.  Reload it.  */
                info_reload_file_buffer_contents (file_buffer);
                if (!file_buffer->contents)
                  return NULL;
              }

            if (!file_buffer->tags)
              build_tags_and_nodes (file_buffer);

            return file_buffer;
          }
    }
  return 0;
}

/* Locate the file named by FILENAME, and return the information structure
   describing this file.  The file may appear in our list of loaded files
   already, or it may not.  If it does not already appear, find the file,
   and add it to the list of loaded files.  If the file cannot be found,
   return a NULL FILE_BUFFER *. */
FILE_BUFFER *
info_find_file (char *filename)
{
  FILE_BUFFER *file_buffer;
  char *fullpath;
  int is_fullpath;

  file_buffer = check_loaded_file (filename);
  if (file_buffer)
    return file_buffer;

  /* The file wasn't loaded.  Try to load it now. */

  /* Get the full pathname of this file, as known by the info system.
     That is to say, search along INFOPATH and expand tildes, etc. */
  is_fullpath = IS_ABSOLUTE (filename)
                || filename[0] == '.' && IS_SLASH(filename[1]);
  if (!is_fullpath)
    fullpath = info_find_fullpath (filename, 0);
  else
    fullpath = xstrdup (filename);

  /* FIXME: Put the following in info_find_fullpath, or remove
     it altogether. */
  /* If the file referenced by the name returned from info_find_fullpath ()
     doesn't exist, then try again with the last part of the filename
     appearing in lowercase. */
  /* This is probably not needed at all on those systems which define
     FILENAME_CMP to be mbscasecmp.  But let's do it anyway, lest some
     network redirector supports case sensitivity.  */
  if (!fullpath)
    {
      char *lowered_name;
      char *tmp_basename;

      lowered_name = xstrdup (filename);
      tmp_basename = filename_non_directory (lowered_name);

      while (*tmp_basename)
        {
          if (isupper (*tmp_basename))
            *tmp_basename = tolower (*tmp_basename);

          tmp_basename++;
        }

      fullpath = info_find_fullpath (lowered_name, 0);

      free (lowered_name);
    }

  /* If the file wasn't found, give up, returning a NULL pointer. */
  if (!fullpath)
    return NULL;

  file_buffer = info_load_file (fullpath, 0);

  free (fullpath);
  return file_buffer;
}

/* Find a subfile of a split file.  This differs from info_load_file in
   that it does not fill in a tag table for the file. */
FILE_BUFFER *
info_find_subfile (char *fullpath)
{
  char *with_extension = 0;
  int i;
  FILE_BUFFER *file_buffer = 0;

  /* First try to find the file in our list of already loaded files. */
  if (info_loaded_files)
    {
      for (i = 0; (file_buffer = info_loaded_files[i]); i++)
        /* Check if fullpath starts the name of the recorded file (extra
           extensions like ".info.gz" could be added.) */
        if (!strncmp (file_buffer->fullpath, fullpath, strlen (fullpath)))
          {
            struct stat new_info, *old_info;

            old_info = &file_buffer->finfo;
            if (   stat (file_buffer->fullpath, &new_info) == -1
                || new_info.st_size != old_info->st_size
                || new_info.st_mtime != old_info->st_mtime)
              {
                /* The file has changed.  Forget that we ever had loaded it
                   in the first place. */
                forget_info_file (file_buffer);
                break;
              }
            return file_buffer;
          }
    }

  /* The file wasn't loaded.  Try to load it now. */
  with_extension = info_find_fullpath (fullpath, 0);
  if (with_extension)
    {
      file_buffer = info_load_file (with_extension, 1);
      free (with_extension);
    }
  return file_buffer;
}

/* Load the file with path FULLPATH, and return the information structure
   describing this file, even if the file was already loaded.  IS_SUBFILE
   says whether this file is the subfile of a split file.  If it is, mark
   the FILE_BUFFER object as such and do not build a list of nodes for
   this file. */
static FILE_BUFFER *
info_load_file (char *fullpath, int is_subfile)
{
  char *contents;
  size_t filesize;
  struct stat finfo;
  int compressed;
  FILE_BUFFER *file_buffer = NULL;

  contents = filesys_read_info_file (fullpath, &filesize, &finfo, &compressed);

  if (!contents)
    return NULL;

  /* The file was found, and can be read.  Allocate FILE_BUFFER and fill
     in the various members. */
  file_buffer = make_file_buffer ();
  file_buffer->fullpath = xstrdup (fullpath);
  file_buffer->filename = filename_non_directory (file_buffer->fullpath);
  file_buffer->filename = xstrdup (file_buffer->filename);
  /* Strip off a file extension, so we can find it again in info_find_file. */
  {
    char *p = strchr (file_buffer->filename, '.');
    if (p)
      *p = '\0';
  }
  file_buffer->finfo = finfo;
  file_buffer->filesize = filesize;
  file_buffer->contents = contents;
  if (compressed)
    file_buffer->flags |= N_IsCompressed;

  /* Find encoding of file, if set */
  get_file_character_encoding (file_buffer);

  if (!is_subfile)
    build_tags_and_nodes (file_buffer);
  else
    file_buffer->flags |= N_Subfile;

  /* If the file was loaded, remember the name under which it was found. */
  if (file_buffer)
    add_pointer_to_array (file_buffer, info_loaded_files_index,
                          info_loaded_files, info_loaded_files_slots, 10);

  return file_buffer;
}

/* Look for local variables section in FB and set encoding */
static void
get_file_character_encoding (FILE_BUFFER *fb)
{
  SEARCH_BINDING binding;
  long position;

  long int enc_start, enc_len;
  char *enc_string;

  /* See if there is a local variables section in this info file. */
  binding.buffer = fb->contents;
  binding.start = fb->filesize;
  binding.end = binding.start - 1000;
  if (binding.end < 0)
    binding.end = 0;
  binding.flags = S_FoldCase;

  /* Null means the encoding is unknown. */
  fb->encoding = 0;

  if (search_backward (LOCAL_VARIABLES_LABEL, &binding, &position)
      != search_success)
    return;

  binding.start = position;
  binding.end = fb->filesize;

  if (search_forward (CHARACTER_ENCODING_LABEL, &binding, &enc_start)
      != search_success)
    return;

  enc_start += strlen(CHARACTER_ENCODING_LABEL); /* Skip to after "coding:" */
  enc_start += skip_whitespace(fb->contents + enc_start);

  enc_len = strcspn (fb->contents + enc_start, "\r\n");

  enc_string = xmalloc (enc_len + 1);
  strncpy (enc_string, fb->contents + enc_start, enc_len);
  enc_string[enc_len] = '\0';

  fb->encoding = enc_string;
}

/* Create a new, empty file buffer. */
FILE_BUFFER *
make_file_buffer (void)
{
  FILE_BUFFER *file_buffer = xmalloc (sizeof (FILE_BUFFER));

  file_buffer->filename = file_buffer->fullpath = NULL;
  file_buffer->contents = NULL;
  file_buffer->tags = NULL;
  file_buffer->subfiles = NULL;
  file_buffer->tags_slots = 0;
  file_buffer->flags = 0;
  file_buffer->encoding = 0;

  return file_buffer;
}

/* Prevent this file buffer being used again. */
static void
forget_info_file (FILE_BUFFER *file_buffer)
{
  file_buffer->flags |= N_Gone;
  file_buffer->filename[0] = '\0';
  file_buffer->fullpath = "";
  memset (&file_buffer->finfo, 0, sizeof (struct stat));
}

/* Load the contents of FILE_BUFFER->contents.  This function is called
   when a file buffer was loaded, and then in order to conserve memory, the
   file buffer's contents were freed and the pointer was zero'ed.  Note that
   the file was already loaded at least once successfully, so the tags and/or
   nodes members are still correctly filled. */
static void
info_reload_file_buffer_contents (FILE_BUFFER *fb)
{
  int is_compressed;

  fb->flags &= ~N_IsCompressed;

  /* Let the filesystem do all the work for us. */
  fb->contents =
    filesys_read_info_file (fb->fullpath, &(fb->filesize), &(fb->finfo),
                            &is_compressed);
  if (is_compressed)
    fb->flags |= N_IsCompressed;
}

/* Functions for node creation and retrieval. */

static long get_node_length (SEARCH_BINDING *binding);
static void get_filename_and_nodename (NODE *node,
                                      char **filename, char **nodename,
                                      char *filename_in, char *nodename_in);
static void node_set_body_start (NODE *node);

/* Return a pointer to a newly allocated TAG structure, with
   fields filled in. */
TAG *
info_create_tag (void)
{
  TAG *t = xmalloc (sizeof (TAG));

  memset (t, 0, sizeof (TAG));
  t->filename = 0;
  t->nodename = 0;
  t->nodestart = -1;
  t->nodestart_adjusted = -1;
  t->cache.nodelen = -1;

  return t;
}
/* Return a pointer to a newly allocated NODE structure, with
   fields filled in. */
NODE *
info_create_node (void)
{
  NODE *n = xmalloc (sizeof (NODE));

  n->fullpath = 0;
  n->subfile = 0;
  n->nodename = 0;
  n->contents = 0;
  n->nodelen = -1;
  n->display_pos = 0;
  n->body_start = 0;
  n->flags = 0;
  n->references = 0;
  n->up = 0;
  n->prev = 0;
  n->next = 0;

  return n;
}

/* Return the length of the node which starts at BINDING. */
static long
get_node_length (SEARCH_BINDING *binding)
{
  int i;
  char *body;

  /* [A node] ends with either a ^_, a ^L, or end of file.  */
  for (i = binding->start, body = binding->buffer; i < binding->end; i++)
    {
      if (body[i] == INFO_FF || body[i] == INFO_COOKIE)
        break;
    }
  return i - binding->start;
}

#define FOLLOW_REMAIN 0
#define FOLLOW_PATH 1

int follow_strategy;

/* Return a pointer to a NODE structure for the Info node (FILENAME)NODENAME,
   using DEFAULTS for defaults.  If DEFAULTS is null, the defaults are:
   - If FILENAME is NULL, `dir' is used.
   - If NODENAME is NULL, `Top' is used.

   If the node cannot be found, return NULL. */
NODE *
info_get_node_with_defaults (char *filename_in, char *nodename_in,
                NODE *defaults)
{
  NODE *node = 0;
  FILE_BUFFER *file_buffer = NULL;
  char *filename = 0, *nodename = 0;

  info_recent_file_error = NULL;

  get_filename_and_nodename (defaults, &filename, &nodename,
                             filename_in, nodename_in);

  /* If the file to be looked up is "dir", build the contents from all of
     the "dir"s and "localdir"s found in INFOPATH. */
  if (is_dir_name (filename))
    {
      node = get_dir_node ();
      goto cleanup_and_exit;
    }

  if (mbscasecmp (filename, MANPAGE_FILE_BUFFER_NAME) == 0)
    {
      node = get_manpage_node (nodename);
      goto cleanup_and_exit;
    }

  if (follow_strategy == FOLLOW_REMAIN
      && defaults && defaults->fullpath)
    {
      /* Find the directory in the filename for defaults, and look in
         that directory first. */
      char *file_in_same_dir;
      char saved_char, *p;

      p = defaults->fullpath + strlen (defaults->fullpath);
      while (p > defaults->fullpath && !IS_SLASH (*p))
        p--;

      if (p > defaults->fullpath)
        {
          saved_char = *p;
          *p = 0;

          file_in_same_dir = info_add_extension (defaults->fullpath,
                                                 filename, 0);
          if (file_in_same_dir)
            file_buffer = info_find_file (file_in_same_dir);
          free (file_in_same_dir);
          *p = saved_char;
        }
    }

  if (!file_buffer)
    file_buffer = info_find_file (filename);

  if (file_buffer)
    {
      /* Look for the node.  */
      node = info_get_node_of_file_buffer (file_buffer, nodename);
    }

  if (!file_buffer)
    {
      /* Try to find a man page with this name as a fall back. */
      node = get_manpage_node (filename);
      if (!node)
        {
          if (filesys_error_number)
            info_recent_file_error =
              filesys_error_string (filename, filesys_error_number);
        }
    }

  /* If the node not found was "Top", try again with different case. */
  if (!node && (nodename && mbscasecmp (nodename, "Top") == 0))
    {
      node = info_get_node_of_file_buffer (file_buffer, "Top");
      if (!node)
        node = info_get_node_of_file_buffer (file_buffer, "top");
      if (!node)
        node = info_get_node_of_file_buffer (file_buffer, "TOP");
    }

cleanup_and_exit:
  free (filename); free (nodename);
  return node;
}

/* Return NODE specified with FILENAME_IN and NODENAME_IN.  Return value
   should be freed by caller, but none of its fields should be. */
NODE *
info_get_node (char *filename_in, char *nodename_in)
{
  return info_get_node_with_defaults (filename_in, nodename_in, 0);
}

/* Get filename and nodename of node to load using defaults from NODE.
   Output values should be freed by caller. */
static void
get_filename_and_nodename (NODE *node,
                           char **filename, char **nodename,
                           char *filename_in, char *nodename_in)
{
  *filename = filename_in;

  /* If FILENAME is not specified, it defaults to "dir". */
  if (filename_in)
    *filename = xstrdup (filename_in);
  else
    {
      if (node)
        *filename = xstrdup (node->fullpath);
      else
        *filename = xstrdup ("dir");
    }

  if (nodename_in && *nodename_in)
    *nodename = xstrdup (nodename_in);
  else
    /* If NODENAME is not specified, it defaults to "Top". */
    *nodename = xstrdup ("Top");
}

static void
node_set_body_start (NODE *node)
{
  int n = skip_node_separator (node->contents);
  node->body_start = strcspn(node->contents + n, "\n");
  node->body_start += n;
}

/* Return a pointer to a NODE structure for the Info node NODENAME in
   FILE_BUFFER.  NODENAME can be passed as NULL, in which case the
   nodename of "Top" is used.  If the node cannot be found, return a
   NULL pointer.  Return value should be freed by caller, but none of its
   fields should be. */
NODE *
info_get_node_of_file_buffer (FILE_BUFFER *file_buffer, char *nodename)
{
  NODE *node = NULL;

  /* If we are unable to find the file, we have to give up.  There isn't
     anything else we can do. */
  if (!file_buffer)
    return NULL;

  /* If the file buffer was gc'ed, reload the contents now. */
  if (!file_buffer->contents)
    info_reload_file_buffer_contents (file_buffer);

  /* If the name of the node that we wish to find is exactly "*", then the
     node body is the contents of the entire file.  Create and return such
     a node. */
  if (strcmp (nodename, "*") == 0)
    {
      node = info_create_node ();
      node->fullpath = file_buffer->fullpath;
      node->nodename = xstrdup ("*");
      node->contents = file_buffer->contents;
      node->nodelen = file_buffer->filesize;
      node->body_start = 0;
    }
  /* Search the tags table for an entry which matches the node that we want. */
  else
    {
      TAG *tag;
      int i;

      /* If no tags at all (possibly a misformatted info file), quit.  */
      if (!file_buffer->tags)
        return NULL;

      for (i = 0; (tag = file_buffer->tags[i]); i++)
        if (strcmp (nodename, tag->nodename) == 0)
          {
            node = info_node_of_tag (file_buffer, &file_buffer->tags[i]);
            break;
          }
    }

  /* Return the results of our node search. */
  return node;
}

/* Convert any CRLF pairs in the SOURCE file and place the converted buffer in
   DESTINATION.  DESTINATION->contents must be allocated on the heap and at
   least as big as SOURCE->contents, including a terminating null.  DESTINATION
   is allowed to be the same as SOURCE to convert in place. */
void
convert_eols (FILE_BUFFER *destination, FILE_BUFFER *source)
{
  register char *d = destination->contents;
  register char *s = source->contents;

  long textlen = source->filesize;
  while (textlen--)
    {
      if (*s == '\r' && textlen && s[1] == '\n')
        {
          s++;
          textlen--;
        }
      *d++ = *s++;
    }
  *d = '\0';

  destination->filesize = d - destination->contents;
  /* EOL conversion can shrink the text quite a bit.  We don't
     want to waste storage.  */
  destination->contents = xrealloc (destination->contents,
                                    d - destination->contents + 1);
}

/* Magic number that RMS used to decide how much a tags table pointer could
   be off by.  I feel that it should be much smaller, like 4.  */
#define DEFAULT_INFO_FUDGE 1000

/* Find the actual starting memory location of NODE.  Because of the
   way that tags are implemented, the physical nodestart may
   not actually be where the tag says it is.  If that is the case,
   set N_UpdateTags in NODE->flags.  If the node is found, return non-zero.
   Set NODE->nodestart_adjusted directly on the separator that precedes this
   node.  If the node could not be found, return 0. */
static int
adjust_nodestart (FILE_BUFFER *fb, TAG *node, int slack)
{
  long position = -1;
  SEARCH_BINDING s;

  /* Try the optimal case first.  Who knows?  This file may actually be
     formatted (mostly) correctly. */
  s.buffer = fb->contents;
  s.start = node->nodestart;
  s.end = s.start + 1;

  /* Check that the given nodestart is in fact inside the file buffer. */
  if (s.start >= 0 && s.start < fb->filesize)
    {
      /* Check for node separator at node->nodestart
         introducting this node. */
      position = find_node_in_binding (node->nodename, &s);
    }

  if (position == -1)
    {
      if (strict_node_location_p)
        return 0;

      /* Oh well, I guess we have to try to find it in a larger area. */

      s.start -= slack;
      s.end += slack;

      if (s.start < 0)
        s.start = 0;
      else if (s.start > fb->filesize)
        s.start = fb->filesize;
      if (s.end > fb->filesize)
        s.end = fb->filesize;

      position = find_node_in_binding (node->nodename, &s);

      /* If the node still couldn't be found, we lose big. */
      if (position == -1)
        return 0;

      /* Set the flag in NODE->flags to say that the the tags table could
         need updating (if we used a tag to get here, that is). */
      if (node->flags & N_HasTagsTable)
        node->flags |= N_UpdateTags;
    }

  node->nodestart_adjusted = s.buffer + position - fb->contents;
  return 1;
}

/* Look in the contents of *FB_PTR for a node referred to with TAG.  Set
   the location if found in TAG->nodestart_adjusted.

   PARENT->tags contains the tags table for the whole file.  If file is
   non-split, PARENT should be the same as FB. */
static int
find_node_from_tag (FILE_BUFFER *parent, FILE_BUFFER *fb, TAG *tag)
{
  int success;
  int slack;
  TAG **t;
  WINDOW *w;

  /* Start off with a small fudge to reduce chance of finding a node and then
     later having to convert the EOL's, leaving us with the question of what to
     do with the existing buffer and the nodes that refer to it. */
  if (!(fb->flags & N_EOLs_Converted))
    slack = 4;
  else
    slack = DEFAULT_INFO_FUDGE;

  if (tag->nodestart_adjusted != -1)
    success = 1;
  else
    success = adjust_nodestart (fb, tag, slack);

  if (success)
    return success;

  if (fb->flags & N_EOLs_Converted || strict_node_location_p)
    return 0;

  /* Convert EOL's.  If the Info file was produced under MS-Windows with
     some versions of makeinfo, it's possible that it has CR-LF line endings
     with the CR bytes not counted in the tag table. */

  convert_eols (fb, fb);
  fb->flags |= N_EOLs_Converted;

  /* Restore tags table to what was read from the file. */
  for (t = parent->tags; *t; t++)
    {
      /* For split files, only restore the part of the tag table for
         the subfile. */
      if (!FILENAME_CMP ((*t)->filename, fb->fullpath))
        {
          NODE *n = &(*t)->cache;
          int is_anchor = n->nodelen == 0;
          (*t)->nodestart_adjusted = -1;
          if (n->flags & N_WasRewritten)
            free (n->contents);
          info_free_references (n->references);
          free (n->next); free (n->prev); free (n->up);
          memset (n, 0, sizeof (NODE));
          if (!is_anchor)
            n->nodelen = -1;
        }
    }

  /* Look for the node again. */
  success = adjust_nodestart (fb, tag, DEFAULT_INFO_FUDGE);

  /* For each window, check for file buffer being used in window history,
     including currently displayed node, and amend it to refer properly to the
     converted file buffer.  (Window history was set in info_set_node_of_window
     in session.c. )

     There is a chance that there is a NODE in some local variable
     somewhere, which we can't update. */

  for (w = windows; w; w = w->next)
    {
      WINDOW_STATE **h;

      if (!w->hist)
        continue;

      w->node = 0;
      for (h = w->hist; *h; h++)
        {
          NODE *n = (*h)->node;;
          if (!(n->flags & N_IsInternal)
              && (n->subfile ? (!FILENAME_CMP (n->subfile, fb->fullpath))
                             : (!FILENAME_CMP (n->fullpath, fb->fullpath))))
            {
              /* The call to info_get_node is indirectly recursive, but it
                 should not recurse twice because of the N_EOLs_Converted
                 conditional above. */
              (*h)->node = info_get_node (n->fullpath, n->nodename);
              if ((*h)->node)
                {
                  (*h)->node->active_menu = n->active_menu;
                  free_history_node (n);
                }
              else
                {
                  /* We found the node before, but now we can't.  Just leave
                     the node as it was (possibly with its contents pointer
                     pointing to the wrong place). */
                  (*h)->node = n;
                }
            }
        }
      if (h > w->hist)
        w->node = (*(h - 1))->node;
    }

  if (success)
    return success;

  return 0;
}

/* Calculate the length of the node. */
static void
set_tag_nodelen (FILE_BUFFER *subfile, TAG *tag)
{
  SEARCH_BINDING node_body;

  node_body.buffer = subfile->contents;
  node_body.start = tag->nodestart_adjusted;
  node_body.end = subfile->filesize;
  node_body.flags = 0;
  node_body.start += skip_node_separator (node_body.buffer + node_body.start);
  tag->cache.nodelen = get_node_length (&node_body);
}

/* Return the node described by *TAG_PTR, retrieving contents from subfile
   if the file is split.  Return 0 on failure.  If FAST, don't process the
   node to find cross-references, a menu, or perform character encoding
   conversion. */
static NODE *
info_node_of_tag_ext (FILE_BUFFER *fb, TAG **tag_ptr, int fast)
{
  TAG *tag = *tag_ptr;
  NODE *node;
  int is_anchor;
  TAG *anchor_tag;
  int node_pos, anchor_pos;

  FILE_BUFFER *parent; /* File containing tag table. */
  FILE_BUFFER *subfile; /* File containing node. */

  if (!FILENAME_CMP (fb->fullpath, tag->filename))
    parent = subfile = fb;
  else
    {
      /* This is a split file. */
      parent = fb;
      subfile = info_find_subfile (tag->filename);
    }

  if (!subfile)
    return NULL;

  if (!subfile->contents)
    {
      info_reload_file_buffer_contents (subfile);
      if (!subfile->contents)
        return NULL;
    }

  /* If we were able to find this file and load it, then return
     the node within it. */
  if (!(tag->nodestart >= 0 && tag->nodestart < subfile->filesize))
    return NULL;

  node = 0;

  is_anchor = tag->cache.nodelen == 0;

  if (is_anchor)
    {
      anchor_pos = tag_ptr - fb->tags;

      /* Look backwards in the tag table for the node preceding
         the anchor (we're assuming the tags are given in order),
         skipping over any preceding anchors.  */
      for (node_pos = anchor_pos - 1;
           node_pos >= 0 && fb->tags[node_pos]->cache.nodelen == 0;
           node_pos--)
        ;

      /* An info file with an anchor before any nodes is pathological, but
         it's possible, so don't crash.  */
      if (node_pos < 0)
        return NULL;

      anchor_tag = tag;
      tag = fb->tags[node_pos];
      tag_ptr = &fb->tags[node_pos];
    }

  /* We haven't checked the entry pointer yet.  Look for the node
     around about it and adjust it if necessary. */
  if (tag->cache.nodelen == -1)
    {
      if (!find_node_from_tag (parent, subfile, tag))
        return NULL; /* Node not found. */

      set_tag_nodelen (subfile, tag);
    }

  node = xmalloc (sizeof (NODE));
  memset (node, 0, sizeof (NODE));
  if (tag->cache.references)
    {
      /* Initialize the node from the cache. */
      *node = tag->cache;
      if (!node->contents)
        {
          node->contents = subfile->contents + tag->nodestart_adjusted;
          node->contents += skip_node_separator (node->contents);
        }
    }
  else
    {
      /* Data for node has not been generated yet. */
      node->contents = subfile->contents + tag->nodestart_adjusted;
      node->contents += skip_node_separator (node->contents);
      node->nodelen = tag->cache.nodelen;
      node->nodename = tag->nodename;
      node->flags = tag->flags;

      node->fullpath = parent->fullpath;
      if (parent != subfile)
        node->subfile = tag->filename;

      if (fast)
        node->flags |= N_Simple;
      else
        {
          /* Read locations of references in node and similar.  Strip Info file
             syntax from node if preprocess_nodes=On.  Adjust the offsets of
             anchors that occur within the node. */
          scan_node_contents (node, parent, tag_ptr);

          if (!preprocess_nodes_p)
            node_set_body_start (node);
          tag->cache = *node;
          if (!(node->flags & N_WasRewritten))
            tag->cache.contents = 0; /* Pointer into file buffer
                                        is not saved.  */
        }
    }

  /* We can't set this when tag table is built, because
     if file is split, we don't know which of the sub-files
     are compressed. */
  if (subfile->flags & N_IsCompressed)
    node->flags |= N_IsCompressed;

  if (is_anchor)
    {
      /* Start displaying the node at the anchor position.  */

      node->display_pos = anchor_tag->nodestart_adjusted
        - (tag->nodestart_adjusted
           + skip_node_separator (subfile->contents
                                  + tag->nodestart_adjusted));

      /* Otherwise an anchor at the end of a node ends up displaying at
         the end of the last line of the node (way over on the right of
         the screen), which looks wrong.  */
      if (node->display_pos >= (unsigned long) node->nodelen)
        node->display_pos = node->nodelen - 1;
      else if (node->display_pos < 0)
        node->display_pos = 0; /* Shouldn't happen. */
    }

  return node;
}

NODE *
info_node_of_tag (FILE_BUFFER *fb, TAG **tag_ptr)
{
  return info_node_of_tag_ext (fb, tag_ptr, 0);
}

NODE *
info_node_of_tag_fast (FILE_BUFFER *fb, TAG **tag_ptr)
{
  return info_node_of_tag_ext (fb, tag_ptr, 1);
}


code:
/* nodes.h -- How we represent nodes internally.
   $Id$

   Copyright 1993, 1997, 1998, 2002, 2004, 2007, 2011, 2012, 2013,
   2014, 2015 Free Software Foundation, Inc.

   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, either version 3 of the License, or
   (at your option) any later version.

   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
   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, see <http://www.gnu.org/licenses/>.

   Originally written by Brian Fox. */

#ifndef NODES_H
#define NODES_H

#include "info.h"

/* Structure which describes a node reference, such as a menu entry or
   cross reference. */
typedef struct {
  char *label;          /* User Label. */
  char *filename;       /* File where this node can be found. */
  char *nodename;       /* Name of the node. */
  int start, end;       /* Offsets within the containing node of LABEL. */
  long line_number;     /* Specific line number a menu item points to.  */
  int type;             /* Whether reference is a xref or a menu item */
} REFERENCE;

/* Possible values of REFERENCE.type */
#define REFERENCE_XREF 0
#define REFERENCE_MENU_ITEM 1

typedef struct {
  char *fullpath;               /* Non-null is the logical file name. */
  char *subfile;                /* File containing node for split files. */
  char *nodename;               /* The name of this node. */
  char *contents;               /* Characters appearing in this node. */
  long nodelen;                 /* The length of the CONTENTS member. */
  long display_pos;             /* Where to display at, if nonzero.  */
  long body_start;              /* Offset of the actual node body */
  int flags;                    /* See immediately below. */
  REFERENCE **references;       /* Cross-references or menu items in node.
                                   Null-terminated.  references == 0 implies
                                   uninitialized, not empty */
  char *up, *prev, *next;       /* Names of nearby nodes. */
  int active_menu;              /* Used for subnodes search. */
} NODE;

/* Values for NODE.flags or FILE_BUFFER.flags. */
#define N_HasTagsTable 0x01     /* This node was found through a tags table. */
#define N_TagsIndirect 0x02     /* The tags table was an indirect one. */
#define N_UpdateTags   0x04     /* The tags table is out of date. */
#define N_IsCompressed 0x08     /* The file is compressed on disk. */
#define N_IsInternal   0x10     /* This node was made by Info. */
#define N_CannotGC     0x20     /* File buffer cannot be gc'ed. */
#define N_IsManPage    0x40     /* This node is a manpage. */
#define N_WasRewritten 0x100    /* NODE->contents can be passed to free(). */
#define N_IsIndex      0x200    /* An index node. */
#define N_IsDir        0x400    /* A dir node. */
#define N_Subfile      0x800    /* File buffer is a subfile of a split file. */
#define N_EOLs_Converted 0x1000 /* CR bytes were stripped before LF. */
#define N_Gone         0x2000   /* File is no more. */
#define N_Simple       0x4000   /* Data about cross-references is missing. */
#define N_SeenBySearch 0x8000   /* Node has already been seen in a search. */

/* String constants. */
#define INFO_FILE_LABEL                 "File:"
#define INFO_REF_LABEL                  "Ref:"
#define INFO_NODE_LABEL                 "Node:"
#define INFO_PREV_LABEL                 "Prev:"
#define INFO_ALTPREV_LABEL              "Previous:"
#define INFO_NEXT_LABEL                 "Next:"
#define INFO_UP_LABEL                   "Up:"
#define INFO_MENU_LABEL                 "\n* Menu:"
#define INFO_MENU_ENTRY_LABEL           "\n* "
#define INFO_XREF_LABEL                 "*Note"
#define TAGS_TABLE_END_LABEL            "End Tag Table"
#define TAGS_TABLE_BEG_LABEL            "Tag Table:"
#define INDIRECT_TABLE_LABEL            "Indirect:"
#define TAGS_TABLE_IS_INDIRECT_LABEL    "(Indirect)"
#define LOCAL_VARIABLES_LABEL           "Local Variables"
#define CHARACTER_ENCODING_LABEL        "coding:"

/* Character constants. */
#define INFO_COOKIE '\037'
#define INFO_FF     '\014'
#define INFO_TAGSEP '\177'

/* For each logical file that we have loaded, we keep a list of
   the names of the nodes that are found in that file.  A pointer to
   a node in an info file is called a "tag".  For split files, the
   tag pointer is "indirect"; that is, the pointer also contains the
   name of the split file where the node can be found.  For non-split
   files, the filename member simply contains the name of the
   current file. */
typedef struct {
  char *filename;               /* The file where this node can be found. */
  char *nodename;               /* The node pointed to by this tag. */
  long nodestart;               /* The value read from the tag table. */
  long nodestart_adjusted;      /* Where the node or anchor actually is. */
  int flags;                    /* Same as NODE.flags. */
  NODE cache;                   /* Saved information about pointed-to node. */
} TAG;

/* The following structure is used to remember information about the contents
   of Info files that we have loaded at least once before.  The FINFO member
   is present so that we can reload the file if it has been modified since
   last being loaded.  All of the arrays appearing within this structure
   are NULL terminated. */
typedef struct {
  char *filename;               /* The filename used to find this file. */
  char *fullpath;               /* The full pathname of this info file. */
  struct stat finfo;            /* Information about this file. */
  char *contents;               /* The contents of this particular file. */
  size_t filesize;              /* The number of bytes this file expands to. */
  char **subfiles;              /* If non-null, the list of subfiles. */
  TAG **tags;                   /* If non-null, the tags table. */
  size_t tags_slots;            /* Number of slots allocated for TAGS. */
  int flags;                    /* Various flags.  Mimics of N_* flags. */
  char *encoding;               /* Name of character encoding of file. */
} FILE_BUFFER;

/* Array of FILE_BUFFER * which represents the currently loaded info files. */
extern FILE_BUFFER **info_loaded_files;
extern size_t info_loaded_files_index;
extern size_t info_loaded_files_slots;

/* Locate the file named by FILENAME, and return the information structure
   describing this file.  The file may appear in our list of loaded files
   already, or it may not.  If it does not already appear, find the file,
   and add it to the list of loaded files.  If the file cannot be found,
   return a NULL FILE_BUFFER *. */
FILE_BUFFER *info_find_file (char *filename);

FILE_BUFFER *check_loaded_file (char *filename);

FILE_BUFFER *info_find_subfile (char *filename);

TAG *info_create_tag (void);

/* Return a pointer to a new NODE structure. */
NODE *info_create_node (void);

/* Return a pointer to a NODE structure for the Info node (FILENAME)NODENAME.
   FILENAME can be passed as NULL, in which case the filename of "dir" is used.
   NODENAME can be passed as NULL, in which case the nodename of "Top" is used.

   If the node cannot be found, return a NULL pointer. */
NODE *info_get_node (char *filename, char *nodename);

NODE *info_get_node_with_defaults (char *filename, char *nodename,
                                          NODE *defaults);

NODE *info_node_of_tag (FILE_BUFFER *fb, TAG **tag_ptr);
NODE *info_node_of_tag_fast (FILE_BUFFER *fb, TAG **tag_ptr);

/* Return a pointer to a NODE structure for the Info node NODENAME in
   FILE_BUFFER.  NODENAME can be passed as NULL, in which case the
   nodename of "Top" is used.  If the node cannot be found, return a
   NULL pointer. */
NODE *info_get_node_of_file_buffer (FILE_BUFFER *file_buffer,
                                           char *nodename);

/* Grovel FILE_BUFFER->contents finding tags and nodes, and filling in the
   various slots.  This can also be used to rebuild a tag or node table. */
void build_tags_and_nodes (FILE_BUFFER *file_buffer);

void free_history_node (NODE *n);

/* When non-zero, this is a string describing the most recent file error. */
extern char *info_recent_file_error;

/* Create a new, empty file buffer. */
FILE_BUFFER *make_file_buffer (void);

/* Non-zero means don't try to be smart when searching for nodes.  */
extern int strict_node_location_p;

/* Found in dir.c */
NODE *get_dir_node (void);
REFERENCE *lookup_dir_entry (char *label, int sloppy);
REFERENCE *dir_entry_of_infodir (char *label, char *searchdir);

#endif /* not NODES_H */

repo_name: mthl/texinfo

license: mit


code:
/* pcterm.c -- How to handle the PC terminal for Info under MS-DOS/MS-Windows.
   $Id$

   Copyright 1998, 1999, 2003, 2004, 2007, 2008, 2012, 2013, 2014, 2015
   Free Software Foundation, Inc.

   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, either version 3 of the License, or
   (at your option) any later version.

   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
   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, see <http://www.gnu.org/licenses/>.  */

/* WARNING WARNING WARNING!!!  This probably won't work as is with
   anything but DJGPP and MinGW!  However, Borland should come close,
   and other PC compilers will need minor modifications.  */

#ifdef __MSDOS__
/* intl/libintl.h defines a macro `gettext' which
   conflicts with conio.h header.  */
#ifdef gettext
# undef gettext
# define gettext _gettext
#endif

#include <pc.h>
#include <keys.h>
#include <conio.h>
#endif

#ifdef _WIN32
#include <io.h>
#include <conio.h>
#include <process.h>
#include <malloc.h>	/* for alloca */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 4
#endif
#ifndef COMMON_LVB_UNDERSCORE
#define COMMON_LVB_UNDERSCORE 0x8000
#endif

struct text_info {
    WORD normattr;
    WORD attribute;
    SHORT winleft;
    SHORT wintop;
    SHORT winright;
    SHORT winbottom;
    SHORT screenheight;
    SHORT screenwidth;
    SHORT curx;
    SHORT cury;
    COORD bufsize;
    unsigned char currmode;	/* unused and unsupported for Windows */
};

struct termios {
  int dummy;
};

enum text_modes { LASTMODE=-1 };

#define cprintf _cprintf
#define cputs _cputs

#undef read
#undef _read

#include "display.h"

void reset_info_window_sizes (void);
void redisplay_after_signal (void);

#endif

#include "variables.h"
#include "session.h"
#include "terminal.h"

extern int speech_friendly;	/* defined in info.c */

/* **************************************************************** */
/*                                                                  */
/*                PC Terminal Output Functions                      */
/*                                                                  */
/* **************************************************************** */

static struct text_info outside_info;  /* holds screen params outside Info */
#ifdef _WIN32
static WORD norm_attr, inv_attr, xref_attr;
static WORD current_attr;
static HANDLE hstdin = INVALID_HANDLE_VALUE;
static HANDLE hstdout = INVALID_HANDLE_VALUE;
static HANDLE hinfo = INVALID_HANDLE_VALUE;
static HANDLE hscreen = INVALID_HANDLE_VALUE;
static DWORD old_inpmode;
static DWORD old_outpmode;
static UINT output_cp;
#else
static unsigned char    norm_attr, inv_attr, xref_attr;
#endif

static unsigned const char * find_sequence (int);

#ifdef _WIN32

/* Windows-specific initialization and de-initialization.  */
void
w32_info_prep (void)
{
  if (hinfo != INVALID_HANDLE_VALUE)
    {
      DWORD new_mode;

      SetConsoleActiveScreenBuffer (hinfo);
      current_attr = norm_attr;
      hscreen = hinfo;
      SetConsoleMode (hstdin, ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT);
      GetConsoleMode (hscreen, &old_outpmode);
      new_mode = old_outpmode & ~ENABLE_WRAP_AT_EOL_OUTPUT;
      SetConsoleMode (hscreen, new_mode);
      /* Enable underline, if available. */
      SetConsoleMode (hscreen, new_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
    }
}

void
w32_info_unprep (void)
{
  if (hinfo != INVALID_HANDLE_VALUE)
    {
      SetConsoleActiveScreenBuffer (hstdout);
      current_attr = outside_info.normattr;
      hscreen = hstdout;
      SetConsoleMode (hstdin, old_inpmode);
    }
}

void
w32_cleanup (void)
{
  if (hinfo != INVALID_HANDLE_VALUE)
    {
      COORD cursor_pos;

      /* Restore the original position of the cursor.  */
      cursor_pos.X = outside_info.curx;
      cursor_pos.Y = outside_info.cury;
      SetConsoleCursorPosition (hstdout, cursor_pos);

      /* Close the input handle we created.  */
      CloseHandle (hinfo);
    }
}

static void w32_info_init (void) __attribute__((constructor));
static void pc_initialize_terminal (char *);

static void
w32_info_init (void)
{
  /* We need to set this single hook here; the rest
     will be set by pc_initialize_terminal when it is called.  */
  terminal_initialize_terminal_hook = pc_initialize_terminal;
}

/* Emulate DJGPP conio functions for Windows.  */
static void
gettextinfo (struct text_info *ti)
{
  CONSOLE_SCREEN_BUFFER_INFO csbi;
  static TCHAR errbuf[500];
  DWORD ignored;

  hstdin = GetStdHandle (STD_INPUT_HANDLE);
  hstdout = GetStdHandle (STD_OUTPUT_HANDLE);

  if (!GetConsoleMode (hstdin, &ignored))
    hstdin = INVALID_HANDLE_VALUE;

  if (hstdout != INVALID_HANDLE_VALUE
      && GetConsoleMode (hstdout, &ignored))
    {
      hinfo = CreateConsoleScreenBuffer (GENERIC_READ | GENERIC_WRITE,
					 FILE_SHARE_READ | FILE_SHARE_WRITE,
					 NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
      if (hinfo != INVALID_HANDLE_VALUE
	  && GetConsoleScreenBufferInfo (hstdout, &csbi))
	{
	  ti->normattr = csbi.wAttributes;
	  ti->winleft = 1;
	  ti->wintop = 1;
	  ti->winright = csbi.srWindow.Right + 1;
	  ti->winbottom = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
	  ti->attribute = csbi.wAttributes;
	  ti->screenheight = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
	  ti->screenwidth = csbi.srWindow.Right - csbi.srWindow.Left + 1;
	  ti->curx = csbi.dwCursorPosition.X;
	  ti->cury = csbi.dwCursorPosition.Y;
	  ti->bufsize = csbi.dwSize;

	  atexit (w32_cleanup);
	}
      else
	{
	  DWORD error_no = GetLastError ();

	  if (!FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL,
			      error_no,
			      0, /* choose most suitable language */
			      errbuf, sizeof (errbuf), NULL))
	    sprintf (errbuf, "w32 error %u", error_no);
	  CloseHandle (hinfo);
	  info_error (_("Terminal cannot be initialized: %s\n"), errbuf);
	  exit (1);
	}
    }
  else
    {
      /* We were invoked non-interactively.  Do the minimum we must.   */
      ti->screenheight = 24;
      ti->screenwidth = 80;
    }
}

void
textattr (int attr)
{
  if (hscreen != INVALID_HANDLE_VALUE)
    SetConsoleTextAttribute (hscreen, attr);
}

void
textmode (int mode)
{
  /* Nothing.  */
}

void
highvideo (void)
{
  int attr;
  CONSOLE_SCREEN_BUFFER_INFO csbi;

  GetConsoleScreenBufferInfo (hscreen, &csbi);
  attr = csbi.wAttributes | FOREGROUND_INTENSITY;
  attr ^= norm_attr & FOREGROUND_INTENSITY;
  textattr (attr);
}

void
normvideo (void)
{
  int attr;
  CONSOLE_SCREEN_BUFFER_INFO csbi;

  GetConsoleScreenBufferInfo (hscreen, &csbi);
  attr = csbi.wAttributes & ~(FOREGROUND_INTENSITY | BACKGROUND_INTENSITY
			      | COMMON_LVB_UNDERSCORE);
  attr |= norm_attr & (FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);
  textattr (attr);
}

void
blinkvideo (void)
{
  int attr;
  CONSOLE_SCREEN_BUFFER_INFO csbi;

  GetConsoleScreenBufferInfo (hscreen, &csbi);
  attr = csbi.wAttributes | BACKGROUND_INTENSITY;
  attr ^= norm_attr & BACKGROUND_INTENSITY;
  textattr (attr);
}

void
underline (void)
{
  int attr;
  CONSOLE_SCREEN_BUFFER_INFO csbi;

  GetConsoleScreenBufferInfo (hscreen, &csbi);
  attr = csbi.wAttributes | COMMON_LVB_UNDERSCORE;
  textattr (attr);
}

void
textcolor (int color)
{
  int attr;
  CONSOLE_SCREEN_BUFFER_INFO csbi;

  GetConsoleScreenBufferInfo (hscreen, &csbi);
  attr = (csbi.wAttributes & (COMMON_LVB_UNDERSCORE | 0xf0)) | (color & 0x0f);
  textattr (attr);
}

void
textbackground (int color)
{
  int attr;
  CONSOLE_SCREEN_BUFFER_INFO csbi;

  GetConsoleScreenBufferInfo (hscreen, &csbi);
  attr = (csbi.wAttributes & (COMMON_LVB_UNDERSCORE | 0x0f)) | ((color & 0x0f) << 4);
  textattr (attr);
}

void
ScreenGetCursor (int *row, int *col)
{
  CONSOLE_SCREEN_BUFFER_INFO csbi;

  if (hscreen == INVALID_HANDLE_VALUE)
    *row = *col = 0;
  else
    {
      GetConsoleScreenBufferInfo (hscreen, &csbi);
      *row = csbi.dwCursorPosition.Y;
      *col = csbi.dwCursorPosition.X;
    }
}

void
ScreenSetCursor (int row, int col)
{
  if (hscreen != INVALID_HANDLE_VALUE)
    {
      COORD cursor_pos;

      cursor_pos.X = col;
      cursor_pos.Y = row;

      SetConsoleCursorPosition (hscreen, cursor_pos);
    }
}

void
ScreenClear (void)
{
  if (hscreen != INVALID_HANDLE_VALUE)
    {
      DWORD nchars = screenwidth * screenheight;
      COORD start_pos;
      DWORD written;

      start_pos.X = start_pos.Y = 0;
      FillConsoleOutputAttribute (hscreen, norm_attr, nchars, start_pos,
				  &written);
      FillConsoleOutputCharacter (hscreen, ' ', nchars, start_pos, &written);
    }
}

void
clreol (void)
{
  if (hscreen != INVALID_HANDLE_VALUE)
    {
      DWORD nchars;
      COORD start_pos;
      DWORD written;
      CONSOLE_SCREEN_BUFFER_INFO csbi;

      GetConsoleScreenBufferInfo (hscreen, &csbi);
      start_pos = csbi.dwCursorPosition;
      nchars = csbi.dwSize.X - start_pos.X;

      FillConsoleOutputAttribute (hscreen, current_attr, nchars, start_pos,
				  &written);
      FillConsoleOutputCharacter (hscreen, ' ', nchars, start_pos, &written);
    }
}

void
ScreenVisualBell (void)
{
  if (hscreen != INVALID_HANDLE_VALUE)
    {
      DWORD nchars = screenwidth * screenheight;
      COORD start_pos;
      DWORD written;
      PWORD attr;
      DWORD i;

      start_pos.X = start_pos.Y = 0;
      attr = xmalloc (nchars * sizeof (WORD));
      ReadConsoleOutputAttribute (hscreen, attr, nchars, start_pos, &written);
      for (i = 0; i < nchars; ++i)
	attr[i] ^= norm_attr ^ inv_attr;
      WriteConsoleOutputAttribute (hscreen, attr, nchars, start_pos, &written);
      Sleep (50);
      for (i = 0; i < nchars; ++i)
	attr[i] ^= norm_attr ^ inv_attr;
      WriteConsoleOutputAttribute (hscreen, attr, nchars, start_pos, &written);
      free (attr);
    }
  else
    {
      printf ("%c", '\a');
      fflush (stdout);
    }
}

int
movetext(int left, int top, int right, int bottom, int destleft, int desttop)
{
  if (hscreen != INVALID_HANDLE_VALUE)
    {
      SMALL_RECT src;
      COORD dest;
      CHAR_INFO fill;

      src.Left = left - 1;
      src.Top = top - 1;
      src.Right = right - 1;
      src.Bottom = bottom - 1;

      dest.X = destleft - 1;
      dest.Y = desttop - 1;

      fill.Attributes = norm_attr;
      fill.Char.AsciiChar = (CHAR)' ';

      return ScrollConsoleScreenBuffer (hscreen, &src , NULL, dest, &fill) != 0;
    }
  else
    return 0;
}

int
ScreenRows (void)
{
  if (hscreen != INVALID_HANDLE_VALUE)
    {
      CONSOLE_SCREEN_BUFFER_INFO csbi;

      GetConsoleScreenBufferInfo (hscreen, &csbi);
      return csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
    }
  else
    return 24;
}

int
ScreenCols (void)
{
  if (hscreen != INVALID_HANDLE_VALUE)
    {
      CONSOLE_SCREEN_BUFFER_INFO csbi;

      GetConsoleScreenBufferInfo (hscreen, &csbi);
      return csbi.srWindow.Right - csbi.srWindow.Left + 1;
    }
  else
    return 80;
}

void
_set_screen_lines (int lines)
{
  if (hscreen != INVALID_HANDLE_VALUE)
    {
      SMALL_RECT window_rectangle;
      CONSOLE_SCREEN_BUFFER_INFO csbi;
      COORD scrbufsize;

      GetConsoleScreenBufferInfo (hscreen, &csbi);

      window_rectangle = csbi.srWindow;
      window_rectangle.Bottom = window_rectangle.Top + lines - 1;
      SetConsoleWindowInfo (hscreen, TRUE, &window_rectangle);

      /* Set the screen buffer size to the same dimensions as the window,
	 so that the dysfunctional scroll bar disappears.  */
      scrbufsize.X = window_rectangle.Right - window_rectangle.Left + 1;
      scrbufsize.Y = window_rectangle.Bottom - window_rectangle.Top + 1;
      SetConsoleScreenBufferSize (hscreen, scrbufsize);
    }
}

void
w32_set_screen_dimensions (int cols, int rows)
{
  if (hscreen != INVALID_HANDLE_VALUE)
    {
      SMALL_RECT window_rectangle;
      CONSOLE_SCREEN_BUFFER_INFO csbi;

      GetConsoleScreenBufferInfo (hscreen, &csbi);

      window_rectangle = csbi.srWindow;
      window_rectangle.Bottom = window_rectangle.Top + rows - 1;
      window_rectangle.Right = window_rectangle.Left + cols - 1;
      SetConsoleWindowInfo (hscreen, TRUE, &window_rectangle);
    }
}

/* Emulate `sleep'.  */
unsigned
sleep (unsigned sec)
{
  Sleep (sec*1000);
  return 0;
}

/* Keyboard input support.  */

static int
w32_our_tty (int fd)
{
  /* Is this our tty?  */
  return hstdin != INVALID_HANDLE_VALUE
	 && hstdin == (HANDLE)_get_osfhandle (fd);
}

/* Translate a Windows key event into the equivalent sequence of bytes
   to be submitted to Info dispatcher.  */
#define define_seq(p,s1,s2)					\
  do {								\
    if ((ctl & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0)	\
      memcpy (p, s1, sizeof (s1)), p += sizeof (s1) - 1;	\
    else							\
      memcpy (p, s2, sizeof (s2)), p += sizeof (s2) - 1;	\
  } while (0)

static int
w32keyseq (unsigned char ascii_ch, WORD vkey, DWORD ctl, unsigned char *seq)
{
  unsigned char *p = seq;

  switch (ascii_ch)
    {
      case '\0':
	/* Keys with no ASCII code are extended keys, like arrows.  */
	switch (vkey)
	  {
	    case VK_PRIOR:
	      define_seq (p, "\033\061p", "\033v");
	      break;
	    case VK_NEXT:
	      define_seq (p, "\033\061n", "\026");
	      break;
	    case VK_END:
	      define_seq (p, "\033>", "\033>");
	      break;
	    case VK_HOME:
	      define_seq (p, "\033<", "\033<");
	      break;
	    case VK_LEFT:
	      define_seq (p, "\033b", "\033[D");
	      break;
	    case VK_UP:
	      define_seq (p, "\033\061u", "\033[A");
	      break;
	    case VK_RIGHT:
	      define_seq (p, "\033f", "\033[C");
	      break;
	    case VK_DOWN:
	      define_seq (p, "\033\061m", "\033[B");
	      break;
	    case VK_INSERT:
	      define_seq (p, "\033[L", "\033[L");
	      break;
	    case VK_DELETE:	/* Delete => Ctrl-d, Alt-Delete => ESC d */
	      if ((ctl & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0)
		define_seq (p, "\033d", "\033d");
	      else
		define_seq (p, "\033d", "\004");
	      break;
	    case VK_HELP:	/* F1 => Ctrl-h */
	    case VK_F1:
	      *p++ = '\010';
	      break;
	    case 50:		/* Ctrl-@ => '\0' */
	      if ((ctl & SHIFT_PRESSED) != 0)
		*p++ = '\0';
	      break;
	    default:
	      if (0x41 <= vkey && vkey <= 0x5a)
		{
		  /* Alt-Ctrl-a, Alt-Ctrl-b, etc.  */
		  *p++ = '\033';
		  *p++ = '\001' + vkey - 0x41;
		}
	  }
	break;
      case ' ':			/* Ctrl-SPC => '\0' */
	if ((ctl & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0)
	  ascii_ch = '\0';
	*p++ = ascii_ch;
	break;
      case '\t':		/* Shift-TAB/Alt-TAB => Esc-TAB */
	if ((ctl & (SHIFT_PRESSED | LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0)
	  {
	    memcpy (p, "\033\011", sizeof ("\033\011"));
	    p += sizeof ("\033\011") - 1;
	  }
	else
	  *p++ = '\t';
	break;
      case '\b':
	/* Backspace => DEL.  */
	ascii_ch = '\177';
	/* FALLTHROUGH */
      default:
	if ((ctl & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0)
	  *p++ = '\033';
	*p++ = ascii_ch;
	break;
    }
  return p - seq;
}

static unsigned char buffered_chars[512];
static size_t buf_head;
static size_t buf_tail;

static ssize_t
w32_kbd_read (unsigned char *inbuf, size_t n)
{
  DWORD nevents, nread;
  INPUT_RECORD inrec;
  ssize_t nret = 0;

  do {

    /* Stuff any unread buffered characters.  */
    while (buf_head < buf_tail && n > 0)
      {
	*inbuf++ = buffered_chars[buf_head++];
	nret++;
	n--;
      }
    if (n <= 0)
      break;

    /* Wait for input.  */
    while (GetNumberOfConsoleInputEvents (hstdin, &nevents)
	   && nevents < 1)
      Sleep (20);

    while (nevents-- && n > 0)
      {
	if (!ReadConsoleInput (hstdin, &inrec, 1, &nread))
	  return -1;

	if (nread > 0)
	  {
	    switch (inrec.EventType)
	      {
		case KEY_EVENT:
		  if (inrec.Event.KeyEvent.bKeyDown == TRUE
		      && !(inrec.Event.KeyEvent.wVirtualScanCode == 0
			   || inrec.Event.KeyEvent.wVirtualKeyCode == VK_SHIFT
			   || inrec.Event.KeyEvent.wVirtualKeyCode == VK_CONTROL
			   || inrec.Event.KeyEvent.wVirtualKeyCode == VK_MENU))
		    {
		      unsigned char keyseq[10];
		      int count = inrec.Event.KeyEvent.wRepeatCount;
		      unsigned char ch = inrec.Event.KeyEvent.uChar.AsciiChar;
		      WORD vkey = inrec.Event.KeyEvent.wVirtualKeyCode;
		      DWORD ctl_state = inrec.Event.KeyEvent.dwControlKeyState;
		      int nbytes = w32keyseq (ch, vkey, ctl_state, keyseq);

		      /* Supply up to N characters to the caller.  */
		      while (count && n >= nbytes)
			{
			  if (nbytes == 1 && keyseq[0] == '\032')
			    {
			      terminal_goto_xy (0, screenheight - 1);
			      terminal_clear_to_eol ();
			      fflush (stdout);
			      terminal_unprep_terminal ();
			      kill (getpid (), 0);
			      terminal_prep_terminal ();
			      reset_info_window_sizes ();
			    }
			  else
			    {
			      memcpy (&inbuf[nret], keyseq, nbytes);
			      nret += nbytes;
			      n -= nbytes;
			    }
			  count--;
			}
		      /* Buffer the rest.  */
		      if (count > 0)
			{
			  buf_head = buf_tail = 0;
			  while (count--
				 && buf_tail < sizeof(buffered_chars) - nbytes)
			    {
			      memcpy (&buffered_chars[buf_tail], keyseq, nbytes);
			      buf_tail += nbytes;
			    }
			}
		    }
		  break;
		case WINDOW_BUFFER_SIZE_EVENT:
		  {
		    int rows, cols;

		    /* Note: this event is only sent when the console
		       window's _screen_buffer_ size is changed via
		       the Properties->Layout dialog.  */
		    cols = inrec.Event.WindowBufferSizeEvent.dwSize.X;
		    rows = inrec.Event.WindowBufferSizeEvent.dwSize.Y;
		    screenwidth = cols;
		    screenheight = rows;
		    w32_set_screen_dimensions (cols, rows);
		    display_initialize_display (screenwidth, screenheight);
		    window_new_screen_size (screenwidth, screenheight);
		    redisplay_after_signal ();
		  }
		  break;
		case MOUSE_EVENT:
		  {
		    /* Only vertical wheel support for now.  */
		    int wheeled =
		      (inrec.Event.MouseEvent.dwEventFlags & MOUSE_WHEELED) != 0;
		    if (wheeled && mouse_protocol == MP_NORMAL_TRACKING)
		      {
			extern void info_up_line (WINDOW *, int count);
			extern void info_down_line (WINDOW *, int count);
			extern WINDOW *active_window;

			int hiword =
			  HIWORD (inrec.Event.MouseEvent.dwButtonState);

			if ((hiword & 0xFF00) == 0)
			  info_up_line (active_window, 3);
			else
			  info_down_line (active_window, 3);
			display_update_display ();
		      }
		  }
		  break;
		default:
		  break;
	      }
	  }
      }
  } while (n > 0);
  return nret;
}

long
w32_chars_avail (int fd)
{
  if (w32_our_tty (fd))
    return buf_tail - buf_head;
  else
    {
      struct stat st;

      if (fstat (fd, &st) < 0)
	return 1;
      else
	return st.st_size;
    }
}

ssize_t
w32_read (int fd, void *buf, size_t n)
{
  if (w32_our_tty (fd))
    return w32_kbd_read (buf, n);
  else
    return _read (fd, buf, n);
}

/* Write to the console a string of text encoded in UTF-8 or UTF-7.  */
static void
write_utf (DWORD cp, const char *text, int nbytes)
{
  /* MSDN says UTF-7 requires zero in flags.  */
  DWORD flags = (cp == CP_UTF7) ? 0 : MB_ERR_INVALID_CHARS;
  /* How much space do we need for wide characters?  */
  int wlen = MultiByteToWideChar (cp, flags, text, nbytes, NULL, 0);

  if (wlen)
    {
      WCHAR *text_w = alloca (wlen * sizeof (WCHAR));
      DWORD written;

      if (MultiByteToWideChar (cp, flags, text, nbytes, text_w, wlen) > 0)
	{
	  WriteConsoleW (hscreen, text_w, (nbytes < 0) ? wlen - 1 : wlen,
			 &written, NULL);
	  return;
	}
    }
  /* Fall back on conio.  */
  if (nbytes < 0)
    cputs (text);
  else
    cprintf ("%.*s", nbytes, text);
}

/* A replacement for nl_langinfo which does a more accurate job for
   the console output codeset.  Windows can use 3 different encodings
   at the same time, and the Posix-compliant nl_langinfo simply
   doesn't know enough to decide which one is needed when CODESET is
   requested.  */
#undef nl_langinfo
#include <langinfo.h>

char *
rpl_nl_langinfo (nl_item item)
{
  if (item == CODESET)
    {
      static char buf[100];

      /* We need all the help we can get from GNU libiconv, so we
	 request transliteration as well.  */
      sprintf (buf, "CP%u//TRANSLIT", GetConsoleOutputCP ());
      return buf;
    }
  else
    return nl_langinfo (item);
}

#ifndef HAVE_WCWIDTH
/* A replacement for wcwidth.  The Gnulib version calls setlocale for
   every character Info is about to display, which makes display of
   large nodes annoyingly slow.

   Note that the Gnulib version is still compiled and put into
   libgnu.a, because the configure script doesn't know about this
   replacement.  But the linker will not pull the Gnulib version into
   the binary, because it resolves the calls to this replacement
   function.  */
int
wcwidth (wchar_t wc)
{
  return wc == 0 ? 0 : iswprint (wc) ? 1 : -1;
}
#endif

#endif	/* _WIN32 */

/* Turn on reverse video. */
static void
pc_begin_inverse (void)
{
  textattr (inv_attr);
}

/* Turn off reverse video. */
static void
pc_end_inverse (void)
{
  textattr (norm_attr);
}

/* The implementation of the underlined text.  The DOS/Windows console
   doesn't support underlined text (until Win10), so we make it blue instead
   (blue, because this face is used for hyperlinks).  */
static void
pc_begin_underline (void)
{
  if (xref_attr != COMMON_LVB_UNDERSCORE)
    textattr (xref_attr);
  else
    underline ();
}

static void
pc_end_underline (void)
{
  if (xref_attr != COMMON_LVB_UNDERSCORE)
    textattr (norm_attr);
  else
    normvideo ();
}

/* Standout (a.k.a. "high video") text.  */
static void
pc_begin_standout (void)
{
  highvideo ();
}

static void
pc_end_standout (void)
{
  normvideo ();
}

static void
pc_begin_blink (void)
{
  blinkvideo ();
}

static void
pc_default_color (void)
{
  textattr (norm_attr);
}

/* Info definitions of 8 colors (see terminal.h) are in an order
   that's different from Windows/DOS console colors.  This function
   unscrambles the order, and also maps 8 standard ANSI colors to the
   low-intensity shades of the 16 PC colors, so that "standout" works
   by turning the intensity bit.  */
static int
convert_color (int terminal_color)
{
  /* The terminal.h order is:
     black, red, green, yellow, blue, magenta, cyan, white.  */
  static int pc_color_map[] = {
    0, 4, 2, 6, 1, 5, 3, 7
  };
  int intensity = terminal_color & (FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);
  terminal_color &= ~(FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);

  if (terminal_color >= 0
      && terminal_color < sizeof(pc_color_map) / sizeof (pc_color_map[0]))
    return pc_color_map[terminal_color] | intensity;
  return 7;	/* lightgray */
}

static void
pc_set_fg_color (int color)
{
  textcolor (convert_color (color) | (norm_attr & FOREGROUND_INTENSITY));
}

static void
pc_set_bg_color (int color)
{
  textbackground (convert_color (color) | (norm_attr & BACKGROUND_INTENSITY));
}

/* Move the cursor up one line. */
static void
pc_up_line (void)
{
  int x, y;
  ScreenGetCursor (&y, &x);
  ScreenSetCursor (MAX (y-1, 0), x);
}

/* Move the cursor down one line. */
static void
pc_down_line (void)
{
  int x, y;
  ScreenGetCursor (&y, &x);
  ScreenSetCursor (MIN (screenheight-1, y+1), x);
}

/* Clear the entire terminal screen. */
static void
pc_clear_screen (void)
{
  ScreenClear ();
}

/* Clear from the current position of the cursor to the end of the line. */
static void
pc_clear_to_eol (void)
{
  clreol (); /* perhaps to be replaced by a loop */
}

/* Set the global variables SCREENWIDTH and SCREENHEIGHT. */
static void
pc_get_screen_size(void)
{
  /* Current screen dimensions are the default.  */
  if (!outside_info.screenheight)	/* paranoia */
    gettextinfo (&outside_info);
  screenwidth  = outside_info.screenwidth;
  screenheight = outside_info.screenheight;

  /* Environment variable "LINES" overrides the default.  */
  if (getenv ("LINES") != NULL)
    screenheight = atoi (getenv ("LINES"));

  /* Environment variable "INFO_LINES" overrides "LINES".  */
  if (getenv ("INFO_LINES") != NULL)
    screenheight = atoi (getenv ("INFO_LINES"));
}

/* Move the cursor to the terminal location of X and Y. */
static void
pc_goto_xy (x, y)
     int x, y;
{
  ScreenSetCursor (y, x); /* yes, pc.h says ScreenSetCursor (row, column) !! */
}

/* Print STRING to the terminal at the current position. */
static void
pc_put_text (string)
     char *string;
{
  if (speech_friendly)
    fputs (string, stdout);
#ifdef __MINGW32__
  else if (hscreen == INVALID_HANDLE_VALUE)
    fputs (string, stdout);
  else if (output_cp == CP_UTF8 || output_cp == CP_UTF7)
    write_utf (output_cp, string, -1);
#endif
  else
    cputs (string);
}

/* Ring the terminal bell.  The bell is rung visibly if the terminal is
   capable of doing that, and if terminal_use_visible_bell_p is non-zero. */
static void
pc_ring_bell(void)
{
  if (terminal_has_visible_bell_p && terminal_use_visible_bell_p)
    ScreenVisualBell ();
  else
    {
      printf ("%c",'\a');
      fflush (stdout);
    }
}

/* Print NCHARS from STRING to the terminal at the current position. */
static void
pc_write_chars (string, nchars)
    char *string;
    int nchars;
{
  if (!nchars)
    return;

  if (speech_friendly)
    printf ("%.*s", nchars, string);
#ifdef __MINGW32__
  else if (hscreen == INVALID_HANDLE_VALUE)
    printf ("%.*s", nchars, string);
  else if (output_cp == CP_UTF8 || output_cp == CP_UTF7)
    write_utf (output_cp, string, nchars);
#endif
  else
    cprintf ("%.*s", nchars, string);
}

/* Scroll an area of the terminal from START to (and excluding) END,
   AMOUNT lines.  If AMOUNT is negative, the lines are scrolled
   towards the top of the screen, else they are scrolled towards the
   bottom of the screen.  The lines of the old region which do not
   overlap the new region are cleared, to mimic terminal operation.  */
static void
pc_scroll_terminal (start, end, amount)
    int start, end, amount;
{
  int line_to_clear = amount > 0 ? start : end + amount;

  /* Move the text.  Note that `movetext' expects 1-based coordinates.  */
  movetext (1, start + 1, ScreenCols (), end, 1, start + amount + 1);

  /* Now clear the lines which were left unoccupied.  */
  if (amount < 0)
    amount = -amount;
  while (amount--)
    {
      ScreenSetCursor (line_to_clear++, 0);
      clreol ();
    }
}

/* Put the screen in the video mode and colors which Info will use.
   Prepare to start using the terminal to read characters singly.  */
static void
pc_prep_terminal (void)
{
  int tty;

#ifdef _WIN32
  w32_info_prep ();
#endif

  /* Do not set screen height if we already have it, because
     doing so erases the screen.  */
  if (screenheight != ScreenRows ())
    _set_screen_lines (screenheight);

  /* Don't fail if they asked for screen dimensions that their
     hardware cannot support.  */
  screenheight = ScreenRows ();
  screenwidth  = ScreenCols ();

  /* Try setting the colors user asked for.  */
  textattr (norm_attr);
  ScreenClear ();

  /* Switch console reads to binary mode.  */
  tty = fileno (stdin);
#ifdef __DJGPP__
  setmode (tty, O_BINARY);
  __djgpp_set_ctrl_c (1);	/* re-enable SIGINT generation by Ctrl-C */
#endif
}

/* Restore the tty settings back to what they were before we started using
   this terminal. */
static void
pc_unprep_terminal (void)
{
  int tty;

#ifdef _WIN32
  w32_info_unprep ();
#endif

  textattr (outside_info.normattr);

  /* Do not set screen height if we already have it, because
     doing so erases the screen.  */
  if (outside_info.screenheight != ScreenRows ())
    {
      _set_screen_lines (outside_info.screenheight);
      textmode (LASTMODE);
    }
#ifdef __MSDOS__
  else
    pc_clear_to_eol ();	/* for text attributes to really take effect */
#endif
#ifdef _WIN32
  if (hscreen != INVALID_HANDLE_VALUE)
    SetConsoleScreenBufferSize (hstdout, outside_info.bufsize);
#endif

  /* Switch back to text mode on stdin.  */
  tty = fileno (stdin);
#ifdef __DJGPP__
  setmode (tty, O_TEXT);
#endif
}

/* Initialize the terminal which is known as TERMINAL_NAME.  If this
   terminal doesn't have cursor addressability, `terminal_is_dumb_p'
   becomes nonzero.  The variables SCREENHEIGHT and SCREENWIDTH are set
   to the dimensions that this terminal actually has.  Finally, the
   terminal screen is cleared. */
static void
pc_initialize_terminal (term_name)
    char *term_name;
{
  char *info_colors;

  if (!term_name)
    {
      term_name = getenv ("TERM");
      if (!term_name)
#ifdef __MSDOS__
	term_name = "pc-dos";	/* ``what's in a name?'' */
#endif
#ifdef _WIN32
	term_name = "w32console";
#endif
    }

  /* Get current video information, to be restored later.  */
  if (outside_info.screenwidth == 0)
    gettextinfo (&outside_info);

  /* Current screen colors are the default.  */
  norm_attr    = outside_info.normattr;
  inv_attr     = (((outside_info.normattr &    7) << 4) |
                  ((outside_info.normattr & 0x7f) >> 4));
#ifdef __MSDOS__
  xref_attr = CYAN;
#endif
#ifdef _WIN32
  xref_attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
#endif
  xref_attr |= outside_info.normattr & 0xf0;

  /* Does the user want non-default colors?  */
  info_colors = getenv ("INFO_COLORS");
  if ((info_colors != (char *)0) && !speech_friendly)
    {
      /* Decode a color from a string descriptor.
	 The descriptor string is a sequence of color specifiers separated
	 by a non-numeric character.  Each color specifier should represent
	 a small integer which fits into an unsigned char, and can be given
	 in any base supported by strtoul.  Examples of valid descriptors:

		"10 31"
		"0x13/0x45"
		"007.077"

	The separator between two color specifiers can be any character which
	cannot be used in a printed representation of an integer number.  */
      char *endp;
      unsigned long color_desc = strtoul (info_colors, &endp, 0);

      if (color_desc <= UCHAR_MAX)
	{
	  norm_attr = (unsigned char)color_desc;
	  xref_attr = (xref_attr & 0x0f) | (norm_attr & 0xf0);
	  color_desc = strtoul (endp + 1, &endp, 0);
	  if (color_desc <= UCHAR_MAX)
	    inv_attr = (unsigned char)color_desc;
#ifdef _WIN32
	  if (*endp == 'u')
	    xref_attr = COMMON_LVB_UNDERSCORE;
	  else
#endif
	  if (*endp != '\0')
	    {
	      color_desc = strtoul (endp + 1, &endp, 0);
	      if (color_desc <= UCHAR_MAX)
		{
#ifdef _WIN32
		  if (*endp == 'u')
		    color_desc |= COMMON_LVB_UNDERSCORE;
		  xref_attr = (WORD)color_desc;
#else
		  xref_attr = (unsigned char)color_desc;
#endif
		}
	    }
	}
    }

  /* We can scroll.  */
  terminal_can_scroll = 1;

  /* We know how to produce a visible bell, if somebody's looking...  */
  if (!speech_friendly)
    terminal_has_visible_bell_p = 1;

  /* We are *certainly* NOT dumb!  */
  terminal_is_dumb_p = 0;

  pc_get_screen_size ();

#ifdef __MINGW32__
  /* Record the screen output codepage.  */
  output_cp = GetConsoleOutputCP ();
#endif

#ifdef __MSDOS__
  /* Store the arrow keys.  */
  term_ku = (char *)find_sequence (K_Up);
  term_kd = (char *)find_sequence (K_Down);
  term_kr = (char *)find_sequence (K_Right);
  term_kl = (char *)find_sequence (K_Left);

  term_kP = (char *)find_sequence (K_PageUp);
  term_kN = (char *)find_sequence (K_PageDown);

  term_kh = (char *)find_sequence (K_Home);
  term_ke = (char *)find_sequence (K_End);
  term_ki = (char *)find_sequence (K_Insert);
  term_kD = (char *)find_sequence (K_Delete);
#elif defined _WIN32
  term_kh = "\033<";
  term_ke = "\033>";
  term_ki = "\033[L";
#endif	/* __MSDOS__ */

  /* Set all the hooks to our PC-specific functions.  */
  terminal_begin_inverse_hook       = pc_begin_inverse;
  terminal_end_inverse_hook         = pc_end_inverse;
  terminal_begin_standout_hook      = pc_begin_standout;
  terminal_end_standout_hook        = pc_end_standout;
  terminal_begin_underline_hook     = pc_begin_underline;
  terminal_end_underline_hook       = pc_end_underline;
  terminal_begin_bold_hook          = pc_begin_standout;
  terminal_begin_blink_hook         = pc_begin_blink;
  terminal_end_all_modes_hook       = pc_default_color;
  terminal_default_colour_hook      = pc_default_color;
  terminal_set_colour_hook          = pc_set_fg_color;
  terminal_set_bgcolour_hook        = pc_set_bg_color;
  terminal_prep_terminal_hook       = pc_prep_terminal;
  terminal_unprep_terminal_hook     = pc_unprep_terminal;
  terminal_up_line_hook             = pc_up_line;
  terminal_down_line_hook           = pc_down_line;
  terminal_clear_screen_hook        = pc_clear_screen;
  terminal_clear_to_eol_hook        = pc_clear_to_eol;
  terminal_get_screen_size_hook     = pc_get_screen_size;
  terminal_goto_xy_hook             = pc_goto_xy;
  terminal_put_text_hook            = pc_put_text;
  terminal_ring_bell_hook           = pc_ring_bell;
  terminal_write_chars_hook         = pc_write_chars;
  terminal_scroll_terminal_hook     = pc_scroll_terminal;
}

/* **************************************************************** */
/*                                                                  */
/*            How to Read Characters From the PC Terminal           */
/*                                                                  */
/* **************************************************************** */

/* This will most certainly work ONLY with DJGPP.  */
#ifdef __DJGPP__

#include <errno.h>
#include <sys/fsext.h>
#include <dpmi.h>

/* Translation table for some special keys.
   Arrow keys which are standard on other keyboards are translated into
   standard ESC-sequences, in case somebody rebinds the simple keys
   (like C-f, C-b, C-n, etc.).

   The strange "\033\061" prefix in some keys is a numeric argument of
   one, which means ``do the next command once''.  It is here so that
   when the according PC key is pressed in the middle of an incremental
   search, Info doesn't see just an ASCII character like `n' or `B',
   and doesn't add it to the search string; instead, it will exit the
   incremental search and then perform the command.  */
static struct
{
  int inkey;
  unsigned char const * const sequence;
} DJGPP_keytab[] = {		   /* these are for moving between nodes... */
  {K_Control_PageDown,  "\033\061n"},
  {K_Control_PageUp,    "\033\061p"},
  {K_Control_Up,        "\033\061u"},
  {K_Control_Down,      "\033\061m"},
  {K_Control_Center,    "\033\061l"},

  {K_Home,              "\033[H"}, /* ...and these are for moving IN a node */
  {K_End,               "\033[F"}, /* they're Numeric-Keypad-Keys, so       */
  {K_Left,              "\033[D"}, /* NUMLOCK should be off !!              */
  {K_Right,             "\033[C"},
  {K_Down,              "\033[B"},
  {K_Up,                "\033[A"},
  {K_PageDown,          "\033[G"},
  {K_PageUp,            "\033[I"},
  {K_Control_Left,      "\033b"},
  {K_Control_Right,     "\033f"},
  {K_Control_Home,      "\033<"},
  {K_Control_End,       "\033>"},

  {K_EHome,             "\033[H"}, /* these are also for moving IN a node */
  {K_EEnd,              "\033[F"}, /* they're the "extended" (Grey) keys  */
  {K_ELeft,             "\033[D"},
  {K_ERight,            "\033[C"},
  {K_EDown,             "\033[B"},
  {K_EUp,               "\033[A"},
  {K_EPageDown,         "\033[G"},
  {K_EPageUp,           "\033[I"},
  {K_Control_ELeft,     "\033b"},
  {K_Control_ERight,    "\033f"},
  {K_Control_EHome,     "\033<"},
  {K_Control_EEnd,      "\033>"},

  {K_BackTab,           "\033\011"},
  {K_F1,                "\10"},    /* YEAH, gimme that good old F-one-thing */
  {K_Delete,            "\177"},   /* to make Kp-Del be DEL (0x7f)          */
  {K_EDelete,           "\177"},   /* to make Delete be DEL (0x7f)          */
  {K_Insert,            "\033[L"},
  {K_EInsert,           "\033[L"},

  /* These are here to map more Alt-X keys to ESC X sequences.  */
  {K_Alt_Q,             "\033q"},
  {K_Alt_W,             "\033w"},
  {K_Alt_E,             "\033e"},
  {K_Alt_R,             "\033r"},
  {K_Alt_T,             "\033t"},
  {K_Alt_Y,             "\033y"},
  {K_Alt_U,             "\033u"},
  {K_Alt_I,             "\033i"},
  {K_Alt_O,             "\033o"},
  {K_Alt_P,             "\033p"},
  {K_Alt_LBracket,      "\033["},
  {K_Alt_RBracket,      "\033]"},
  {K_Alt_Return,        "\033\015"},
  {K_Alt_A,             "\033a"},
  {K_Alt_S,             "\033s"},
  {K_Alt_D,             "\033d"},
  {K_Alt_F,             "\033f"},
  {K_Alt_G,             "\033g"},
  {K_Alt_H,             "\033h"},
  {K_Alt_J,             "\033j"},
  {K_Alt_K,             "\033k"},
  {K_Alt_L,             "\033l"},
  {K_Alt_Semicolon,     "\033;"},
  {K_Alt_Quote,         "\033'"},
  {K_Alt_Backquote,     "\033`"},
  {K_Alt_Backslash,     "\033\\"},
  {K_Alt_Z,             "\033z"},
  {K_Alt_X,             "\033x"},
  {K_Alt_C,             "\033c"},
  {K_Alt_V,             "\033v"},
  {K_Alt_B,             "\033b"},
  {K_Alt_N,             "\033n"},
  {K_Alt_M,             "\033m"},
  {K_Alt_Comma,         "\033<"}, /* our reader cannot distinguish between */
  {K_Alt_Period,        "\033>"}, /* Alt-. and Alt->, so we cheat a little */
  {K_Alt_Slash,         "\033?"}, /* ditto, to get Alt-?                   */
  {K_Alt_Backspace,     "\033\177"}, /* M-DEL, to delete word backwards */
  {K_Alt_1,             "\033\061"},
  {K_Alt_2,             "\033\062"},
  {K_Alt_3,             "\033\063"},
  {K_Alt_4,             "\033\064"},
  {K_Alt_5,             "\033\065"},
  {K_Alt_6,             "\033\066"},
  {K_Alt_7,             "\033\067"},
  {K_Alt_8,             "\033\070"},
  {K_Alt_9,             "\033\071"},
  {K_Alt_0,             "\033\060"},
  {K_Alt_Dash,          "\033\055"},
  {K_Alt_EPageUp,       "\033\033[I"},
  {K_Alt_EPageDown,     "\033\033[G"},
  {K_Alt_Equals,        "\033\075"},
  {K_Alt_EDelete,       "\033\177"},
  {K_Alt_Tab,           "\033\011"},
  {0, 0}
};

/* Given a key, return the sequence of characters which
   our keyboard driver generates.  */
static unsigned const char *
find_sequence (int key)
{
  int i;

  for (i = 0; DJGPP_keytab[i].inkey; i++)
    if (key == DJGPP_keytab[i].inkey)
      return DJGPP_keytab[i].sequence;

  return NULL;
}

/* Return zero if a key is pending in the
   keyboard buffer, non-zero otherwise.  */
static int
kbd_buffer_empty (void)
{
  __dpmi_regs r;
  int retval;

  r.h.ah = 0x11;	/* Get enhanced keyboard status */
  __dpmi_int (0x16, &r);

  /* If the keyboard buffer is empty, the Zero Flag will be set.  */
  return (r.x.flags & 0x40) == 0x40;
}

/* The buffered characters pending to be read.
   Actually, Info usually reads a single character, but when we
   translate a key into a sequence of characters, we keep them here.  */
static unsigned char buffered[512];

/* Index of the next buffered character to be returned.  */
static int buf_idx;

/* Return the number of characters waiting to be read.  */
long
pc_term_chars_avail (void)
{
  if (buf_idx >= sizeof (buffered)) /* paranoia */
    {
      buf_idx = 0;
      buffered[buf_idx] = '\0';
      return 0;
    }
  else
    return strlen (buffered + buf_idx);
}

/* Our special terminal keyboard reader.  It will be called by
   low-level libc functions when the application calls `read' or
   the ANSI-standard stream-oriented read functions.  If the
   caller wants to read the terminal, we redirect the call to
   the BIOS keyboard functions, since that lets us recognize more
   keys than DOS does.  */
static int
keyboard_read (__FSEXT_Fnumber func, int *retval, va_list rest_args)
{
  /* When we are called, REST_ARGS are: file_descriptor, buf, nbytes.  */
  unsigned char *buf;
  size_t nbytes, nread = 0;
  int fd = va_arg (rest_args, int);

  /* Is this call for us?  */
  if (func != __FSEXT_read || !isatty (fd))
    return 0;	/* and the usual DOS call will be issued */

  buf = va_arg (rest_args, unsigned char *);
  nbytes = va_arg (rest_args, size_t);

  if (!buf)
    {
      errno = EINVAL;
      *retval = -1;
      return 1;
    }
  if (!nbytes)
    {
      *retval = 0;
      return 1;
    }

  /* Loop here until enough bytes has been read.  */
  do
    {
      int key;

      /* If any ``buffered characters'' are left, return as much
	 of them as the caller wanted.  */
      while (buffered[buf_idx] && nbytes)
	{
	  *buf++ = buffered[buf_idx++];
	  nread++;
	  nbytes--;
	}

      if (nbytes <= 0)
	break;

      /* Wait for another key.
	 We do that in a busy-waiting loop so we don't get parked
	 inside a BIOS call, which will effectively disable signals.
         While we wait for them to type something, we repeatedly
         release the rest of our time slice, so that other programs
         in a multitasking environment, such as Windows, get more cycles.  */
      while (kbd_buffer_empty ())
	__dpmi_yield ();

      key = getxkey ();

      /* Translate the key if necessary.
	 Untranslated non-ASCII keys are silently ignored.  */
      if ((key & 0x300) != 0)
	{
	  unsigned char const * key_sequence = find_sequence (key);

	  if (key_sequence != NULL)
	    {
	      strcpy (buffered, key_sequence);
	      buf_idx = 0;
	    }
	}
      else if (key == K_Control_Z)
	raise (SIGUSR1);	/* we don't have SIGTSTP, so simulate it */
      else if (key <= 0xff)
	{
	  *buf++ = key;
	  nbytes--;
	  nread++;
	}
    }
  while (nbytes > 0);

  *retval = nread;
  return 1;	/* meaning that we handled the call */
}

/* Install our keyboard handler.
   This is called by the startup code before `main'.  */
static void __attribute__((constructor))
install_keyboard_handler (void)
{
  __FSEXT_set_function (fileno (stdin), keyboard_read);

  /* We need to set this single hook here; the rest
     will be set by pc_initialize_terminal when it is called.  */
  terminal_initialize_terminal_hook = pc_initialize_terminal;
}

#endif /* __DJGPP__ */

/* **************************************************************** */
/*                                                                  */
/*                Emulation of SIGTSTP on Ctrl-Z                    */
/*                                                                  */
/* **************************************************************** */

#include <limits.h>
#include "signals.h"

#ifndef PATH_MAX
# define PATH_MAX 512
#endif

/* Effectively disable signals which aren't defined
   (assuming no signal can ever be zero).
   SIGINT is ANSI, so we expect it to be always defined.  */
#ifndef SIGUSR1
# define SIGUSR1 0
#endif
#ifndef SIGQUIT
# define SIGQUIT 0
#endif

int
kill (pid_t pid, int sig)
{
  static char interrupted_msg[] = "Interrupted\r\n";
  static char stopped_msg[] = "Stopped.  Type 'exit RET' to return.\r\n";
  char cwd[PATH_MAX + 1];

  if (pid == getpid ()
      || pid == 0
      || pid == -1
      || pid == -getpid ())
    {
      switch (sig)
	{
	RETSIGTYPE (*old_INT)(int), (*old_QUIT)(int);

	case SIGINT:
#ifdef __DJGPP__
	  /* If SIGINT was generated by a readable key, we want to remove
	     it from the PC keyboard buffer, so that DOS and other
	     programs never see it.  DJGPP signal-handling mechanism
	     doesn't remove the INT key from the keyboard buffer.  */
	  if (!kbd_buffer_empty ())
	    getxkey ();
#endif
	  pc_write_chars (interrupted_msg, sizeof (interrupted_msg) - 1);
	  exit (EXIT_FAILURE);
	case SIGUSR1:
	  /* Simulate SIGTSTP by invoking a subsidiary shell.  */
#ifndef _WIN32
	  pc_goto_xy (0, outside_info.screenheight - 1);
	  pc_clear_to_eol ();
	  pc_write_chars (stopped_msg, sizeof (stopped_msg) - 1);
#endif

	  /* The child shell can change the working directory, so
	     we need to save and restore it, since it is global.  */
	  if (!getcwd (cwd, PATH_MAX)) /* should never happen */
	    cwd[0] = '\0';

	  /* We don't want to get fatal signals while the subshell runs.  */
	  old_INT = signal (SIGINT, SIG_IGN);
	  old_QUIT = signal (SIGQUIT, SIG_IGN);
#ifdef _WIN32
	  {
	    const char *argv[2];
	    const char *shell = NULL;

	    argv[0] = NULL;
	    shell = getenv ("SHELL");
	    if (!shell)
	      {
		shell = getenv ("COMSPEC");
		if (!shell)
		  return -1;
		argv[0] = " /k";
	      }
	    argv[1] = NULL;
	    _spawnvp (_P_WAIT, shell, argv);
	  }
#else
	  system ("");
#endif
	  if (*cwd)
	    chdir (cwd);
	  signal (SIGINT, old_INT);
	  signal (SIGQUIT, old_QUIT);
	  break;
	default:
	  if (sig)
	    raise (sig);
	  break;
	}
      return 0;
    }
  else
    return -1;
}

/* These should never be called, but they make the linker happy.  */

int       tputs (const char *a, int b, int (*c)(int))
{
  perror ("tputs"); return 0; /* here and below, added dummy retvals */
}

char*     tgoto (const char *a, int b, int c)
{
  perror ("tgoto"); return 0;
}

int       tgetnum (char*a)
{
  perror ("tgetnum"); return 0;
}

int       tgetflag (char*a)
{
  perror ("tgetflag"); return 0;
}

char*     tgetstr (char *a, char **b)
{
  perror ("tgetstr"); return 0;
}

int       tgetent (char *a, const char *b)
{
  perror ("tgetent"); return 0;
}

int	tcgetattr(int fildes, struct termios *termios_p)
{
  perror ("tcgetattr"); return 0;
}

int	tcsetattr(int fd, int opt_actions, const struct termios *termios_p)
{
  perror ("tcsetattr"); return 0;
}