home *** CD-ROM | disk | FTP | other *** search
/ Microsoft Programmer's Library 1.3 / Microsoft_Programmers_Library.7z / MPL / msc / prftc.txt < prev    next >
Encoding:
Text File  |  2013-11-08  |  851.5 KB  |  22,571 lines

  1.  Proficient C
  2.  
  3.  
  4.  
  5.  The Microsoft(R) guide to intermediate and advanced C programming
  6.  
  7.  
  8.                                                    by AUGIE HANSEN
  9.  
  10.  
  11.  
  12.  PUBLISHED BY
  13.  Microsoft Press
  14.  A Division of Microsoft Corporation
  15.  16011 N.E. 36th Way, Box 97017, Redmond, Washington 98073-9717
  16.  
  17.  Copyright (C) 1987 by Augie Hansen
  18.  All rights reserved. No part of the contents of this book may be reproduced
  19.  or transmitted in any form or by any means without the written permission
  20.  of the publisher.
  21.  
  22.  Library of Congress Cataloging in Publication Data
  23.  Hansen, Augie
  24.  Proficient C.
  25.  Includes index.
  26.  1. C (Computer program language) I. Title.
  27.  QA76.73C15H367    1987    005.13'3    86-31109
  28.  ISBN 1-55615-007-5
  29.  
  30.  Printed and bound in the United States of America.
  31.  
  32.        4 5 6 7 8 9 FGFG 8 9 0 9 8 7
  33.  
  34.  Distributed to the book trade in the United States by Harper & Row.
  35.  
  36.  Distributed to the book trade in Canada by General Publishing Company,
  37.  Ltd.
  38.  
  39.  Distributed to the book trade outside the United States and Canada by
  40.  Penguin Books Ltd.
  41.  
  42.  Penguin Books Ltd., Harmondsworth, Middlesex, England
  43.  Penguin Books Australia Ltd., Ringwood, Victoria, Australia
  44.  Penguin Books N.Z. Ltd., 182-190 Wairau Road, Auckland 10, New Zealand
  45.  British Cataloging in Publication Data available
  46.  
  47.  
  48.  Microsoft(R) and MS-DOS(R) are registered trademarks of Microsoft
  49.  Corporation.
  50.  
  51.  
  52.  
  53.  This book is dedicated to my parents, who worried throughout my childhood
  54.  and teenage years that I would electrocute myself in my electronics
  55.  laboratory, and to my family, Doris, Lindsey, Reid, and Corri. They all
  56.  mean much more to me than I can ever tell them using mere words.
  57.  
  58.  
  59.  
  60.  Contents
  61.  
  62.  
  63.  
  64.        Acknowledgments
  65.  
  66.        Introduction
  67.  
  68.  SECTION I  Getting Started
  69.     1  The C Compiler System
  70.     2  Program Development
  71.     3  The DOS-to-C Connection
  72.  
  73.  SECTION II  Standard Libraries and Interfaces
  74.     4  Using Standard Libraries
  75.     5  PC Operating System Interfaces
  76.     6  The User Interface
  77.     7  Automatic Program Configuration
  78.  
  79.  SECTION III  File-Oriented Programs
  80.     8  Basic File Utilities
  81.     9  File Printing
  82.    10  Displaying Non-ASCII Text
  83.  
  84.  SECTION IV  Screen-Oriented Programs
  85.    11  Screen Access Routines
  86.    12  Buffered Screen-Interface Functions
  87.    13  The ANSI Device Driver
  88.    14  Viewing Files
  89.  
  90.  SECTION V  Appendixes
  91.     A  Microsoft C Compiler, Version 4.00
  92.     B  Other C Compilers
  93.     C  Characters and Attributes
  94.     D  Local Library Summary
  95.  
  96.      Index
  97.  
  98.  
  99.  
  100.  Acknowledgments
  101.  
  102.       Working with the management and staff at Microsoft
  103.  Press has been a distinct honor and a rewarding experience
  104.  in many ways. I am impressed by the dedication, skill, and
  105.  knowledge of all the individuals that I have dealt with
  106.  either directly or indirectly during the development and
  107.  preparation of Proficient C.
  108.       The person who is responsible for suggesting this book
  109.  to me is Claudette Moore. She helped me pare down the
  110.  original outline to something manageable, provided all the
  111.  needed software, arranged for Microsoft experts to answer my
  112.  questions, and kept things moving in the right direction.
  113.       Marie Doyle had the primary responsibility for editing
  114.  the manuscript. She has been tireless in her efforts to
  115.  obtain consistency and accuracy of the material and to
  116.  ferret out my hidden assumptions. Thanks also to Dori
  117.  Shattuck who participated in the early editing of the
  118.  project.
  119.       I am indebted to Jeff Hinsch for poring over all the
  120.  figures and source listings, testing all programs to verify
  121.  correct operation, and working with me at some pretty
  122.  strange hours to transfer files electronically.
  123.       Jim Beley has been involved throughout the project. He
  124.  was best known to me as the hit man ("Just calling to check
  125.  on our schedule ... "), but his efforts and those of his
  126.  staff are essential to the preparation and delivery of a
  127.  book. Jim made many helpful suggestions along the way to
  128.  improve the book and assure its timely delivery.
  129.       I also wish to thank Reed Koch, who reviewed the
  130.  manuscript and contributed many useful comments and
  131.  suggestions on the manuscript and the programs.
  132.       Many other individuals who have devoted themselves to
  133.  this project will probably never be known to me by name. I
  134.  appreciate their efforts nonetheless.
  135.  
  136.  
  137.  
  138.  Introduction
  139.  
  140.  
  141.  
  142.       Building a house that someone is willing to live in is quite an
  143.  undertaking. The process starts with a good design that makes sense in the
  144.  context of its surroundings and proceeds to completion through a series of
  145.  carefully planned steps. Throughout the process, the builder must select
  146.  the right materials and must employ the right tools to shape and join those
  147.  materials.
  148.       A computer programmer faces a similar challenge. He or she needs good
  149.  tools and needs to use them as effectively as a craftsperson in any other
  150.  profession or trade. Rather than hammers and saws, the programmer needs
  151.  assemblers, compilers, linkers, debuggers, and many other specialized
  152.  tools. In addition, the computer programmer can benefit from the work of
  153.  others by examining and using collections of ready-to-use program modules.
  154.  
  155.  
  156.  Objectives of This Book
  157.  
  158.       Proficient C is a book for programmers. It is primarily about writing
  159.  programs that are good tools for programmers. It demonstrates the use of C
  160.  in the development of nontrivial applications, so we will not waste any
  161.  time solving the Fibonacci series or calculating factorials. Although
  162.  programs that do such things are instructive, they are of little value to
  163.  us in building our tools.
  164.       Proficient C presents proven techniques for developing programs that
  165.  solve real problems. Now, any person with a modicum of business sense knows
  166.  that a problem is simply an opportunity in disguise, and some programmers
  167.  have been astute enough to detect this connection and turn it into piles
  168.  of hard cash. Whether or not money is your motivation for programming is
  169.  unimportant; if you are going to program for any reason, you should try to
  170.  do it well. This book is designed to help you do just that.
  171.       We will focus on creating building blocks--reusable modules that have
  172.  wide applicability. Being frugal with our time and energy, we will use
  173.  existing modules, such as those in the standard libraries that accompany
  174.  most C compilers, as much as possible. And, of course, we will use our own
  175.  modules again and again as we develop increasingly sophisticated programs.
  176.  
  177.  
  178.  Intended Audience
  179.  
  180.       Programmers with at least a modest level of experience in some high-
  181.  level language or assembly language will have an easy time going through
  182.  this book. A working knowledge of C or a similar language (such as Pascal,
  183.  ALGOL, Modula 2, or Ada) is assumed. Those readers without such experience
  184.  will be best served by first reading an introductory text on C. Several
  185.  good beginner- and intermediate-level C books are listed at the end of this
  186.  introduction.
  187.       I don't share the opinion that BASIC programmers are somehow crippled
  188.  by the experience of programming in BASIC, but I admit that many will find
  189.  some of the concepts embodied in C to have an alien feel. Topics like
  190.  indirection and recursion don't become immediately clear to most
  191.  programmers who encounter them for the first time. Study and practice are
  192.  required in order to become comfortable with them.
  193.  
  194.  
  195.  Hardware
  196.  
  197.       All functions and programs in this book were developed and tested in
  198.  an MS-DOS environment on an AT&T PC6300, and in a PC-DOS environment on an
  199.  IBM PC/AT. The programs will probably also work on other compatible
  200.  hardware that runs MS-DOS version 2.00 or later. With suitable compilers,
  201.  many of the programs in this book that are not dependent upon specific PC
  202.  features can easily be ported to other hardware and operating systems.
  203.  That's one of the many beauties of C.
  204.       You are well advised to have as much primary and secondary storage as
  205.  possible. A comfortable amount of random access memory (RAM) is 512
  206.  kilobytes. This allows room for a RAM disk to speed some operations. A hard
  207.  disk is also recommended. These recommendations are based on the fact that
  208.  most modern C compilers in the PC marketplace are large and are usually
  209.  accompanied by massive libraries of standard functions. C compilers will
  210.  function on less well-endowed systems, but the cost is usually diminished
  211.  performance.
  212.  
  213.  
  214.  Software
  215.  
  216.       Many of the programs in this book require MS-DOS or PC-DOS, version
  217.  2.00 or later. For the most part, I will refer to DOS in the generic sense.
  218.  I will use the terms MS-DOS and PC-DOS in contexts where the difference is
  219.  noticeable. (There are very few of these, limited mostly to a few external
  220.  commands that may be available in one version and not in another.)
  221.       A good text editor is critical to success in large programming
  222.  projects. You can use the DOS EDLIN editor if you're a bit of a masochist,
  223.  but I recommend that you acquire a good programmer's editor like EDIX
  224.  (Emerging Technology Consultants, Inc.), Brief (Solution Systems), or
  225.  Epsilon (Lugaru Software, Ltd.). You may also use Microsoft Word, WordStar,
  226.  or any other word processor that permits files to be saved in a straight
  227.  ASCII (unformatted) form.
  228.       Without a C compiler, of course, this book will be of little value to
  229.  you. The programs were written using Microsoft C, version 4.00, but other C
  230.  compilers can be used instead. I have tried to minimize the use of features
  231.  of one compiler that would prevent the use of others of comparable
  232.  capability. Appendix B describes several other C compilers and what, if
  233.  anything, must be done to compile the programs presented in this book
  234.  using those compilers.
  235.       The DOS linker (LINK) provided with the operating system is used to
  236.  combine object files and library modules into executable programs. A
  237.  version of the linker that understands DOS pathname conventions is a
  238.  necessity. Microsoft provides updated versions of the linker with all of
  239.  its language products.
  240.  
  241.  
  242.  Organization
  243.  
  244.       Section I describes the C compiler system and presents some views on
  245.  program development. In Chapter 1, we look at the proposed ANSI standard
  246.  for C and at compilers that attempt to track this moving target. Chapter 2
  247.  explores ways in which program development can be made more of a science
  248.  than an art. Chapter 3 shows how C is used in a DOS setting with emphasis
  249.  placed on effective use of the DOS command-line and environment variables.
  250.       Section II describes standard libraries and interfaces. Chapter 4
  251.  focuses on the use of the many existing functions in the standard C
  252.  libraries. In Chapter 5, we explore the low-level interface to DOS from
  253.  our C programs. Although not portable to other operating systems, such as
  254.  UNIX/XENIX, these functions are important for certain classes of programs
  255.  running on the PC. Additional functions built on top of the standard
  256.  libraries are used at a higher level to manage the all-important
  257.  user/machine interface. These functions are the topic of Chapter 6. In
  258.  Chapter 7, we automate program configuration, but allow the user to
  259.  override the automatic settings in various ways.
  260.       Section III presents a useful set of file-oriented programs.
  261.  Chapter 8 provides several UNIX-like file and directory utilities,
  262.  including an LS program to list a directory in special formats, a TOUCH
  263.  command to assist the MAKE program maintainer, and an RM command. This
  264.  orientation to basic file utilities leads to the development of a versatile
  265.  print command (PR) in Chapter 9. Chapter 10 deals with ways of viewing
  266.  the contents of non-ASCII files.
  267.       Section IV switches gears, leaving the realm of line-oriented programs
  268.  to address the issues of screen-oriented programs. Chapter 11 introduces
  269.  the concept of a buffered screen interface, and the concept is brought to
  270.  fruition and expanded upon in Chapter 12, which covers screen-management
  271.  functions. Chapter 13 describes an interface package that uses the much-
  272.  maligned ANSI device driver provided with DOS. Chapter 14 presents a
  273.  visual means of viewing files.
  274.  
  275.  
  276.  Other Books on C
  277.  
  278.       Many introductory-level C books crowd the shelves in bookstores, with
  279.  most of the books introduced in the past four or five years due to the
  280.  rapidly increasing popularity of PCs and to the "discovery" that C is an
  281.  extremely compliant and versatile computer programming language. Here is a
  282.  list of some C books that my students and I have found to be valuable
  283.  learning aids.
  284.       The C Primer, 2nd Edition, by Les Hancock and Morris Krieger (McGraw-
  285.  Hill, 1985), is noted for its gentle introduction to programming in C. The
  286.  use of flowcharts and examples based on models familiar to everyone makes
  287.  this an easy-to-read and enjoyable book.
  288.       Programming in C, by Stephen G. Kochan (Hayden, 1983), has been my
  289.  mainstay as a teaching book for beginning programmers. It covers the
  290.  essentials of C without getting bogged down in superfluous detail.
  291.       C Primer Plus by Mitchell Waite, Stephen Prata, and Donald Martin
  292.  (SAMS, 1984), is replete with corny but memorable examples and
  293.  illustrations. It is a comprehensive book that gets a bit deeper into C
  294.  language than some of the other beginners' books.
  295.       In addition to these introductory books, you may want to take a pass
  296.  through Variations in C by Steve Schustack, another C book from Microsoft
  297.  Press (1985). It is oriented toward developers of business applications
  298.  and is loaded with advice on good programming style and many very useful
  299.  examples.
  300.       And every C programmer should have a copy of the ubiquitous The C
  301.  Programming Language by Brian Kernighan and Dennis M. Ritchie (Prentice-
  302.  Hall, 1978). Although it is somewhat dated now, it is a treasure trove of
  303.  C lore and law and is still considered the standard reference on the C
  304.  language.
  305.  
  306.  
  307.  Notational Conventions
  308.  
  309.       The following notational conventions apply to the text and examples in
  310.  this book:
  311.  
  312.         DOS program names are shown in capital letters (DIR, TYPE). The
  313.  names of programs we develop are treated the same way (PR, VE, NOTES) when
  314.  the executable program is being referred to.
  315.  
  316.         Names of C functions, variables, and keywords are shown in italics
  317.  (environ, fgets(), main(), #define). This same treatment is given to the
  318.  names of source and object files (myprog.c, myprog.obj) and user-supplied
  319.  function, constant, and variable names.
  320.         Command syntax follows these conventions:
  321.  
  322.  
  323.  ═══════════════════════════════════════════════════════════════════════════
  324.  ITEM                CONVENTION                 EXAMPLE
  325.  ───────────────────────────────────────────────────────────────────────────
  326.  literal text     │  roman type              │  cmd
  327.  placeholders     │  italic type             │  cmd option
  328.  repeat item      │  ellipses                │  cmd file . . .
  329.  optional item    │  square brackets ([])    │  cmd [opts] file
  330.  logical OR       │  vertical bar (|)        │  cmd [a | b] file . . .
  331.  ───────────────────────────────────────────────────────────────────────────
  332.  
  333.  
  334.  
  335.  SECTION I  Getting Started
  336.  
  337.  
  338.  
  339.  Chapter 1  The C Compiler System
  340.  
  341.  
  342.  
  343.       The tradition of C, originally established in its UNIX context, has
  344.  had a profound effect on the programming environments of a vast range of
  345.  machine types and sizes and has influenced the gamut of computer operating
  346.  systems. Whether you program on an S-100-bus 8080 system running CP/M or on
  347.  a CRAY-2 hosting a version of UNIX System V, or nearly anything in between,
  348.  there is probably a C compiler system available for your environment.
  349.       In this chapter, we will briefly describe the proposed American
  350.  National Standards Institute (ANSI) standard for C, some emerging standards
  351.  for operating systems, and extensions of those that affect our C
  352.  programming. Then we'll get a brief overview of the Microsoft C Compiler,
  353.  its various memory models, and C programming support tools.
  354.  
  355.  
  356.  Standards and Compatibility
  357.  
  358.       Anything that survives the test of time and that is likely to have a
  359.  wide base of support is a candidate for standardization. As standards
  360.  evolve, de facto or otherwise, we face the issue of compatibility with the
  361.  standard. So it is with C.
  362.  
  363.  
  364.       Proposed ANSI-Standard C
  365.  
  366.       The first attempt at standardizing C was the distribution of the "C
  367.  Reference Manual" written by Dennis M. Ritchie, the author of C. The "C
  368.  Reference Manual" was published as an appendix to the book The C
  369.  Programming Language by Brian Kernighan and Dennis M. Ritchie. For many
  370.  years, this book has been the only generally available description of C.
  371.  However, since the "C Reference Manual" was promulgated, there have been
  372.  numerous changes to C that are not supported by all C compiler suppliers,
  373.  so portability among computer systems, and even among compilers on the same
  374.  computer system, has been diminished.
  375.       The /usr/group Committee produced the "UNIX 1984 Standard" document to
  376.  attempt standardization of the UNIX operating system. One major aspect of
  377.  the /usr/group proposal is a standard set of library routines. Many of the
  378.  recommendations, but not all of them, have been incorporated into AT&T's
  379.  "System V Interface Definition," which defines a standard base for, among
  380.  other things, the Operating System Services and Other Library Routines, the
  381.  two major components of the standard library for C. The current de facto
  382.  standard for C is the UNIX System V implementation by AT&T.
  383.       In recent years, the American National Standards Institute's Technical
  384.  Committee on the C Programming Language--Committee X3J11--has been working
  385.  to produce the first official American National Standard for C. Committee
  386.  X3J11 is composed of members from various industrial companies, educational
  387.  institutions, and government agencies.
  388.       The proposed ANSI standard for C is an attempt to "promote
  389.  portability, reliability, maintainability, and efficient execution of C
  390.  language programs on a variety of computing systems." The draft standard
  391.  uses the "C Reference Manual" and the "UNIX 1984 Standard" as base
  392.  documents for the language and the library, respectively.
  393.       The following list describes some of the features that have been added
  394.  to C or changed since its original description and points out some of the
  395.  implications of the new features and changes.
  396.  
  397.         The enum type specifier has been added, giving C an enumeration
  398.          data type.
  399.  
  400.         The void keyword can be applied to functions that do not return a
  401.          value. A function that does return a value can have its return
  402.          value cast to void to indicate to the compiler (and lint, under
  403.          UNIX/XENIX) that the value is being deliberately ignored.
  404.  
  405.         Structure handling has been greatly improved. The member names in
  406.          structure and union definitions need not be unique. Structures can
  407.          be passed as arguments to functions, returned by functions, and
  408.          assigned to structures of the same type.
  409.  
  410.         Function declarations can include argument-type lists (function
  411.          prototyping) to notify the compiler of the number and types of
  412.          arguments.
  413.  
  414.         Hexadecimal character constants can be expressed using an
  415.          introductory \x followed by from one to three hexadecimal digits
  416.          (0--F). Example: 16 decimal=\x10, which can be written as 0x10
  417.          using the current notation.
  418.  
  419.         The # in preprocessor directives can have leading white space (any
  420.          combination of spaces and tabs), permitting indented preprocessor
  421.          directives for clarity. In addition, new preprocessor directives
  422.          have been added:
  423.  
  424.              #if defined (expression)
  425.              #elif (expression)
  426.  
  427.  
  428.       Standard Run-Time Library Routines
  429.  
  430.       The standard libraries that accompany most C compilers are standard
  431.  because we accept them as such, not because of any law. Over many years and
  432.  millions of lines of C source code, programmers have evolved a set of
  433.  often-used routines that have been polished and packaged into libraries.
  434.  Some of the routines, such as read() and write(), provide system-level
  435.  services and are essentially our hooks into the underlying operating
  436.  system. Other routines--string-handling functions and macros, for example--
  437.  are part of the external subroutine library. (In Chapter 4, we will examine
  438.  some representative standard library functions and present examples of
  439.  their uses in programs.)
  440.       In the last few years, some new additions have been made to the
  441.  standard libraries. Among them are a set of buffer-management routines,
  442.  some new string-handling routines, improved I/O error reporting, and
  443.  additional debugging support routines. The proposed ANSI standard specifies
  444.  a basic set of system-level and external routines.
  445.  
  446.  
  447.  Microsoft C, Version 4.00
  448.  
  449.       The Microsoft C Compiler, version 4.00, adheres closely to the
  450.  proposed ANSI standard. I will briefly describe the compiler system and its
  451.  features, to set the stage for the programs that follow. Appendix A
  452.  provides a detailed description.
  453.       The Microsoft C Compiler is a three-pass optimizing compiler. The
  454.  passes are named C1, C2, and C3. These passes could be called directly, but
  455.  in practice they should not be. We use one of the control programs MSC and
  456.  CL to invoke them for us. MSC is the primary compiler control program. With
  457.  MSC, the linker must be called separately to combine program object files
  458.  and library modules into an executable program. CL is similar in operation
  459.  to the UNIX/XENIX C compiler control program cc. It invokes both the
  460.  compiler passes and the linker, as needed, to produce an executable
  461.  program.
  462.       The Microsoft C Compiler uses DOS environment variables to locate
  463.  libraries and header files in special disk directories. These are normally
  464.  set to \lib and \include, respectively. The compiler will use a designated
  465.  temporary area on disk if the variable TMP is defined. The temporary area
  466.  can be a virtual disk to speed disk-intensive processing.
  467.       If the machine used at run time has a math coprocessor, the
  468.  coprocessor is used automatically. Floating-point emulation is used if no
  469.  coprocessor is installed. Thus, it is not necessary to have separately
  470.  compiled versions of the same program for differently configured
  471.  systems.
  472.       The material in this book was developed on personal computers running
  473.  MS-DOS and the Microsoft C Compiler, version 4.00. But many of the programs
  474.  presented here can be moved with little or no change to other hardware,
  475.  other operating systems, and other C compilers. Appendix B describes
  476.  several other major C compilers and how they can be used to compile the
  477.  programs in this book.
  478.       The primary areas that preclude unqualified portability to other
  479.  environments are related to the occasional use of special keys on the
  480.  PCkeyboard, to screen updating via BIOS and direct-access routines, and to
  481.  the use of other hardware elements of the PC, such as built-in timers and
  482.  reserved memory areas. Where possible, dependencies on PC hardware have
  483.  been avoided. One of the goals of the book is to show off some of the
  484.  marvelous capabilities of the PC and the Microsoft C Compiler, so by
  485.  design, some of the material is not portable to other environments without
  486.  some changes.
  487.  
  488.  
  489.       Memory Models and Uses
  490.  
  491.       Microsoft C supports programs up to one megabyte in size. It provides
  492.  five primary memory models plus some extensions that let you produce mixed
  493.  memory models to meet special needs. The memory models are defined by the
  494.  following table:
  495.  
  496.  ═══════════════════════════════════════════════════════════════════════════
  497.    MODEL                     SIZES
  498.  ───────────────────────────────────────────────────────────────────────────
  499.                  Code        Data         Arrays
  500.                ─────────────────────────────────────────────────────────────
  501.  
  502.  small         │ 64 KB     │ 64 KB     │  64 KB
  503.  medium        │ 1 MB      │ 64 KB     │  64 KB
  504.  compact       │ 64 KB     │ 1 MB      │  64 KB
  505.  large         │ 1 MB      │ 1 MB      │  64 KB
  506.  huge          │ 1 MB      │ 1 MB      │  >64 KB
  507.  ───────────────────────────────────────────────────────────────────────────
  508.  
  509.  
  510.       Support Tools
  511.  
  512.       When we talk about a C compiler, we usually implicitly include a set
  513.  of support tools that make up a working C compiler system. We'll now
  514.  examine some of the important support tools that enhance the C programming
  515.  environment.
  516.  
  517.       LINK, the DOS Linker  Regardless of what language you program in, you
  518.  will use the DOS LINK program, the object linker provided with DOS.
  519.  Depending upon which version of DOS and which language product you have,
  520.  you may have to upgrade to a newer version of the linker. The linker must
  521.  understand DOS pathnames and, when used with the Microsoft C Compiler, must
  522.  be able to access the values in DOS environment variables that point to the
  523.  library and include file directories.
  524.       The version of LINK that is used to link the programs in this book is
  525.  version 3.51. It was shipped with the version 4.00 C compiler. This version
  526.  of LINK provides a single-level overlay-linking capability that permits
  527.  memory space to be shared sequentially by several program modules.
  528.  Earlier versions of LINK, back to version 3.12, will also work, but they do
  529.  not have the overlay capability of the 3.51 version. I chose not to use
  530.  overlays in this book, so there is no requirement for an overlay linker.
  531.  
  532.       LIB, the Object-File Library Maintainer  A library in the computer
  533.  programming context is a collection of compiled or assembled functions. The
  534.  functions in a given library are typically related to each other by some
  535.  common factor, such as the role they serve in handling one major
  536.  programming task. LIB is provided with Microsoft language products. It
  537.  allows us to create, organize, and maintain run-time libraries.
  538.       The linker calls on various libraries to resolve external names during
  539.  a linker session. The linker automatically looks for the standard C library
  540.  (slibc.lib, mlibc.lib, and so on) for the appropriate memory model. It
  541.  looks in the current directory unless the DOS environment variable LIB
  542.  tells it to look elsewhere, or you provide a list of other libraries to
  543.  search.
  544.       We will make extensive use of the libraries provided with our
  545.  compilers, and we will create some libraries of our own. (Our library
  546.  strategy is documented in Chapter 2.) Nearly all of the service routines
  547.  we write in this book will end up in an object library somewhere. Appendix
  548.  D summarizes all object library functions developed in this book.
  549.  
  550.       MAKE, the UNIX-Style Program Maintainer  Any repetitive task begs to
  551.  be automated. MAKE is an automated program maintainer that emulates the
  552.  basic behavior of the UNIX MAKE program. I will present information on the
  553.  use of MAKE in building some of the programs in this book. Because my
  554.  programs, even some of the simple ones, are usually broken into a number of
  555.  small modules, it is important to have a tool that takes most of the
  556.  drudgery out of maintaining the programs as changes are made to them during
  557.  the development and lifetime maintenance periods.
  558.       MAKE performs its magic by applying rules, both built-in and user-
  559.  supplied, to keep each object file up to date with its sources. The user-
  560.  supplied rules reside in a "makefile" that lists all the modules of a
  561.  program; the dependencies of objects on sources, header files, and
  562.  libraries; and the recipe for how to connect the pieces. MAKE is equally
  563.  able to control documentation projects and other activities that manage
  564.  groups of disk files.
  565.       The next chapter describes the design and development process espoused
  566.  in this book and shows the relationships of the various activities involved
  567.  in building a program. MAKE plays an important role in the process.
  568.  
  569.       CodeView, the Symbolic Debugger  With the release of version 4.00 of
  570.  the Microsoft C Compiler, Microsoft unleashed CodeView, a spectacular
  571.  window-oriented, source-level symbolic debugger.
  572.       Anyone who has done even a modest amount of programming knows the
  573.  sense of frustration that accompanies a bug hunt. A program that should
  574.  take a couple of hours to design, write, and test can cost you many hours
  575.  of anguish because of a subtle, hard-to-find bug. CodeView has the
  576.  debugging capabilities and operating features needed to shrink the
  577.  debugging task down to manageable proportions.
  578.       The debugger is a logical extension of the DOS DEBUG program and
  579.  Microsoft's SYMDEB, so knowledgeable users of those programs will find
  580.  CodeView very easy to learn. Using multiple windows, CodeView allows you to
  581.  view source code, disassemble object code, and monitor program variables,
  582.  CPU registers, and the stack. Figure 1-1 is a sample CodeView screen that
  583.  reveals some of the primary features of the debugger.
  584.       CodeView works on color or monochrome systems, can handle a 43-line
  585.  EGA (enhanced graphics adapter) screen, and supports both mouse and
  586.  keyboard user interfaces.
  587.  
  588.       MASM, the Macro Assembler  The Microsoft C Compiler produces object
  589.  files in the Microsoft object format, so there is no need to run a separate
  590.  assembler pass. For information about MASM and ASM, see Advanced MS-DOS by
  591.  Ray Duncan, Microsoft Press, 1986.
  592.  
  593.               ╔══════════════════════════════════════════╗
  594.               ║                                          ║
  595.               ║    Figure 1-1 is found on page 10        ║
  596.               ║    in the printed version of the book.   ║
  597.               ║                                          ║
  598.               ╚══════════════════════════════════════════╝
  599.  
  600.        FIGURE 1-1 ▐ Sample CodeView screen display
  601.  
  602.  
  603.  
  604.  Chapter 2  Program Development
  605.  
  606.  
  607.  
  608.       Program development means different things to different people. The
  609.  particular interpretation often depends on whether the question is asked of
  610.  a programmer who always works independently or one who normally functions
  611.  as a member of a team.
  612.       Whether done by an independent programmer or a team of programmers,
  613.  program development is a blending of art and science and is an iterative
  614.  process. For anything but trivial projects, we rarely know exactly how we
  615.  will do the job before actually starting to do it. We can usually make some
  616.  good guesses, but there is often an element of discovery involved. As long
  617.  as something is learned each time through the loop, and as long as mistakes
  618.  are not repeated, we will likely make some headway.
  619.       The science part is somehow keeping ourselves headed in the right
  620.  direction, capturing needed information during the course of a project, and
  621.  effectively applying what is learned. The art is in knowing what to try
  622.  when all else fails and in being able to conjure up the correct data
  623.  structures and algorithms to solve seemingly intractable problems.
  624.       There is usually at least one approach to solving a problem that makes
  625.  the task easier than do other approaches. Experience and imagination are
  626.  your two most important allies in pursuit of this approach. The experience
  627.  need not be all yours, either--try to borrow existing ideas and adapt them
  628.  to your purpose if there is a reasonable fit. And be pleased to have other
  629.  programmers borrow your ideas--it's a sure sign of success.
  630.  
  631.  
  632.  Guiding Principles
  633.  
  634.       During the past 10 years or so, I have had the good fortune to be both
  635.  an observer and a participant on solo and group programming efforts. As a
  636.  technical writer and publications supervisor, I was able to see the good,
  637.  the bad, and the ugly aspects of very large programming efforts. As a
  638.  developer, I saw firsthand how the development process can be made to work
  639.  well and how it can be made to fail pitifully.
  640.       The consistent and careful application of structured design and the
  641.  incremental development of both programs and the documents that describe
  642.  them are critical factors in the success of programming projects. I suspect
  643.  that the exact method used has less bearing on the outcome of a project
  644.  than does the fact that the participants consciously attempt to control
  645.  what is going on rather than just letting it happen.
  646.       This book encourages a heavy dependence on the development of reusable
  647.  modules. The concept of creating reusable modules--essentially stockpiling
  648.  programming pieces--seems obvious in retrospect, but it was quite a
  649.  revelation to a generation of programmers 10 to 15 years ago. The standard
  650.  library is a wonderful example of the concept coming to fruition. There is
  651.  something elegant about a function that, for example, copies a string onto
  652.  the end of another string and returns a pointer to the start of the
  653.  resulting string. Variations of this technique pervade the standard library
  654.  and give us options in using the library routines that we would not have if
  655.  the routines had been designed in a less general way.
  656.       The point here is that we should keep an eye focused on the future use
  657.  of what we are doing now. First, strive to get something working, but don't
  658.  quit too soon. Refine it, generalize it, and make it available for future
  659.  use, both by yourself and by others. The extra time spent in this endeavor
  660.  will be paid back many times over in saved project time. Also, don't
  661.  neglect to document what you do, both in comments within the sources and in
  662.  separate manual-style documentation.
  663.  
  664.  
  665.  The Development Cycle
  666.  
  667.       The first step in the development cycle is determining what must be
  668.  done. This is the problem-definition phase and is the time when we
  669.  determine the user's needs, investigate the operating environment, and try
  670.  to get a feel for the boundary conditions that will influence our
  671.  design.
  672.       The next phase, program design, is anything but a straightforward
  673.  process. We need to keep our minds open to many alternatives. We should
  674.  attempt paper designs, perhaps written in pseudocode, which is a human-
  675.  language description disguised as program code. Our hope is that at least
  676.  one of the paper designs will lead to a solution that can be implemented. A
  677.  major objective at this stage is to produce a language-independent solution
  678.  to the problem at hand so that we have options later, in terms of choosing
  679.  a language that best fits the circumstances.
  680.       When we have what we believe is a workable solution, we can select a
  681.  language and start coding. I prefer to use C because of its compactness and
  682.  versatility, but I sometimes use Pascal, Assembler, or BASIC in my work.
  683.  The choice should be based on factors such as the "lingua franca" of the
  684.  host environment (local custom), whether any work has already been done on
  685.  the problem in a particular language (invested value), and whether any of
  686.  the current investment is worth saving (sometimes it isn't). But telling a
  687.  hundred assembly-language programmers that from today forward all work will
  688.  be in C is potentially risky business. It might be easier to learn yet
  689.  another assembly language!
  690.       The next phase of the program-development cycle is an important one
  691.  that is often overlooked. Testing should be planned right along with
  692.  development, and should be done either by the programmer of the item to be
  693.  tested or by an associate working closely with the programmer. As soon as
  694.  the code is done, there should be a way to exercise it to see that the
  695.  program does what it is supposed to do, doesn't do anything it shouldn't,
  696.  and protects itself from possible adverse effects of unwanted inputs.
  697.       It is not too hard to check a program for correct behavior in the
  698.  presence of expected inputs. This is, of course, important. But the
  699.  behavior of a program in the face of unexpected inputs is equally
  700.  important. Writing a program that defends itself well is a bit of a
  701.  challenge and requires a somewhat perverse mentality on the part of the
  702.  programmer and the tester.
  703.       Checking boundary conditions, handling errors promptly and correctly,
  704.  and generally just being on guard for the conditions that could make your
  705.  program crumble is something that requires discipline and practice. Be sure
  706.  to check the return codes from routines that flag errors. Standard library
  707.  I/O routines are particularly good about telling you when something goes
  708.  wrong (full disk, bad filename, and so forth). Don't let this vital
  709.  information fall on the floor.
  710.       As I said earlier, the program-development process is an iterative
  711.  one. At any point in the process, don't be afraid to pull the plug and
  712.  start over if you find yourself traveling the wrong path. If necessary, go
  713.  to completion, refine the original definition, and do the job again until
  714.  it's right. You'll be glad you did.
  715.  
  716.  
  717.  Our Local Environment
  718.  
  719.       For the purposes of our development work in this book, we will set up
  720.  some directories for header files and libraries. The hardware and software
  721.  assumptions listed in the Introduction apply. We will use the Microsoft
  722.  recommendations for the standard run-time libraries and supplied header-
  723.  file locations. Any header and library files that we create will be placed
  724.  in the new directories \include\local and \lib\local, respectively. This
  725.  practice precludes one of our homegrown files from clobbering one of the
  726.  files provided with the compiler if we accidentally use the same name. It
  727.  also collects all of our own files together for convenient access.
  728.       We will be preparing several header files as we go on through this
  729.  book. For now, we'll start with the file std.h, which contains some basic
  730.  information needed by many of our C source files.
  731.       In addition to defining some often-used constants, std.h contains a
  732.  definition of a Boolean data type (using the enum type specifier) and some
  733.  aliases for standard data types that have long names (unsigned short, for
  734.  example). I used to use these often, but as my typing speed increased, I
  735.  began to use the original names. Feel free to use whatever you find most
  736.  natural (or easiest to type).
  737.       We can use the preprocessor to retrieve our local header files in a
  738.  way that is consistent with the approach used for standard header files.
  739.  
  740.       The preprocessor directive
  741.  
  742.       #include <local\std.h>
  743.  
  744.  tells the preprocessor to read in the text of the file \include\local\std.h
  745.  because we previously set the DOS environment variable INCLUDE to
  746.  \include.
  747.  
  748.  
  749.               ╔══════════════════════════════════════════╗
  750.               ║                                          ║
  751.               ║ Click to view the listing found on page ║
  752.               ║ 15 in the printed version of the book.   ║
  753.               ║                                          ║
  754.               ╚══════════════════════════════════════════╝
  755.  
  756.  
  757.       Similarly, our local library directory will eventually contain a
  758.  utility library, util.lib, a pair of operating-system interface libraries,
  759.  dos.lib and bios.lib, and several other special-purpose libraries. The
  760.  linker must always be given the correct name for the directory that
  761.  contains the library files. We will arrange for this to be done
  762.  automatically by using a makefile that includes pathname specifiers for the
  763.  required directories. Then we'll let MAKE do all the hard work. Isn't high
  764.  tech wonderful?
  765.       Now it's time to do some programming. We begin in Chapter 3 with an
  766.  examination of how our C programs interact with the MS-DOS system.
  767.  
  768.  
  769.  
  770.  Chapter 3  The DOS-to-C Connection
  771.  
  772.  
  773.  
  774.       DOS is, of course, the very popular and widely used operating system
  775.  for the IBM PC and other personal computers that have a comparable 16-bit
  776.  architecture. Proficient C is designed to help you write C programs in a
  777.  DOS environment.
  778.       To obtain the greatest benefit from our PCs, we must understand both
  779.  the C language and the DOS environment in which we program. In addition, we
  780.  would like to exploit the similarities among DOS, UNIX, and XENIX for some
  781.  classes of programs, especially those that don't require the use of
  782.  specialized hardware or direct screen access.
  783.       From the beginning, DOS has had some features in common with UNIX and
  784.  its many descendants, including XENIX. Among these similarities are basic
  785.  aspects of command syntax (copy from to, rather than the "backward"
  786.  approach of CP/M); the use of a common "file" orientation for all data
  787.  streams, including the console (keyboard and screen) and the printer; and a
  788.  batch-processing capability (.BAT files under DOS and shell scripts under
  789.  UNIX). Starting with version 2.00, the operating system acquired many more
  790.  features that are reminiscent of those provided by UNIX. Among the new
  791.  features were a hierarchical file system and related support for a hard
  792.  disk, installable device drivers for customization, a user-controllable
  793.  environment, and input/output (I/O) redirection. These important features
  794.  added greatly to the power and flexibility of DOS.
  795.       In this chapter, we will examine I/O redirection, command-line
  796.  processing, and access to the DOS environment. Some aspects of each of
  797.  these topics will be involved in the construction of the programs that are
  798.  presented in the remainder of this book.
  799.       Proficient C makes extensive use of the routines in the C run-time
  800.  library. In this chapter, we use only a few of the more common library
  801.  routines. A detailed discussion of the C run-time library is presented in
  802.  the next chapter (Chapter 4). We defer discussion of device drivers until
  803.  Chapter 13.
  804.  
  805.  
  806.  Input/Output Redirection and Piping
  807.  
  808.       By way of a few simple examples, we will quickly review some of the
  809.  basics of input and output and set the stage for considerably more detailed
  810.  work using the I/O features of the C run-time library, a marvelous
  811.  collection of prepackaged routines.
  812.       Program I/O is based on the concept of standard streams: standard
  813.  input (stdin), standard output (stdout), and standard error (stderr). The
  814.  stdin stream is usually "connected" to the keyboard, and both the stdout
  815.  and stderr are usually connected to the screen. Under DOS, both stdin and
  816.  stdout can be redirected to other devices, but stderr always goes to the
  817.  screen.
  818.       Because all references to I/O devices are treated like files under
  819.  DOS, we can make ad hoc changes in the source or destination of data being
  820.  processed by a program. The output of the DIR command, for example,
  821.  normally goes directly to the screen (stdout). But with a minor change to
  822.  the command line, we can send the directory listing directly to a printer
  823.  by redirecting the DIR command's standard output. The command
  824.  
  825.       dir > prn
  826.  
  827.  tells DOS to feed the data that DIR normally sends to the console screen to
  828.  the default printer device instead. Thus, the standard output of DIR is
  829.  redirected.
  830.       We can similarly redirect input to a command. The DOS MORE command is
  831.  a good example. MORE is classified in the DOS manual as a filter--a program
  832.  that is designed to read from the standard input device, perform a
  833.  transformation of some kind on that input, and produce some output on the
  834.  standard output device. The transformation performed by MORE is to parcel
  835.  out the input it receives in screen-sized chunks. MOREcan be used as the
  836.  termination of a pipeline--a series of programs connected to each other by
  837.  a pipe, symbolized by the vertical bar (|). The DOS pipe is actually
  838.  implemented as a pair of redirections. The output of one program is
  839.  redirected to a specially named disk file, which then becomes the input
  840.  to the next program in the pipeline.
  841.  
  842.  
  843.       ┌────────────────────┐                             ┌─────────────┐
  844.       │ ┌────────────────┐ │                             │             │
  845.       │ │                │ │                  stdout     │             │
  846.       │ │                ├─┤────────────────────────────┤             │
  847.       │ │     screen     ├─┤                  stderr     │             │
  848.       │ │                │.│────────────────────────────┤             │
  849.       │ └────────────────┘▓│                             │             │
  850.       └────────────────────┘                             │             │
  851.                                                          │   program   │
  852.                                                          │             │
  853.                                                          │             │
  854.        ┌───────────────────────────────┐                 │             │
  855.        │┌─┬────────┬────────┬─┐        │                 │             │
  856.        ││▒│▒▒▒▒▒▒▒▒│▒▒▒▒▒▒▒▒│▒│  ┌────┐│                 │             │
  857.        ││                     │  │    ││      stdin      │             │
  858.        ││▒  keyboard  ▒▒▒     └┐ │▒   │├────────────────│             │
  859.        │└┐▒▒▒             ▒┌┐▒┌┘ │ ▒▒ ││                 │             │
  860.        │ └─────────────────┘└─┘  └────┘│                 │             │
  861.        └───────────────────────────────┘                 └─────────────┘
  862.  
  863.       FIGURE 3-1 ▐ Default standard I/O streams
  864.  
  865.  
  866.       The ability to join individual programs into custom-designed
  867.  collections via the pipe mechanism is an important consideration in the
  868.  design of an operating system. We can easily plug the output of one program
  869.  into the input of another to perform special processing. To control the
  870.  output of the TYPE command, for example, we issue the following
  871.  command:
  872.  
  873.       type myfile.txt | more
  874.  
  875.  This command will display no more than a full screen of text at a time,
  876.  allowing the user to page through the text at a leisurely pace rather than
  877.  see the text flash by and scroll off the screen before it can be read.
  878.       The same effect can be achieved by redirecting input to MORE using the
  879.  command
  880.  
  881.       more < myfile.txt
  882.  
  883.  We have redirected the standard input of MORE in this case.
  884.  
  885.       To illustrate the use of redirection and piping techniques, here
  886.  is a program called CP, which is a highly simplified version of the DOS
  887.  COPY command. It is written as a filter in that it copies its standard
  888.  input (stdin) to its standard output (stdout) one character at a time.
  889.  Strictly speaking, however, CP is not a filter, because it simply passes
  890.  its input to its output without modification.
  891.  
  892.  /*
  893.   *      cp -- a simplified copy command
  894.   */
  895.  
  896.  #include <stdio.h>
  897.  
  898.  main()
  899.  {
  900.          int ch;
  901.  
  902.          while ((ch = getc(stdin)) != EOF)
  903.                  putc(ch, stdout);
  904.          exit(0);
  905.  }
  906.  
  907.       This program should look familiar to anyone who has studied C
  908.  programming at any level. Although very simple, CP allows us to copy a file
  909.  from one place in a file system to another with ease. It has some serious
  910.  limitations that we will address as we proceed through this chapter--for
  911.  one thing, CP does no error checking, so it depends on DOS to do that work
  912.  on its behalf. In addition, CP takes no filenames on the command line, so
  913.  all input must be via redirection, which requires extra typing.
  914.       CP simply gathers a single character at a time from the standard input
  915.  and checks to see whether it is the EOF (end-of-file) indicator. If the
  916.  input is not EOF, it is immediately sent to the standard output. If EOF is
  917.  detected, the processing loop terminates, and so does the program.
  918.       EOF is defined in stdio.h as -1. This is why the character-storage
  919.  location ch is declared to be an int instead of a char data type. Since
  920.  char can be signed or unsigned (depending on system implementation), we
  921.  must always be prepared to detect the EOF flag to terminate the copying
  922.  process.
  923.       The getc() and putc() library routines are implemented as macros and
  924.  are defined in stdio.h. They are fast and use the standard I/O library
  925.  user-buffering scheme. A buffer of BUFSIZ bytes (512 bytes in the Microsoft
  926.  C implementation) is used for reading and writing operations. When getc()
  927.  attempts to read past the end of an input stream, it returns EOF.
  928.       There are several ways to use CP. Simply typing CP on the DOS command
  929.  line causes the program to take input from the keyboard and display a copy
  930.  of it on the console screen. You will see the text you type twice, once as
  931.  you type it, because DOS is echoing your input, and a second time after you
  932.  type a carriage return to end your input. User input may be terminated by
  933.  typing Ctrl-Z followed by a carriage return.
  934.  
  935.  C>cp
  936.  This is a line of text.
  937.  This is a line of text.
  938.  ^Z
  939.  
  940.  C>
  941.  
  942.       To copy the contents of a file to the screen, you type the
  943.  command
  944.  
  945.       CP < filename
  946.  
  947.  just as you would with the MORE command. To create a new file (or update an
  948.  existing file), you can type
  949.  
  950.       CP >> newfile
  951.  
  952.  and CP will copy your keyboard input into newfile until you type Ctrl-Z
  953.  (the DOS end-of-file marker).
  954.       Although this simple program is useful, it would be much more
  955.  versatile if it could accept optional parameters on the invocation
  956.  commandline, such as the names of files to process, and control information
  957.  to tailor the processing to meet user needs. The next section explores the
  958.  use of command arguments and applies them in several demonstration
  959.  programs.
  960.  
  961.  
  962.  Command-line Processing
  963.  
  964.       A program's invocation command line may contain optional arguments in
  965.  addition to the program name. The main() function of a C program is usually
  966.  written in such a way as to look first for optional arguments and then for
  967.  filenames or other parameters.
  968.       Under UNIX, the convention is to start optional arguments with a
  969.  leading dash. DOS, however, uses a forward slash (/) to specify options, or
  970.  what are commonly referred to as switches. (UNIX uses the forward slash as
  971.  the pathname separator. Since DOS was not originally so close in spirit to
  972.  UNIX as it is now, DOS's use of the forward slash as a switch flag is
  973.  forgivable. However, this choice made it necessary to use something else
  974.  for a separator in pathnames when they were introduced in DOS version 2.00,
  975.  so DOS designers decided to use the backslash (\) as the pathname
  976.  separator.) I have elected to use the leading dash as an option flag, but
  977.  you can easily modify the code to allow the use of /, or to accept either
  978.  form, as is done in the Microsoft C Compiler programs.
  979.       Arguments are passed to our programs by the C run-time startup
  980.  function. A count of arguments, argc, and an array of pointers to character
  981.  strings, argv, are passed as arguments to main() (along with envp, which
  982.  we'll look at in detail shortly). If a program needs to access the
  983.  arguments, it must include argc and argv in the definition of its main()
  984.  function:
  985.  
  986.  main(argc, argv)
  987.  int argc;
  988.  char *argv[];
  989.  {
  990.          /* function body */
  991.  }
  992.  
  993.       Figure 3-2 depicts how the command line and its components
  994.  are managed in memory. The command line can be thought of as having two
  995.  components: the command name itself (available within C programs only under
  996.  DOS version 3.00 and later) and the command "tail," which may contain
  997.  optional arguments such as filenames or input data. Only the command-name
  998.  component is required by DOS. The command-line text, if present, is
  999.  obtained from the DOS command-line tail. Information in the tail may be
  1000.  optional or required, based on the design of the program being
  1001.  executed.
  1002.  
  1003.  
  1004.       main (argc, argv)
  1005.    argc               program name
  1006.   ┌─────┐  ┌─────┬─────┬─────┬─────┬─────┐
  1007.   │  3  │  │  P  │  R  │  O  │  G  │ \0  │ (DOS 3.00
  1008.   │     │  │     │     │     │     │     │  and later)
  1009.   └─────┘  ─────┴─────┴─────┴─────┴─────┘
  1010.            │
  1011.            │                         command-line arguments
  1012.    argv    │  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
  1013.   ┌─────┐  │  │  A  │  R  │  G  │  1  │ \0  │  A  │  R  │  G  │  2  │ \0  │
  1014.  0│  ■──┼──┘  │     │     │     │     │     │     │     │     │     │     │
  1015.   │     │     ─────┴─────┴─────┴─────┴──────────┴─────┴─────┴─────┴─────┘
  1016.   ├─────┤     │                             │
  1017.  1│  ■──┼─────┘                             │
  1018.   │     │                                   │
  1019.   ├─────┤                                   │
  1020.  2│  ■──┼───────────────────────────────────┘
  1021.   │     │
  1022.   ├─────┤
  1023.  3│  ■  │ NULL  (per proposed ANSI C standard, but not done by
  1024.   │     │        all C compilers--don't depend on it yet)
  1025.   └─────┘
  1026.  
  1027.       FIGURE 3-2 ▐ Command-line arguments
  1028.  
  1029.  
  1030.       Note the declaration of argv in the template for the main() function
  1031.  shown above. The declaration
  1032.  
  1033.       char *argv[];
  1034.  
  1035.  says that argv is an "array of type pointer to type char." This declaration
  1036.  may also be written as
  1037.  
  1038.       char **argv;
  1039.  
  1040.  which says that argv is a "pointer to a pointer to type char." The net
  1041.  result is the same, but these are not identical declarations.
  1042.  
  1043.  
  1044.       Program Name
  1045.  
  1046.       The name used to invoke a program under DOS version 3.00 and later is
  1047.  available to the program as argv[0] (or *argv, if the pointer has not been
  1048.  moved). Earlier versions of DOS do not save the name used to invoke a
  1049.  program, so most C compilers for DOS substitute a dummy value, such as a
  1050.  null string or c. Being able to retrieve the invoking name can be very
  1051.  useful at times, so if our programs detect the use of DOS version 3.00 or
  1052.  later, we will let them use the name to their advantage.
  1053.       How can the name be used? For one thing, when many programmers are
  1054.  producing programs in diverse locations for the same family of machines and
  1055.  operating systems, it is likely that the same name will be used for two or
  1056.  more programs that come into general distribution. We might like to be able
  1057.  to rename one of two identically named commands to avoid problems. This is
  1058.  easy to do if we have the source code, but it might not work if all we have
  1059.  is the executable program. Moreover, some programs (usually copy-protected
  1060.  programs) will not run if called by a different name. Another problem
  1061.  occurs when a program is renamed because, in most cases, the program name
  1062.  is hard-coded into error and help messages. So if we renamed CP.EXE to
  1063.  CPY.EXE, we might still get an error message like cp: can't open file.txt,
  1064.  which isn't nearly as useful as one that displays the currently used
  1065.  program name.
  1066.       In Chapter 7, we'll find other uses for the command name. And
  1067.  throughout the programs in this book, where it makes sense to do so, we
  1068.  will try to obtain the invoking program name for use in error and help
  1069.  messages.
  1070.  
  1071.  
  1072.       Ambiguous Filenames
  1073.  
  1074.       Microsoft C provides a set of functions to handle the processing of
  1075.  wildcard characters in the formation of ambiguous filenames. Four object
  1076.  files are provided, one for each major memory model, and they may be placed
  1077.  in the default library directory for convenient access. The ssetargv.obj
  1078.  file is used with small-model programs; the csetargv.obj, msetargv.obj, and
  1079.  lsetargv.obj files are used with compact-, medium-, and large-model
  1080.  programs, respectively.
  1081.       The setargv() function expands wildcard characters in the command-line
  1082.  tail according to DOS rules (see the DOS manual for details) and passes
  1083.  them to the program in the form of an argument list. It is then up to the
  1084.  program to sift through the argument list it receives and separate optional
  1085.  arguments from filename arguments.
  1086.  
  1087.  
  1088.       Accessing the Command Line
  1089.  
  1090.       The following program shows how to access command-line arguments,
  1091.  including the program name. The REPLAY program first assumes that it will
  1092.  be called by the name replay but, just in case, it checks to see which
  1093.  version of DOS it is running under. If the DOS major number, obtained from
  1094.  the _osmajor global variable defined in stdlib.h, has a value of at least
  1095.  3, REPLAY calls getpname() to extract the filename part of the full
  1096.  pathname passed by DOS. If the user renames REPLAY.EXE to something else--
  1097.  say ARGLIST.EXE--then the program will issue the correct name when it
  1098.  displays the banner line just before displaying the arguments, if any,
  1099.  found on the command line.
  1100.  
  1101.  
  1102.               ╔══════════════════════════════════════════╗
  1103.               ║                                          ║
  1104.               ║ Click to view the listing found on page ║
  1105.               ║ 25 in the printed version of the book.   ║
  1106.               ║                                          ║
  1107.               ╚══════════════════════════════════════════╝
  1108.  
  1109.  
  1110.       Notice a few things in this demonstration program. We test for the
  1111.  presence of arguments (if (argc == 1)) and quit with a nonzero exit code if
  1112.  there are none. An argument count of 1 means that only the command name was
  1113.  typed on the DOS command line. This simple precaution prevents us from
  1114.  printing a banner line and nothing more, which would look kind of
  1115.  silly.
  1116.       The temporary pointer p is declared as a pointer to a pointer to a
  1117.  character and is assigned the initial value of argv. When we enter the
  1118.  loop, argv is incremented so that it points past the command-name string to
  1119.  the first optional argument. Each time through the loop, argv gets
  1120.  incremented, so argv - p in the body of the loop yields the number of the
  1121.  current argument and *argv yields the argument string. We could just as
  1122.  easily have incremented p while leaving argv stationary, which would
  1123.  require the argument-number calculation to be reversed (p - argv).
  1124.       The pgm character array is large enough to hold an eight-character
  1125.  name plus a terminating null character. It must be declared static so that
  1126.  it can be initialized to the expected name. We put a name in the array so
  1127.  that if none is available from the operating system, we'll still have one
  1128.  to work with--although there is a small risk that it may be the wrong one,
  1129.  if someone has changed the program name.
  1130.       If _osmajor is 3 or more, we call getpname() to grab the base program
  1131.  name out of the pathname string, which may be anything from a simple
  1132.  filename to a full or relative pathname, complete with drive specifier and
  1133.  extension. (DOS allows pathnames for programs in version 2.00 and later.
  1134.  For the purposes of this book, earlier versions of DOS are considered to be
  1135.  obsolete.)
  1136.       Here is the code for getpname():
  1137.  
  1138.  
  1139.               ╔══════════════════════════════════════════╗
  1140.               ║                                          ║
  1141.               ║ Click to view the listing found on page ║
  1142.               ║ 26 in the printed version of the book.   ║
  1143.               ║                                          ║
  1144.               ╚══════════════════════════════════════════╝
  1145.  
  1146.  
  1147.       The program-name pointer, pname, accesses an array in the calling
  1148.  function that is large enough to hold the filename part (eight characters)
  1149.  of a valid DOS filespec, plus the terminating null byte.
  1150.       The getpname() function receives the full pathname of a file (path),
  1151.  immediately sets cp to the start of path, finds the terminating null byte,
  1152.  and backs up one position. It then moves backward to the start of the
  1153.  filename, which is marked by the beginning of path if the name is a simple
  1154.  filename, or by one of the accepted separators (:, \, or /). Notice that
  1155.  the backslash has to be escaped (\\) to turn off its special meaning. The
  1156.  last thing getpname() does is copy the filename part to pname, stopping
  1157.  when it sees a dot or a null byte. (It is customary in programming to refer
  1158.  to a single . as a dot.)
  1159.       The getpname() function is one we will use often, so it should be put
  1160.  in a place where it can be retrieved easily. We will now start accumulating
  1161.  useful object modules in a library of our own called util.lib, which will
  1162.  reside in a directory called \lib\local on the default disk drive. (Users
  1163.  of other compilers may need to make some adjustments if their compilers do
  1164.  not allow this.) The function is first compiled using the command
  1165.  
  1166.       msc getpname;
  1167.  
  1168.  and then the object file, getpname.obj, is added to the utility library by
  1169.  typing
  1170.  
  1171.       lib \lib\local\util +getpname;
  1172.  
  1173.  Any time we need to use this function, we add the library path to the list
  1174.  of libraries to be searched by LINK.
  1175.       We can automate the process by using MAKE with a script in a makefile
  1176.  routine such as the following one I assembled for the REPLAY program (the
  1177.  use of the MAKE program maintainer is explained in Chapter 1):
  1178.  
  1179.  # makefile for replay program
  1180.  
  1181.  LIB=c:\lib
  1182.  
  1183.  LLIB=c:\lib\local
  1184.  
  1185.  replay.obj:     replay.c
  1186.          msc $*;
  1187.  
  1188.  replay.exe:     replay.obj $(LLIB)\util.lib
  1189.          link $* $(LIB)\ssetargv $(LLIB)\util;
  1190.  
  1191.       This makefile, called replay.mk, is invoked using the command
  1192.  
  1193.       make replay.mk
  1194.  
  1195.  The MAKE program will take care of the details of recompiling objects and
  1196.  relinking the needed program modules to produce a new executable program.
  1197.  This example is the paradigm for all the programs that follow. Simple
  1198.  programs will be presented with specific compile and link descriptions; I
  1199.  will present makefiles for the more complex programs in later chapters.
  1200.  
  1201.  
  1202.       Pointers and Indirection
  1203.  
  1204.       The source code presented thus far uses quite a few pointers. Many
  1205.  programmers who are new to C language (and, for that matter, quite a few
  1206.  who are not so new to it) have problems with the declaration and
  1207.  application of pointers. Indeed, I have seen entire books written about C
  1208.  programming that sidestep the issue of pointers by using indexed arrays
  1209.  almost exclusively. That may seem a good way to minimize the difficulty one
  1210.  experiences in learning C, but it robs the programmer of one of C's most
  1211.  powerful capabilities and is really an unnecessary limitation.
  1212.       If you are having problems with pointers, try to visualize a pointer
  1213.  declaration in the following way. (We'll use a pointer to a character here
  1214.  because it is commonly encountered.) The line
  1215.  
  1216.       char *cp;
  1217.  
  1218.  declares a character pointer. Place your finger tip over the cp part of the
  1219.  declaration and see what's left (char *). Read this as "cp is a pointer
  1220.  (the *) to a character storage location (the char)." In an expression,
  1221.  then, the unadorned cp yields a storage location that holds a pointer to
  1222.  (the address of) a character. If, however, you cover the *cp grouping, you
  1223.  are left with the type char. This says that when you see *cp in an
  1224.  expression, it yields a character storage location. You can retrieve the
  1225.  value stored there and may assign a character value into the storage
  1226.  location.
  1227.       Look back through the source for getpname(), which is loaded with
  1228.  examples of the variable cp used in both contexts. Once you feel
  1229.  comfortable with single indirection, as this is called, try the same thing
  1230.  with double indirection, such as you encounter with the declaration of the
  1231.  argument vector:
  1232.  
  1233.       char **argv;
  1234.  
  1235.  The illustration in Figure 3-3 on the next page may help to clarify this
  1236.  a bit. If it doesn't come easily to you, don't give up on it. A little
  1237.  practice will make it clear. You have to get a picture in mind of what's
  1238.  happening.
  1239.       In Chapter 6, we will examine user/machine interfaces further. We will
  1240.  introduce a variety of ways of handling command-line arguments and talk
  1241.  about getopt(), an AT&T library routine that attempts to standardize
  1242.  command-line syntax.
  1243.  
  1244.  
  1245.                SINGLE INDIRECTION:
  1246.                   ┌──────────────┐ ┌────┬────┬────┬────┬────┬────┬────┬────┐
  1247.                   │      ■───────┼─▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│
  1248.                   └──────────────┘ └────┴────┴────┴────┴────┴────┴────┴────┘
  1249.                    char *s;
  1250.                           ░ s yields a pointer to a char
  1251.                          ▓▓ *s yields a char
  1252.  
  1253.  
  1254.                DOUBLE INDIRECTION:
  1255.  ┌──────────────┐ ┌──────────────┐ ┌────┬────┬────┬────┬────┬────┬────┬────┐
  1256.  │      ■───────┼─      ■───────┼─▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│▒▒▒▒│
  1257.  └──────────────┘ └──────────────┘ └────┴────┴────┴────┴────┴────┴────┴────┘
  1258.                    char **s;
  1259.                           ░ s yields a pointer to a pointer to a char
  1260.                          ▓▓ *s yields a pointer to a char
  1261.                         ▒▒▒ ** yields a char
  1262.  
  1263.       FIGURE 3-3 ▐ Pointers and indirection
  1264.  
  1265.  
  1266.  DOS Environment Variables
  1267.  
  1268.       Another way to get data into a program or to modify a program's
  1269.  behavior is by using variables that are maintained in the DOS environment.
  1270.  To see which variables are in the environment and what their values are,
  1271.  type
  1272.  
  1273.       set
  1274.  
  1275.  at the DOS prompt and view the listing. All environments contain the values
  1276.  of COMSPEC and PATH. On my system, the environment list looks like
  1277.  this:
  1278.  
  1279.  
  1280.  COMSPEC=C:\COMMAND.COM
  1281.  PATH=C:\DOS;C:\BIN;C:\PWP;C:\WP;
  1282.  PROMPT=$p$g
  1283.  PWPLIB=c:\pwp
  1284.  INCLUDE=c:\include
  1285.  LIB=c:\lib
  1286.  TMP=c:\
  1287.  CONFIG=c:\config
  1288.  FGND=cyan
  1289.  BKGND=blue
  1290.  BORDER=blue
  1291.  
  1292.  
  1293.       From the DOS command line or from within a batch file, a user can add
  1294.  variables to the operating environment by using the SET command. The
  1295.  command
  1296.  
  1297.       set var=val
  1298.  
  1299.  instructs DOS to add the variable var to the environment and assign it the
  1300.  value val. Within a batch file, the value of the DOS environment variable
  1301.  var can be obtained by using the construct %var%. The following batch file,
  1302.  SHOW.BAT, does this to display the values set for foreground and background
  1303.  colors (see Chapter 13 to find out how these values can be used):
  1304.  
  1305.  echo off
  1306.  echo FGND = %FGND%
  1307.  echo BKGND = %BKGND%
  1308.  
  1309.       The initial echo off is used to silence DOS, which displays each line
  1310.  in a batch file before processing it, unless it's told not to. (The default
  1311.  really ought to be echoing off, and many enterprising programmers have
  1312.  patched COMMAND.COM for each version of DOS to disable this echoing.)
  1313.  
  1314.                                COMMENT
  1315.            The amount of environment space reserved by DOS (prior
  1316.            to version 3.2) is only 160 bytes. You may want to
  1317.            expand the default space by using SETENV, a utility
  1318.            program supplied with version 4.00 of the Microsoft C
  1319.            Compiler. Some other commercial software products
  1320.            include a program called ENVSIZE, or something similar,
  1321.            but not all C compilers have such a utility.
  1322.  
  1323.       We want to be able to selectively read DOS environment variables into
  1324.  our C programs, and we may even want to change them during the execution of
  1325.  our programs. The library functions getenv() and putenv() allow us to do
  1326.  both of these tasks. The functions use an environment pointer to gain
  1327.  access to the DOS environment.
  1328.  
  1329.  
  1330.       The Environment Pointer
  1331.  
  1332.       Access to the DOS environment is obtained through the global variable
  1333.  environ, or the main() function argument envp. The header file stdlib.h
  1334.  declares the environment pointer as
  1335.  
  1336.       extern char **environ
  1337.  
  1338.  Thus, environ is an array of pointers to environment strings.
  1339.       To add a third argument to main() to obtain a pointer to the
  1340.  environment, you would use the following form:
  1341.  
  1342.  main(argc, argv, envp)
  1343.  int argc;
  1344.  char *argv[];
  1345.  char *envp[];
  1346.  {
  1347.          . . .
  1348.  }
  1349.  
  1350.  If envp is declared as an argument to main(), both argc and argv also must
  1351.  be declared because of the way C function arguments are processed. Note
  1352.  that the declarations for argv and envp could be written
  1353.  
  1354.       char **argv;
  1355.  
  1356.       char **envp;
  1357.  because these are functionally identical to the previous
  1358.  declarations.
  1359.       The envp argument to main() is simply a copy of environ at the time
  1360.  the program began execution. If subsequent changes are made to the
  1361.  environment, the global environ variable is kept current, but the value of
  1362.  envp, which is local to main(), is not.
  1363.  
  1364.       Reading from the DOS Environment  To read the value of a DOS variable
  1365.  into a C program, use the getenv() function, which returns a pointer to a
  1366.  character string if the variable is defined, or returns NULL if it is
  1367.  not.
  1368.       The following test program, SHOWENV, accepts a list of strings from
  1369.  the user and checks each in turn to see if it is the name of a currently
  1370.  defined environment variable. If it is, SHOWENV displays the string and its
  1371.  value. If not, SHOWENV displays the message "var not defined" and moves on
  1372.  to the next string, if any. If no arguments are given to the command,
  1373.  SHOWENV prints out the entire environment list.
  1374.  
  1375.  
  1376.               ╔══════════════════════════════════════════╗
  1377.               ║                                          ║
  1378.               ║ Click to view the listing found on page ║
  1379.               ║ 33 in the printed version of the book.   ║
  1380.               ║                                          ║
  1381.               ╚══════════════════════════════════════════╝
  1382.  
  1383.  
  1384.       The library function strupr() is applied to the string being
  1385.  processed, because DOS always converts the variable name to uppercase
  1386.  letters before saving it in the environment. If strupr() were not used, no
  1387.  match would be found if the user typed the string arguments in lowercase
  1388.  letters.
  1389.  
  1390.       Writing to the DOS Environment  The process of modifying the DOS
  1391.  environment list is analogous to reading it, except that the putenv()
  1392.  function is called. A return value of 0 indicates success and -1 indicates
  1393.  failure, usually due to a lack of memory in which to write the modified
  1394.  environment. The program SETMYDIR attempts to add the variable MYDIR to the
  1395.  DOS environment by calling setenv().
  1396.  
  1397.  
  1398.               ╔══════════════════════════════════════════╗
  1399.               ║                                          ║
  1400.               ║ Click to view the listing found on page ║
  1401.               ║ 34 in the printed version of the book.   ║
  1402.               ║                                          ║
  1403.               ╚══════════════════════════════════════════╝
  1404.  
  1405.  
  1406.       Please note that putenv() modifies the environment in which the
  1407.  current process is running (the environment inherited is a copy from DOS),
  1408.  but it does not change the original DOS environment. To observe this,
  1409.  compile the SETMYDIR program. Then run a SET command to list the starting
  1410.  environment (assuming you don't already have a MYDIR variable), run the
  1411.  SETMYDIR program, then run the SET command again. You'll see the MYDIR
  1412.  variable come into existence while the program is running, then it will
  1413.  disappear when the program stops executing.
  1414.  
  1415.                                CAUTION
  1416.            Be careful to assign values into the environment
  1417.            correctly. The form of the SET command is rigid. No
  1418.            white space (space, tab) is permitted on either side of
  1419.            the equal sign. If spaces or tabs are inserted, neither
  1420.            getenv() nor the %var% batch-file mechanism will
  1421.            recognize that the variable is defined.
  1422.  
  1423.       The ability to modify the environment passed to a running program is
  1424.  important. A program can, for example, set up its own private PATH so that
  1425.  it can access its own set of commands while running, without disturbing the
  1426.  user's normal environment, which is restored when the program terminates.
  1427.       That's about it for the DOS-to-C connection via command lines and
  1428.  environment variables. We'll be using these connections extensively in our
  1429.  programs, so spend some time getting familiar with the techniques and
  1430.  routines described in this chapter.
  1431.       In the next chapter we will take a much closer look at some of the
  1432.  standard library routines that are part of the run-time library of the C
  1433.  compiler package.
  1434.  
  1435.  
  1436.  
  1437.  SECTION II  Standard Libraries and Interfaces
  1438.  
  1439.  
  1440.  
  1441.  Chapter 4  Using Standard Libraries
  1442.  
  1443.  
  1444.  
  1445.       C has no built-in input/output capabilities; all I/O operations are
  1446.  handled by external functions. Neither does C have any built-in
  1447.  stringhandling facilities or mathematical functions. This is not a
  1448.  deficiency, but rather it is a benefit that permits each C compiler
  1449.  implementation to use features and capabilities of the host hardware and
  1450.  operating system, while presenting the same external interface to programs.
  1451.  All such facilities are external to C and are handled by routines
  1452.  (functions and macros) that typically reside in special libraries, which
  1453.  are collections of often-used routines.
  1454.       The UNIX heritage of C has produced a large body of ready-to-use
  1455.  routines that have successfully made the trip to DOS despite some
  1456.  significant differences in the underlying operating systems and their
  1457.  supporting environments. The routines mask the differences by providing a
  1458.  clean, consistent, and fairly well-documented interface to programs.
  1459.       In this chapter, we will explore a small selection of the hundreds of
  1460.  routines in the standard C run-time library and use them to construct some
  1461.  simple but useful programs. Other library routines will be introduced in
  1462.  later chapters as the need for them arises.
  1463.  
  1464.  
  1465.  Why Use Libraries?
  1466.  
  1467.       Simply put, the standard libraries give us a reasonable degree of
  1468.  program portability, provide consistent and predictable interfaces, and,
  1469.  most important, reduce programming effort. Out of the millions of lines of
  1470.  C code that have been written over the years, certain operations have been
  1471.  programmed over and over again. Just a few obvious examples include:
  1472.  opening and closing files; reading and writing characters; detecting errors
  1473.  and displaying relevant messages; splitting, concatenating, and copying
  1474.  strings; and managing a hierarchy of directories and files.
  1475.       In the standard libraries, many of these operations have been highly
  1476.  refined for speed, flexibility, and applicability to a wide range of
  1477.  situations. I prefer to use something that exists, if it works, rather than
  1478.  start from ground zero. Therefore, this book will concentrate on using the
  1479.  run-time library provided with the compiler wherever the library routines
  1480.  will do the job. We will develop mid-level and high-level routines that
  1481.  depend on standard libraries to carry out the low-level work. In all but a
  1482.  few special cases, we will avoid creating new low-level functions--it is
  1483.  simply not necessary most of the time.
  1484.       The portability issue is often the basis of religious debate,
  1485.  something I wish to avoid because it is a waste of valuable time and
  1486.  energy. The portability I strive for in the C programs in this book is that
  1487.  needed to move a program among the various MS-DOS and PC-DOS systems, to
  1488.  extensions of the DOS environment such as Windows, and to a lesser degree,
  1489.  to XENIX and other UNIX-like systems. In some cases, I will sacrifice
  1490.  portability to achieve a high level of performance under raw DOS because
  1491.  that's what will "sell" best for a given application.
  1492.       To go for complete DOS/UNIX transparency requires that some of the
  1493.  nicer features of a PC must be forsaken so that programs will run on any
  1494.  terminal type that can be connected to a multi-user system. In particular,
  1495.  this usually means saying good-bye to the use of function keys, arrow keys,
  1496.  attractive line-drawing characters, and fast screen updates. Going for the
  1497.  best performance on a PC usually means sacrificing portability to UNIX for
  1498.  such screen-intensive applications as a programmer's editor.
  1499.       However, careful design can minimize the effects of most of the
  1500.  differences between DOS and UNIX. The use of standard libraries goes a long
  1501.  way toward ensuring DOS/UNIX portability because of the effort Microsoft
  1502.  and other C compiler vendors have placed on providing compatible libraries.
  1503.  The differences among vendors' libraries usually occur in PCspecific low-
  1504.  level interfaces. Although I will discourage the use of such PC-specific
  1505.  functions in programs designed for general use, they are the subject of
  1506.  Chapter 5 and will be used in some of our programs.
  1507.  
  1508.  
  1509.  Exception Handling
  1510.  
  1511.       Some days, things just don't seem to go right. Ever had one of those?
  1512.  Well, programs have days like that, too. As programmers, we have a
  1513.  responsibility to our programs to build in some way of dealing with
  1514.  unexpected events. This is called exception handling and is an essential
  1515.  component in the development of commercial-quality programs.
  1516.       Exception handling has two parts: detection and recovery. The standard
  1517.  library functions attempt to detect errors and inform calling functions
  1518.  about them. It is up to us to build in the appropriate recovery procedures,
  1519.  even if that simply means giving up on a task, which is sometimes all we
  1520.  can do.
  1521.       One of the more common error conditions is the absence of a needed
  1522.  file. The fopen() function, for example, tries to open a named file. If
  1523.  asked to open a nonexistent file for reading, fopen() returns NULL instead
  1524.  of a valid file pointer. What's the appropriate response to this error
  1525.  condition? At the very least, we may want to inform the user about the
  1526.  missing file. If the error occurs in a loop that is processing a list of
  1527.  files, we may choose to print a brief but informative message ("file XYZ.C
  1528.  not found") to stderr before moving on to the next name in the list. In
  1529.  other circumstances, it may be best to print the message and terminate
  1530.  processing with an ERRORLEVEL (exit code), to tell DOS that something is
  1531.  wrong.
  1532.  
  1533.  
  1534.       Standard Library Error Functions
  1535.  
  1536.       A set of standard library functions assists us with exception
  1537.  handling. A global variable, errno, holds the number of the most recent
  1538.  error. This number is used as an index into an array of error messages,
  1539.  sys_errlist. (The message associated with error number 0 is "Error 0," a
  1540.  catchall for errors not described elsewhere.) Rather than access errno and
  1541.  sys_errlist directly, we can call on perror() to print the error message to
  1542.  the standard error output. The manual page summary of perror() is:
  1543.  
  1544.       #include <stdlib.h>
  1545.       void perror(string);
  1546.       char *string;
  1547.  
  1548.  where void indicates that there is no return value and string is a pointer
  1549.  to a message that we provide.
  1550.  
  1551.                                COMMENT
  1552.            Compilers that do not adhere completely to the proposed
  1553.            ANSI C standard may not support the void data type (or
  1554.            lack of type, in this case) for functions. It may be
  1555.            necessary to use the following typedef to compensate
  1556.            for this deficiency:
  1557.  
  1558.            typedef int void;
  1559.  
  1560.            Calls to
  1561.            void functions will actually return an integer that can
  1562.            safely be ignored.
  1563.  
  1564.       The output of perror() is the string we provided, followed by a colon,
  1565.  a space, the text of the system message for the most recent error, and a
  1566.  trailing newline, all sent to stderr. Thus, the statement
  1567.  
  1568.       perror("File error");
  1569.  
  1570.  if triggered by an attempt to open a nonexistent file for reading, produces
  1571.  the following output on the screen:
  1572.  
  1573.  
  1574.  File error: No such file or directory
  1575.  
  1576.  
  1577.       Appendix A of the Microsoft C Compiler Library Reference Manual lists
  1578.  the errno symbolic constants used under MS-DOS, the message text for each,
  1579.  and a description of each error. The DOS error messages are necessarily a
  1580.  subset of the error messages used by UNIX and XENIX. See your own
  1581.  C compiler library documentation for a detailed description of each error
  1582.  and message.
  1583.  
  1584.       Ambiguous Return Values  Two macros, ferror() and feof(), are used to
  1585.  resolve an ambiguity that is produced by certain standard library routines:
  1586.  Because they are designed to return a pointer to a character, functions
  1587.  like fgets() (which we will use in an example shortly) must return NULL to
  1588.  indicate either that an error has occurred or that the end of the file has
  1589.  been reached. We need to take additional steps to determine which condition
  1590.  caused the NULL return value before we take any action.
  1591.       The manual page summary of ferror() is
  1592.  
  1593.       #include <stdio.h>
  1594.       int ferror(stream);
  1595.       FILE *stream;
  1596.  
  1597.  
  1598.  where stream must be the file or stream associated with the NULLreturning
  1599.  routine. The summary for feof() is the same except for the macro name. The
  1600.  ferror() macro returns a nonzero value to indicate that an error condition
  1601.  exists and feof() returns a nonzero value if the end of the file has been
  1602.  reached, thus resolving the ambiguity of the NULL value.
  1603.       The following code fragment illustrates a way of handling the
  1604.  ambiguous return:
  1605.  
  1606.          .
  1607.          .
  1608.          .
  1609.  errcount = 0;
  1610.  while ((s = fgets(line, BUFSIZ, fp)) != NULL) {
  1611.          /* process the line */
  1612.                  .
  1613.                  .
  1614.                  .
  1615.  }
  1616.  if (ferror(fp)) {
  1617.          perror("Read error");
  1618.          clearerr(fp);
  1619.          if (++errcount >= MAX_ERR) {
  1620.                  fprintf(stderr, "Too many errors\n");
  1621.                  exit(1);
  1622.          }
  1623.  }
  1624.          .
  1625.          .
  1626.          .
  1627.  
  1628.       Notice the use of clearerr() in the preceding fragment. Once an error
  1629.  occurs in a stream, the error indication remains until the stream is closed
  1630.  or until the indication is intentionally cleared by using either clearerr()
  1631.  or rewind(). In the foregoing example, we clear the error indication and
  1632.  allow some maximum number of errors (MAX_ERR) before giving up. On the
  1633.  other hand, if the NULL is the result of having reached the end of the
  1634.  file, we fall through to other processing.
  1635.       A common programming procedure is to display an error message and then
  1636.  call exit() to flush output buffers and return to the operating system with
  1637.  an exit code that indicates an abnormal termination. For convenience, we
  1638.  will package these statements into a function called fatal(), which
  1639.  displays the program name, the message text, the system error message, and
  1640.  a newline. This will usually provide enough information to the user to help
  1641.  determine the cause of the abnormal exit.
  1642.  
  1643.  /*      fatal -- issue a diagnostic message and terminate  */
  1644.  
  1645.  
  1646.  #include <stdio.h>
  1647.  #include <stdlib.h>
  1648.  
  1649.  
  1650.  void fatal(pname, mesg, errlvl)
  1651.  char *pname;    /* program name */
  1652.  char *mesg;     /* message text */
  1653.  int errlvl;     /* errorlevel (exit code) */
  1654.  {
  1655.          fputs(pname, stderr);   /* display error message */
  1656.          fputc(':', stderr);
  1657.          fputc(' ', stderr);
  1658.          fputs(mesg, stderr);
  1659.          fputs('\n', stderr);
  1660.  
  1661.  
  1662.          /* return to DOS with the specified errorlevel */
  1663.          exit(errlvl);
  1664.  
  1665.  }
  1666.  
  1667.  
  1668.       The fatal() function doesn't return to the calling function, hence the
  1669.  void return type. The errlvl value should be nonzero--a small positive
  1670.  integer--to indicate an abnormal termination. The specific values used for
  1671.  errlvl should be documented in a manual page for the program.
  1672.  
  1673.       Operating System Interrupts  Another type of exception is an interrupt
  1674.  from the operating system, usually caused by the user pressing Ctrl-Break
  1675.  or Ctrl-C. The library function signal() can be used to control how such
  1676.  interrupts are handled. The summary for signal() is
  1677.  
  1678.       #include <signal.h>
  1679.       int (*signal(sig, func))();
  1680.       int sig;
  1681.       int (*func)();
  1682.  
  1683.  This bizarre-looking declaration is not a misprint. The line
  1684.  
  1685.       int (*func)();
  1686.  
  1687.  declares a pointer to a function that returns an integer. This is not the
  1688.  same as
  1689.  
  1690.       int *func();
  1691.  
  1692.  which declares a function that returns a pointer to an integer. The
  1693.  declaration of signal() also is a pointer to a function that returns an
  1694.  integer.
  1695.       The only value for sig permitted under DOS is SIGINT, #defined in the
  1696.  signal.h header file. SIGINT is the DOS Ctrl-Break interrupt. Other
  1697.  signals, including SIGHUP (hangup) and others that apply under the
  1698.  multitasking UNIX and XENIX operating system, currently have no meaning
  1699.  under DOS.
  1700.       One of three responses to the interrupt can be specified in the func
  1701.  argument. A value of SIG_IGN causes the interrupt to be ignored. We might
  1702.  choose to ignore the DOS Ctrl-Break interrupt during all or a portion of
  1703.  the lifetime of a program to prevent the user from aborting some critical
  1704.  operation. For example, it is unwise to let the user break out of a text
  1705.  editor without first presenting the opportunity of saving changes that were
  1706.  made to the editing buffer--it could be that the user was fumbling for,
  1707.  say, Ctrl-D to move the cursor right and hit Ctrl-C by mistake. I know,
  1708.  I've done it myself!
  1709.       Second, a func value of SIG_DFL causes the running program to abort
  1710.  upon receipt of the interrupt, closing all open files and returning control
  1711.  to DOS. Buffers are not flushed. This is usually what will happen if you
  1712.  don't use signal(), unless you are using DOS functions that ignore Ctrl-
  1713.  Break by design (see Chapter 5).
  1714.       A third type of response is to provide a pointer to a signal-handling
  1715.  function. This is referred to as signal catching and is more of an art than
  1716.  a science. The safest method of signal catching simply cleans up any work
  1717.  in progress and makes a graceful exit. It either accepts the default
  1718.  treatment or, in a few cases, simply ignores the interrupt.
  1719.       A return value of -1 from signal() flags an error, errno = EINVAL,
  1720.  which says that the sig argument is invalid. This identifies a programming
  1721.  problem that should be corrected before any user ever sees the
  1722.  program.
  1723.  
  1724.  
  1725.  Time Functions
  1726.  
  1727.       The standard library provides several routines for retrieving and
  1728.  converting time values. The UNIX ctime subroutine package and the time()
  1729.  system call are available as functions in the Microsoft C run-time library
  1730.  for DOS. Because these functions have received very little attention in
  1731.  books about DOS, we will give them a moment in the spotlight.
  1732.       The time() function returns the number of seconds that have elapsed
  1733.  since 00:00:00 GMT (Greenwich mean time) on January 1, 1970. This date and
  1734.  time is called the epoch. GMT is now officially called Universal time (UT),
  1735.  but the use of the term GMT is likely to continue indefinitely, just as
  1736.  core is still used to mean main memory, even if it's all solid state. File-
  1737.  modification times and other times and dates that are visible to the user
  1738.  are presented as local time values.
  1739.       The declaration of time() is
  1740.  
  1741.       #include <time.h>
  1742.       long time(timeptr);
  1743.       long *timeptr;    /* (usually NULL) */
  1744.  
  1745.  
  1746.  The time() function takes a pointer to long as an argument and produces a
  1747.  long return value. The argument can always be the NULL pointer. Before C
  1748.  had a long data type, it was necessary to provide a suitable buffer for the
  1749.  time value. Passing the address of the buffer synthesized a "call by
  1750.  reference" (C uses "call by value" for parameter passing). Now the
  1751.  application of time() simply assigns the return value to a long
  1752.  integer.
  1753.  
  1754.  
  1755.       Time Conversions
  1756.  
  1757.       The long time value is wonderful for the computer, but we humans don't
  1758.  often tell someone what time it is as a number of seconds since the epoch.
  1759.  A more convenient notation for us is a character string that spells out, at
  1760.  least as abbreviations, what the date and time are. We need some convenient
  1761.  way to make a conversion from the long value to a character string. We also
  1762.  have to deal with Universal time and local time and with the complications
  1763.  of daylight saving time.
  1764.       Figure 4-1 shows the relationships of the time() function
  1765.  and the functions that constitute the ctime subroutine package. Two
  1766.  functions, gmtime() and localtime(), can be used to convert values
  1767.  from the long result of time() to time structure pointers. The DOS time
  1768.  structure template, struct tm, defines a set of variables of type int that
  1769.  can hold hour, minute, second, month, day, and year values, plus a flag for
  1770.  daylight saving time and numbers for day-of-week and day-of-month
  1771.  calculations. This time structure template is defined in the file time.h.
  1772.  
  1773.  
  1774.  RELATIONSHIPS:
  1775.                                ┌──────────┐
  1776.                          ┌─────          ├──────────────────────────┐
  1777.                          │     │ ctime()  │                          │
  1778.                          │     └──────────┘                          │
  1779.                          │                                           │
  1780.  ┌────────┐ ┌──────┐ ┌───┴───┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────┐
  1781.  │▒system▒│ │      │ │▒▒▒▒▒▒▒├─         ├─▒▒▒▒▒▒▒▒▒├─         ├─▒▒▒▒▒▒▒▒│
  1782.  │▒▒time▒▒│ │time()│ │▒long▒▒│ │gmtime() │ │struct tm│ │asctime()│ │ string │
  1783.  └────────┘ └──────┘ └───┬───┘ └─────────┘ └────────┘ └─────────┘ └────────┘
  1784.                          │                       │
  1785.                          │     ┌───────────┐     │
  1786.                          │     │           │     │
  1787.                          └─────localtime()├─────┘
  1788.                                └───────────┘
  1789.  
  1790.  
  1791.  ════════════════════════════════════════════════════════════════════════════
  1792.                                ┌──┐
  1793.  LEGEND:    ▒▒▒ = data type    │  │
  1794.             ▒▒▒                └──┘ = ctime package function
  1795.  
  1796.       FIGURE 4-1 ▐  Time and date functions and variables
  1797.  
  1798.  
  1799.       Time and date information in the structure is easy to access and use,
  1800.  but it is still not suitable for human consumption. Both gmtime() and
  1801.  localtime() return pointers to a time structure. The declarations for these
  1802.  functions are
  1803.  
  1804.       #include <time.h>
  1805.       struct tm *gmtime(time);
  1806.       long *time;
  1807.       struct tm *localtime(time);
  1808.       long *time;
  1809.  
  1810.  
  1811.                                CAUTION
  1812.            These two functions use a single statically allocated
  1813.            structure. Any call to one of these functions,
  1814.            therefore, will overwrite the result of any previous
  1815.            call to either function.
  1816.  
  1817.       While gmtime() always returns a direct conversion of the time value
  1818.  returned by time(), localtime() does some additional adjustments to
  1819.  compensate for the difference between GMT and the time zone set into the
  1820.  DOS environment via the TZ variable. If TZ is not explicitly set (it
  1821.  usually isn't), then Pacific time is used. I am willing to wager that
  1822.  nearly every PC running DOS today thinks it is in Redmond, Washington!
  1823.  We'll see shortly how you can tell your PC where it really is, but
  1824.  first we need to examine how to convert the time structure data into
  1825.  strings we can read and understand.
  1826.  
  1827.       Creating Strings  The function asctime() takes a pointer to the time
  1828.  structure and converts the structure members to a 26-byte string. The
  1829.  format of the string is
  1830.  
  1831.       Sat Jul 12 13:05:33 1986\n\0
  1832.  
  1833.  where \n is a newline character and \0 is the terminating null byte. Each
  1834.  character should be surrounded by single quotes to show that it is a
  1835.  character constant. Rather than clutter the displayed string, I'll just ask
  1836.  you to imagine the quotes there.
  1837.       The function ctime() provides a shortcut for creating a local time
  1838.  string. It takes a long time result from time() and returns a pointer to a
  1839.  character string of the format described above. Its declaration is
  1840.  
  1841.       #include <time.h>
  1842.       char *ctime(time);
  1843.       long *time;
  1844.  
  1845.                                CAUTION
  1846.            The character string pointed to by ctime() is the same
  1847.            statically allocated string pointed to by asctime(). A
  1848.            call to one of these routines destroys the result of
  1849.            any previous call to either of them.
  1850.  
  1851.       Time Zones  Now on to the subject of time zones. A DOS environment
  1852.  variable called TZ can be set to reflect the time zone in which a PC is
  1853.  operating. Adding a line like
  1854.  
  1855.       set TZ=MST7MDT
  1856.  
  1857.  
  1858.  to my AUTOEXEC.BAT file tells DOS that my machine is running in the
  1859.  mountain time zone, that there is a 7-hour difference between mountain
  1860.  standard time and Greenwich mean time, and that daylight saving time is
  1861.  honored in this zone. Several other variables are also maintained by the
  1862.  ctime() subroutine package. They can all be updated by a call to tzset()
  1863.  (or to asctime(), which calls tzset()). These variables are
  1864.  
  1865.  ═══════════════════════════════════════════════════════════════════════════
  1866.    VARIABLE                 MEANING
  1867.  ───────────────────────────────────────────────────────────────────────────
  1868.    int daylight;         │  Daylight savings time flag
  1869.    long timezone;        │  Difference from GMT in seconds
  1870.    char *tzname[];       │  timezone strings
  1871.  ────────────────────────┴──────────────────────────────────────────────────
  1872.  
  1873.  If a daylight string is set in the TZ variable, daylight is set to a
  1874.  nonzero value. The value of timezone is the difference in seconds between
  1875.  local standard time and GMT. The variable tzname is an array of pointers to
  1876.  character strings. The first (tzname[0]) is a pointer to the three-letter
  1877.  string for the standard time zone and the second (tzname[1]) is a pointer
  1878.  to the daylight string (e.g., MDT) if one was specified. The last element
  1879.  (tzname[2]) is a NULL pointer that typically terminates an array of
  1880.  pointers. If no TZ setting is done at boot time, the ctime package uses
  1881.  PST8PDT as a default.
  1882.       When compiled and run, the following little program, TIMEDATA, shows
  1883.  some of the time-related values for your system. Don't be surprised to see
  1884.  Pacific time zone data even if that is not your "home" time zone.
  1885.  
  1886.  
  1887.               ╔══════════════════════════════════════════╗
  1888.               ║                                          ║
  1889.               ║ Click to view the listing found on page ║
  1890.               ║ 49 in the printed version of the book.   ║
  1891.               ║                                          ║
  1892.               ╚══════════════════════════════════════════╝
  1893.  
  1894.  
  1895.       Here is an example of the output of TIMEDATA taken from my system in
  1896.  Denver, Colorado:
  1897.  
  1898.  
  1899.  daylight savings time flag = 1
  1900.  difference (in seconds) from GMT = 25200
  1901.  standard time zone string is mst
  1902.  daylight time zone string is mdt
  1903.  
  1904.  ctime():        Mon Feb 16 06:19:33 1987
  1905.  
  1906.  local time:     Mon Feb 16 06:19:33 1987
  1907.  
  1908.  universal time: Mon Feb 16 13:19:33 1987
  1909.  
  1910.  
  1911.       If you accept the Pacific default for timezone while operating in a
  1912.  different time zone, there is usually no harm done. The effect of not
  1913.  setting TZ is that you are fibbing about the local time, which will cause
  1914.  DOS to have an incorrect notion of what GMT is. For example, had I let TZ
  1915.  default to PST8PDT and set local time correctly for Denver, the system
  1916.  value for GMT would be off by one hour.
  1917.       However, problems might occur for systems connected into wide-area
  1918.  networks that must keep accurate track of file-modification times and
  1919.  various other timed events, possibly while crossing several time zones. In
  1920.  such cases, all connected systems should have the correct time zones set in
  1921.  the TZ variable and agree on what the value of GMT is.
  1922.       Getting a time-and-date string is a first step for us in the
  1923.  development of a program called NOTES, a simple electronic diary. But we
  1924.  will also need a way of creating and modifying files before we can assemble
  1925.  a working prototype, so let's talk about that next.
  1926.  
  1927.  
  1928.  Basic File and Character I/O Functions
  1929.  
  1930.       Much of what we will do in the rest of this book will involve some
  1931.  interaction with disk files. There are many standard I/O routines in the C
  1932.  run-time library that deal with files on various levels. In this section,
  1933.  we will review some of the more frequently used functions. Then we will
  1934.  create a useful demonstration program.
  1935.       The run-time library provides access to disk files and other
  1936.  I/Ostreams using DOS file handles. These functions--open(), close(),
  1937.  read(), write(), lseek(), and so on--are analogous to the UNIX system calls
  1938.  of the same names that use file descriptors. These functions are unbuffered
  1939.  from a user's perspective, although the operating-system kernel may
  1940.  anticipate read operations and delay write operations to minimize disk-head
  1941.  movement, and improve I/O efficiency.
  1942.  
  1943.  
  1944.       Buffered I/O
  1945.  
  1946.       Another level of file access is the set of standard library
  1947.  subroutines that do buffered I/O. The functions fopen), fclose(), fgetc(),
  1948.  fputc(), fgets(), and fputs() are representative of this level, and are
  1949.  used extensively in the programs in this book. These functions use a file
  1950.  pointer that points to a structure of type FILE. The FILE structure
  1951.  template is defined in stdio.h, the header file that is included in all
  1952.  source files that use standard I/O library functions.
  1953.       All file-access functions will be covered at one time or another in
  1954.  programs later in the book. For now, we'll concentrate on the buffered I/O
  1955.  library functions. By way of example, here is a fairly typical use of the
  1956.  standard run-time library in a program called NOTES, which lets us keep an
  1957.  electronic notebook or diary. This first version, NOTES1, is a simplified
  1958.  prototype.
  1959.  
  1960.  
  1961.               ╔══════════════════════════════════════════╗
  1962.               ║                                          ║
  1963.               ║ Click to view the listing found on page ║
  1964.               ║ 52 in the printed version of the book.   ║
  1965.               ║                                          ║
  1966.               ╚══════════════════════════════════════════╝
  1967.  
  1968.  
  1969.       This program, although rather simple and not yet fully developed, is
  1970.  immediately useful in its current form. It takes input from the keyboard
  1971.  and appends it to a "notesfile" in the current directory. The program is
  1972.  invoked by typing NOTES on the DOS command line. Each new session starts by
  1973.  placing a blank line and a date/time stamp at the end of the data file,
  1974.  currently hard-coded into the program as NOTES.TXT. Text input is simply
  1975.  typed at the keyboard and terminated by the user typing a Ctrl-Z (end-of-
  1976.  file) character on a line by itself.
  1977.       This simple version of NOTES allows no options. If you make an error
  1978.  while inputting text, you must back up and correct it before moving off the
  1979.  line. To edit the NOTES.TXT file, you must leave the NOTES program and call
  1980.  upon some text-editing program. We will embellish the NOTES program with
  1981.  additional features in later chapters, but for now we will accept its
  1982.  limitations and treat it as a learning aid.
  1983.       The call to fopen() is interesting. The function will open the named
  1984.  file for appending if the file already exists, or it will silently create
  1985.  the file if it doesn't exist. The call returns a valid FILE pointer if the
  1986.  file is opened successfully, or NULL to flag an error. (Attempting to open
  1987.  a directory, for example, produces an error.) Our program must be prepared
  1988.  to deal with errors. Therefore, at the risk of having our programs look
  1989.  like LISP source code, we will always test the return value of I/O
  1990.  functions. To ignore this extra bit of work is not simply bad programming
  1991.  practice; it can be dangerous. A program that fails without a whimper is
  1992.  not apt to inspire strong feelings of trust in its users.
  1993.       The declaration of fopen() is
  1994.  
  1995.       #include <stdio.h>
  1996.       FILE *fopen(pathname, type);
  1997.       char *pathname;
  1998.       char *type;
  1999.  
  2000.       The pathname argument may be a simple filename and optional extension,
  2001.  or a full or relative pathname with an optional leading drive specifier.
  2002.  The type argument is one of the following:
  2003.  
  2004.  ═══════════════════════════════════════════════════════════════════════════
  2005.    ARGUMENT       MEANING
  2006.  ───────────────────────────────────────────────────────────────────────────
  2007.    r           │  Open for reading (file must exist)
  2008.    w           │  Open for writing (create or truncate)
  2009.    a           │  Open for appending (create if necessary)
  2010.    r+          │  Open for both reading and writing (the file must exist)
  2011.    w+          │  Open for both reading and writing (create or truncate)
  2012.    a+          │  Open for reading and appending (create if necessary)
  2013.  ───────────────────────────────────────────────────────────────────────────
  2014.  
  2015.  The optional + within the type string specifies update mode, in which both
  2016.  reading and writing are allowed. The file pointer must be set by an
  2017.  intervening call to fseek() or rewind() when switching between read and
  2018.  write operations or vice versa.
  2019.       Unlike UNIX and XENIX, which use a single newline (NL) character to
  2020.  terminate a text line, DOS uses a carriage-return character (CR) plus a
  2021.  linefeed (LF) character. The codes to NL and LF are identical (ASCII code
  2022.  10), so you will usually see the DOS end-of-line treatment described as
  2023.  CR/NL, but it is really CR/LF. Technically, LF moves down a line but
  2024.  retains the same column position, so a CR is needed to get back to the
  2025.  first column. The NL code convention achieves the two tasks in a single
  2026.  operation, saving a byte per line in ordinary text files. Both methods of
  2027.  terminating a line are allowed by the ANSI standard (see Chapter 13 for
  2028.  more information about ANSI standards).
  2029.       In addition to a different end-of-line treatment, DOS uses an explicit
  2030.  Ctrl-Z (ASCII code 26) to mark the end of a file. (Under UNIX and XENIX,
  2031.  the end-of-file condition is sensed when an attempt is made to perform an
  2032.  operation beyond the last position in a file, which is known because an
  2033.  exact size is maintained for each disk file.)
  2034.       The conventions just described for DOS text files do not apply to
  2035.  binary files (executable program files, for example), which can contain any
  2036.  arbitrary bit patterns in any position. For binary files, all bits are
  2037.  significant, and an accurate file size must be maintained by DOS so it
  2038.  knows where the end of the file is.
  2039.       To maintain compatibility with existing C programs, UNIX textfile
  2040.  conventions are used within C programs under DOS. When a file is opened,
  2041.  the differences are specified by a text (t) or binary (b) flag appended to
  2042.  the type argument to fopen(), or by the global _fmode translation-mode
  2043.  variable if neither t nor b is explicitly specified. The default for _fmode
  2044.  is text-translation mode.
  2045.       We can use separate statements for the processing and the
  2046.  errordetection phases of each call to a library routine:
  2047.  
  2048.  FILE *fp;
  2049.  
  2050.  fp = fopen(notesfile, "a");
  2051.  if (fp == NULL)
  2052.          fatal(pgm, notesfile, 1);
  2053.  
  2054.  This is rarely done, however. You are more likely to see this written in
  2055.  the condensed form (shown in notes1.c, on page 52) which is the de facto
  2056.  convention for embedding assignments that is used by many, but by no means
  2057.  all, C programmers:
  2058.  
  2059.          FILE *fp;
  2060.  
  2061.          if ((fp = fopen(notesfile, "a")) == NULL)
  2062.                  fatal(pgm, notesfile, 1);
  2063.  
  2064.  Don't let "convention" sway you, however. Use whichever form looks pleasing
  2065.  to your eye. Just be sure the statements are logically correct and that
  2066.  error conditions are checked and responded to in a reasonable way.
  2067.       The getchar() and putc() calls are actually macros defined in stdio.h.
  2068.  These calls tend to work somewhat faster than the equivalent functions,
  2069.  fgetchar() and fputc(), because they expand to in-line code and eliminate
  2070.  the function-call overhead associated with true functions. However, if we
  2071.  had many calls to putc() and getchar() (which is just getc() with stdin
  2072.  specified as the stream), we would save some space by using the functions
  2073.  because the code for the function only has to appear one time in the object
  2074.  file, rather than once for each call to the macros.
  2075.       Rather than clutter this chapter with information that is available to
  2076.  you in the C compiler's Library Reference Manual, we'll just refer to that
  2077.  document (or the equivalent for other C compilers) for more details and
  2078.  move on to some other interesting topics that are essential to the
  2079.  development of our programs.
  2080.  
  2081.  
  2082.  Process Control Functions
  2083.  
  2084.       A process is an environment in which a program executes. When you run
  2085.  the NOTES.EXE program, for example, DOS allocates an instruction segment, a
  2086.  user data segment, and a system data segment. It then initializes the
  2087.  instruction and data segments from the program and starts execution.
  2088.       The Microsoft C run-time library provides a group of functions for
  2089.  controlling the execution of processes from within a process. The easiest
  2090.  to use is system(), which takes a program-name argument (may be a full or
  2091.  relative pathname) and runs the named program as a subprocess. When the
  2092.  subprocess terminates, control is returned to the calling process. The
  2093.  declaration of system() is
  2094.  
  2095.       #include <process.h>
  2096.       int system(string);
  2097.       char *string;
  2098.  
  2099.  where the string argument is the name of a built-in DOS command or an
  2100.  external program that must be in the current directory or be accessible
  2101.  via the PATH variable. DOS must be able to find and load COMMAND.COM to
  2102.  service the system() call. A return of -1 indicates an error.
  2103.       A simple example of the system() function in use is the following code
  2104.  fragment that calls a text editor from within a running process:
  2105.  
  2106.                  .
  2107.                  .
  2108.                  .
  2109.          if (system("EDIX") == -1) {
  2110.                  perror("System error");
  2111.                  /* error recovery procedure */
  2112.                          .
  2113.                          .
  2114.                          .
  2115.          }
  2116.  
  2117.       An error return could be caused by the absence of a valid COMMAND.COM
  2118.  file or by the inability of DOS to find the EDIX.EXE file with the
  2119.  information provided by the system() call plus the current value of
  2120.  PATH.
  2121.       The exec() and spawn() families of process-control functions are more
  2122.  complicated to use, but they provide considerably more versatility. The two
  2123.  families have much in common and each has six members. The base function
  2124.  name--exec, for example--takes a one- or two-letter suffix to fully specify
  2125.  the function's behavior. Thus, the exec() function names are execl(),
  2126.  execle(), execlp(), execv(), execvp(), and execve(). The symbols appended
  2127.  to the base function names have these meanings:
  2128.  
  2129.  ═══════════════════════════════════════════════════════════════════════════
  2130.    SUFFIX         MEANING
  2131.  ───────────────────────────────────────────────────────────────────────────
  2132.    l           │  Uses a NULL-terminated list of arguments
  2133.    v           │  Uses a variable list of arguments
  2134.    e           │  Receives an environment pointer
  2135.    p           │  Searches PATH to find program files
  2136.  ───────────────────────────────────────────────────────────────────────────
  2137.  
  2138.       Rather than show all six function declarations for each of the
  2139.  families, we'll look at one representative function of each. Both of these
  2140.  functions automatically pass along a copy of the current environment to the
  2141.  child process. Here is the declaration for execl():
  2142.  
  2143.       #include <process.h>
  2144.       int execl(pathname, arg0, arg1, ..., argn, NULL);
  2145.       char *pathname;
  2146.       char *arg0, *arg1, . . .,*argn;
  2147.  
  2148.       The declaration for spawnvp() is
  2149.  
  2150.       #include <process.h>
  2151.       int spawnvp(modeflag, pathname, argv);
  2152.       int modeflag;
  2153.       char *pathname;
  2154.       char *argv[];
  2155.  
  2156.       The exec() family always overlays the current process (parent) with a
  2157.  new process (child) that destroys the parent, so it is impossible to return
  2158.  from an exec() function call. The spawn() family, however, runs the
  2159.  subprocess with an action determined by the modeflag parameter. A value of
  2160.  P_WAIT causes the parent process to suspend execution while the child runs.
  2161.  When the child process terminates, control is returned to the parent. A
  2162.  spawn() call with a modeflag value of P_OVERLAY produces the same effect as
  2163.  the equivalent exec() function call.
  2164.       Arguments are handled differently for the fixed-list (l) and variable-
  2165.  list (v) versions of these functions. The fixed-list version is used when
  2166.  we know in advance how many arguments will be presented. We place a NULL
  2167.  argument after the last argument we want to pass, to tell the exec() or
  2168.  spawn() function that the list is complete. In the variable-list version of
  2169.  the functions, a pointer to an argument list is passed. The argument list
  2170.  is handled just like the argv of a program's main() function, which was
  2171.  described earlier in this section.
  2172.       The first argument in a fixed list (arg0) or a variable list (argv[0])
  2173.  is usually a copy of the pathname argument. Other values will not cause an
  2174.  error, but NULL should not be used in the first position of a fixed list:
  2175.  The NULL would effectively hide the remainder of the list from the child
  2176.  process, since a NULL argument marks the end of the list. Recall that under
  2177.  DOS version 3.00 and later, the program name is available to the child as
  2178.  arg0 or argv[0].
  2179.       Here is an example of the spawnvp() function in action. It is used to
  2180.  run a text editor as a subprocess. If an EDITOR environment variable is
  2181.  defined, its string value is used as the program name. If EDITOR is not
  2182.  defined, NOTES uses the DOS EDLIN program. The editor is given the name of
  2183.  the current notesfile as an argument. This update to the NOTES program also
  2184.  adds an alternative way of terminating input (using a dot alone on a line
  2185.  like the UNIX mail program) and a way of ignoring the Ctrl-Break interrupt
  2186.  so that a user cannot garble the NOTES.TXT file by interrupting it in the
  2187.  middle of text entry.
  2188.  
  2189.  
  2190.               ╔══════════════════════════════════════════╗
  2191.               ║                                          ║
  2192.               ║ Click to view the listing found on page ║
  2193.               ║ 58 in the printed version of the book.   ║
  2194.               ║                                          ║
  2195.               ╚══════════════════════════════════════════╝
  2196.  
  2197.  
  2198.       The source listing for NOTES contains a few things we haven't shown
  2199.  before. First, the signal() function is used to disable the Ctrl-Break
  2200.  input from the keyboard. Since signal() returns a pointer value of -1 on
  2201.  errors instead of the usual NULL, we need to use the strange-looking cast
  2202.  of the value on the right side of the comparison. The (int(*)())-1 is
  2203.  needed because the C compiler expects a pointer to an integer.
  2204.       Second, the dot (.) is used to terminate data input. This involves a
  2205.  slight complication. A dot usually does not occur in free text as the first
  2206.  character of a line, but it may occur anywhere else. We depend on this, and
  2207.  check that the current position is the beginning of a line and that the
  2208.  input character is a dot before terminating data entry.
  2209.       Third, the EOF signal (a typed Ctrl-Z) may be used anywhere to halt
  2210.  data entry. It should, however, be used only on a line by itself. If Ctrl-Z
  2211.  is typed following some text on a line, some programs will not accept the
  2212.  line because it is not properly terminated. Programs should be written to
  2213.  handle this situation, but many insist that each line in a text file end
  2214.  with a CR/LF combination and may fail in unpredictable ways if a line dead-
  2215.  ends without the expected termination.
  2216.       The next program, RUN_ONCE, is a bit of a novelty, but it has a
  2217.  purpose in some restricted circumstances. Let's say you have an office
  2218.  setting in which computer users with very limited experience are expected
  2219.  to run a single program, perhaps an integrated database-spreadsheet-word
  2220.  processor-blender-and-kitchen-sink program. You want the system to come up
  2221.  running this program and you also want to prevent the users from exiting to
  2222.  DOS. If they quit the catchall program, the RUN_ONCE program tells them to
  2223.  turn off the power and then it locks up the machine.
  2224.  
  2225.  
  2226.               ╔══════════════════════════════════════════╗
  2227.               ║                                          ║
  2228.               ║ Click to view the listing found on page ║
  2229.               ║ 62 in the printed version of the book.   ║
  2230.               ║                                          ║
  2231.               ╚══════════════════════════════════════════╝
  2232.  
  2233.  
  2234.       After compiling this program, try running it on your favorite text
  2235.  editor or some other program. For example,
  2236.  
  2237.       run_once word myfile.doc
  2238.  
  2239.  loads and executes the RUN_ONCE program, which spawns Microsoft Word with
  2240.  MYFILE.DOC as an argument. When the user quits Word, the system displays
  2241.  the exit message and appears to be dead. A reboot is required to restart
  2242.  the system. If the RUN_ONCE program is embedded in the AUTOEXEC.BAT file,
  2243.  it will prevent the users from doing anything except what is planned for
  2244.  them by whoever sets up the system. (This is easily defeated by someone who
  2245.  knows DOS. All that is required is to load a floppy disk with another copy
  2246.  of DOS that does not have the RUN_ONCE program installed. But someone who
  2247.  knows this probably knows how to use DOS anyway.)
  2248.  
  2249.  
  2250.  Other Library Functions
  2251.  
  2252.       We have barely scratched the surface of the standard run-time library
  2253.  provided with the Microsoft C compiler. Our purpose has been to describe
  2254.  some representative library routines and to illustrate their use in real
  2255.  programs. As we develop programmer's utilities and other programs in the
  2256.  remainder of this book, we will introduce more of these routines in
  2257.  context.
  2258.       The functions and macros we have looked at so far are portable, with
  2259.  little or no change, to various versions of DOS and to UNIX-based operating
  2260.  systems. The next chapter delves into functions that are tied intimately to
  2261.  DOS and are therefore not easily ported to other operating environments.
  2262.  Although they lack portability, the functions are important for programs
  2263.  that need to obtain the highest level of performance possible from a PC.
  2264.  
  2265.  
  2266.  
  2267.  Chapter 5  PC Operating System Interfaces
  2268.  
  2269.  
  2270.  
  2271.       For me, one of the joys of working with a personal computer, after
  2272.  having spent years working on time-sharing systems over slow-speed data
  2273.  lines, is the incredible responsiveness of the PC in terms of screen
  2274.  updates. To "paint" a 24-line screen at 9600 bps (bits per second)
  2275.  typically takes about 4 seconds--not too bad, really. But at the more
  2276.  common 1200 bps used by most dial-up connections, a complete paint job
  2277.  takes closer to 16 seconds. Paging through a document or a source list at
  2278.  this speed becomes a bit of a chore, and one quickly learns how to use
  2279.  regular expression searches, small window sizes, and other tricks to
  2280.  minimize the time it takes to find the material of interest.
  2281.       In contrast, using one of the slowest methods available on a PC, the
  2282.  DOS print-character function call, painting a full 25-line screen takes
  2283.  only about 4 seconds. By using BIOS routines intelligently, one often can
  2284.  do the job in less than half a second. Using direct screen access, the
  2285.  updates become virtually instantaneous. The PC has spoiled me, and it is
  2286.  really hard to go back to dial-up operations: It feels like the whole world
  2287.  has been put into slow motion.
  2288.       In this chapter, we'll explore methods of managing screen displays via
  2289.  the DOS and BIOS interrupt services. The methods used here are not portable
  2290.  to UNIX and XENIX, and the BIOS routines are not even guaranteed to be
  2291.  portable to some nominally IBM-compatible hardware.
  2292.       This material is presented to acquaint you with some of the techniques
  2293.  used in commercial-quality programs. The mark of such programs is how well
  2294.  they serve the user's needs. Among the needs I hear most often expressed,
  2295.  the following are related to screen and system configuration:
  2296.  
  2297.         The program must work on any compatible machine, regardless of the
  2298.          configuration (disks, display type, and so forth).
  2299.  
  2300.         The screen must be updated quickly_no growing old waiting around
  2301.          for the next frame.
  2302.  
  2303.         If both color/graphics and monochrome display systems are
  2304.          installed, it must be possible to choose which to use.
  2305.  
  2306.         There must be no flickering, blizzard effect, or other visual
  2307.          "noise" at any time.
  2308.  
  2309.       Using DOS and BIOS routines, we can satisfy these needs almost
  2310.  completely. Only the first one is a problem because some machines were
  2311.  designed in a way that limits their BIOS compatibility. As a developer, I
  2312.  want to maximize the audience, so I address such machines by using the ANSI
  2313.  device driver, which is covered in Chapter 13.
  2314.       The routines in this chapter cover needed functions that are not
  2315.  already handled by the standard run-time library, with a few exceptions. In
  2316.  those few cases, a function is presented that duplicates a system function
  2317.  because that function may not be available in the library of other C
  2318.  compilers. A case in point is kbhit(): The function keyready() performs the
  2319.  same function for those whose libraries don't have kbhit() available.
  2320.       We begin with an overview of the DOS and BIOS access routines in the
  2321.  standard library. These routines are the low-level access functions upon
  2322.  which our interface routines are built. Then we will write a program that
  2323.  shows how to use many of the routines we develop. The routines presented
  2324.  here are not a complete set; they are the high-use routines that we will
  2325.  need shortly. Other routines will be added to the DOS and BIOS libraries as
  2326.  we need them in later chapters.
  2327.  
  2328.  
  2329.  System Interface Functions
  2330.  
  2331.       The C run-time library provided with Microsoft C contains several low-
  2332.  level system-access functions. We will use three of them as a basis for our
  2333.  DOS and BIOS interfaces: bdos(), int86(), and intdos().
  2334.       The functions int86x() and intdosx() are used in programs that use
  2335.  long addresses. They behave just like their int86() and intdos()
  2336.  counterparts, but they take an additional argument that specifies segment
  2337.  registers used in forming long addresses. The segment register values can
  2338.  be obtained by using the segread() function.
  2339.       The file dos.h in the \include directory contains several structure
  2340.  and union definitions that are needed by the system access functions. If
  2341.  you are not comfortable using C unions, here is a good example of how they
  2342.  may be used.
  2343.  
  2344.  
  2345.               ╔══════════════════════════════════════════╗
  2346.               ║                                          ║
  2347.               ║ Click to view the listing found on page ║
  2348.               ║ 67 in the printed version of the book.   ║
  2349.               ║                                          ║
  2350.               ╚══════════════════════════════════════════╝
  2351.  
  2352.  
  2353.       The header file defines two structures for the CPU registers and
  2354.  flags. The first is struct WORDREGS:
  2355.  
  2356.    /* word registers */
  2357.  
  2358.    struct WORDREGS {
  2359.            unsigned int ax;
  2360.            unsigned int bx;
  2361.            unsigned int cx;
  2362.            unsigned int dx;
  2363.            unsigned int si;
  2364.            unsigned int di;
  2365.            unsigned int cflag;
  2366.            };
  2367.  
  2368.       The second structure is struct BYTEREGS:
  2369.  
  2370.    /* byte registers */
  2371.  
  2372.    struct BYTEREGS {
  2373.            unsigned char al, ah;
  2374.            unsigned char bl, bh;
  2375.            unsigned char cl, ch;
  2376.            unsigned char dl, dh;
  2377.            };
  2378.  
  2379.       A composite word/byte register data structure is made by overlaying
  2380.  the two structures, like this:
  2381.  
  2382.    /* general purpose registers union - overlays the corresponding word and
  2383.     * byte registers.
  2384.     */
  2385.  
  2386.    union REGS {
  2387.            struct WORDREGS x;
  2388.            struct BYTEREGS h;
  2389.            };
  2390.  
  2391.       The union REGS lets us access the registers either as bytes (AL, AH,
  2392.  and so on) or as words (AX, BX, and so on). We can also access the system
  2393.  carry flag as a word register. It is unfortunate that we don't have similar
  2394.  access to the zero flag.
  2395.       Now that we have the data structures in mind (or at least on paper),
  2396.  we can examine the primary system-access routines. Each is presented here
  2397.  with a manual-page summary and a brief description. In the next two
  2398.  sections, we will apply these functions to the task of creating our DOS and
  2399.  BIOS libraries. Any file that calls the following functions must include
  2400.  the header file dos.h by using the line
  2401.  
  2402.       #include <dos.h>
  2403.  
  2404.       Here are the system-level functions:
  2405.  
  2406.    int bdos(dosfn, dosdx, dosal);
  2407.    int dosfn;                      /* function number */
  2408.    unsigned int dosdx;             /* DX register value */
  2409.    unsigned int dosal;             /* AL register value */
  2410.  
  2411.       The bdos() function is a DOS function interface that loads the DX and
  2412.  AL registers with values provided by the caller and then executes an  INT
  2413.  21H. Following the DOS function call, the value of the AX register is
  2414.  returned to the caller.
  2415.       The uses of bdos() are limited to DOS function calls that take
  2416.  arguments in either or both of the DX and AL registers (or none at all) and
  2417.  that do not use the carry flag to indicate errors. To minimize function-
  2418.  call overhead, we will use bdos() in some keyboard functions and recommend
  2419.  its use for macros or for in-line code within a program.
  2420.  
  2421.    int int86(intno, inregs, outregs);
  2422.    int intno;                      /* interrupt number */
  2423.    union REGS *inregs;             /* register values on call */
  2424.    union REGS *outregs;            /* register values on return */
  2425.  
  2426.       The int86() function is a general-purpose interface routine that gives
  2427.  us nearly complete access to the full range of software interrupts. The
  2428.  function sets up the registers according to the caller's wishes and
  2429.  executes the specified interrupt. Upon return from the function call,
  2430.  int86() fills outregs with the current register values and the value of the
  2431.  system carry flag, which will have a nonzero value if an error has
  2432.  occurred.
  2433.       We will use int86() to call BIOS routines for video and keyboard
  2434.  access and to check equipment and memory information. We also could use it
  2435.  to access DOS functions, but intdos() does the job faster.
  2436.  
  2437.    int intdos(inregs, outregs);
  2438.    union REGS *inregs;             /* register values on call */
  2439.    union REGS *outregs;            /* registers values on return */
  2440.  
  2441.       The intdos() function is just like the int86() function except that it
  2442.  is designed to make DOS function calls directly and does not require an
  2443.  interrupt number. The INT 21H interrupt number is effectively hard-coded
  2444.  into intdos(). The same input and output register behavior is observed as
  2445.  for int86().
  2446.       The declaration of the register arguments to int86() and intdos()
  2447.  shows them as pointers. The union and structure definitions in dos.h do not
  2448.  reserve storage; they simply describe the needed storage requirements. We
  2449.  must declare storage in our routines by a statement like
  2450.  
  2451.       union REGS inregs, outregs;
  2452.  
  2453.  This statement allocates automatic storage for the unions. We then can load
  2454.  values into inregs, make the needed function call, and extract data from
  2455.  outregs. (There is nothing sacred about the names inregs and outregs. Use
  2456.  anything that seems appropriate, but ideally the names should be
  2457.  descriptive.) The int86() and intdos() functions need the addresses of the
  2458.  unions. A call to intdos() looks like this:
  2459.  
  2460.       intdos(&inregs, &outregs);
  2461.  
  2462.       We can access elements of the structures either as bytes or as words.
  2463.  To load a value into AL, for example, we use the byte-oriented
  2464.  approach:
  2465.  
  2466.       inregs.h.al = value;
  2467.  
  2468.  To read the results of a function from the DX register, we use the word-
  2469.  oriented approach:
  2470.  
  2471.       value = outregs.x.dx;
  2472.  
  2473.  Next, we will put these system-level library functions to work for us in
  2474.  two important libraries in our growing collection of function
  2475.  libraries.
  2476.  
  2477.  
  2478.  DOS Library Routines
  2479.  
  2480.       The DOS library is composed of only a few routines that supplement the
  2481.  standard run-time library, which is very complete in its coverage of disk,
  2482.  keyboard, date/time, and many other functions. The routines presented here
  2483.  are primarily for the benefit of those who are using C compilers with
  2484.  libraries that do not fully support the DOS interface.
  2485.       A local header file, doslib.h, contains constants used to specify DOS
  2486.  interrupts and the function numbers for calls to INT 21H, the DOS
  2487.  "umbrella" interrupt.
  2488.  
  2489.  
  2490.               ╔══════════════════════════════════════════╗
  2491.               ║                                          ║
  2492.               ║ Click to view the listing found on page ║
  2493.               ║ 72 in the printed version of the book.   ║
  2494.               ║                                          ║
  2495.               ╚══════════════════════════════════════════╝
  2496.  
  2497.  
  2498.       DOS Version Number
  2499.  
  2500.       Under Microsoft C, the header file stdlib.h defines the global
  2501.  variables _osmajor and _osminor. These variables receive the DOS major and
  2502.  minor version numbers when a program starts running. The numbers may be
  2503.  used to verify that the version of DOS is capable of supporting features
  2504.  required by a program. For example, programs that use pathnames require DOS
  2505.  version 2.00 or later. The simple block of code added to the program
  2506.  provides low-cost insurance and alerts the user that it's time to upgrade.
  2507.  
  2508.    if (_osmajor < 2) {
  2509.            fprintf(stderr, "Need DOS 2.00 or later\n");
  2510.            exit(1);
  2511.    }
  2512.  
  2513.       Another way to get the version number is to ask DOS for the number.
  2514.  The function ver() does the job.
  2515.  
  2516.    /*      ver -- get the MS-DOS (or PC-DOS) version number   */
  2517.  
  2518.    #include <local\doslib.h>
  2519.  
  2520.    /************************************************************
  2521.    *  For MS-DOS versions prior to 2.00, the low byte (AL) of
  2522.    *  the return value is zero.  For versions 2.00 and beyond,
  2523.    *  the low byte is the major version number and the high
  2524.    *  byte (AH) is the minor version number.
  2525.    ************************************************************/
  2526.  
  2527.    int ver()
  2528.    {
  2529.            return(bdos(DOS_VERSION, 0, 0));
  2530.    }
  2531.  
  2532.       The ver() function uses bdos() to get the version number from DOS. It
  2533.  returns the return value from bdos(), which is the value in the AX
  2534.  register. AL holds the major version number and AH holds the minor version
  2535.  number. For versions of DOS prior to version 2.00, the returned major
  2536.  number is 0.
  2537.  
  2538.  
  2539.       Keyboard Functions
  2540.  
  2541.       The run-time library includes a couple of functions that read from and
  2542.  test the keyboard buffer. This section presents some alternative functions
  2543.  that do essentially the same jobs and which can easily be modified for use
  2544.  with other compilers.
  2545.       To gather the user's input, we can call the run-time library function
  2546.  getch(). It reads the next available character from the keyboard buffer and
  2547.  responds to a Ctrl-Break. If there is nothing ready to read, it waits until
  2548.  there is. This operation is called a "blocking read" because the machine
  2549.  simply marks time while waiting for the user to type something. If the user
  2550.  presses Ctrl-Break, getch() executes the Ctrl-Break handler.
  2551.       The function getkey() also does a blocking read. However, it differs
  2552.  from getch() in a few ways. First, it ignores Ctrl-Break. This prevents a
  2553.  user from breaking out of a program at a critical point that might damage
  2554.  files or data stored in memory. Second, getkey() checks the value of the
  2555.  keyboard code. If the code is a null byte (\0), it gets the next character
  2556.  code, bitwise-ORs it with 0x100 (which has the same effect as adding 256),
  2557.  and returns the modified value. This value can be compared against the
  2558.  constants that are defined in keydefs.h to determine what key was pressed.
  2559.  The defined values include most of the keys and combinations of keys on the
  2560.  keyboard.
  2561.       Here is the source code for keydefs.h:
  2562.  
  2563.  
  2564.               ╔══════════════════════════════════════════╗
  2565.               ║                                          ║
  2566.               ║ Click to view the listing found on page ║
  2567.               ║ 76 in the printed version of the book.   ║
  2568.               ║                                          ║
  2569.               ╚══════════════════════════════════════════╝
  2570.  
  2571.  
  2572.       This is the source code for getkey():
  2573.  
  2574.    /*      getkey -- return a code for single and combo keystrokes
  2575.     *      - returns a unique code for each keystroke or combination
  2576.     *      - ignores "Ctrl-Break" input     */
  2577.  
  2578.    #include <dos.h>
  2579.    #include <local\std.h>
  2580.    #include <local\doslib.h>
  2581.    #include <local\keydefs.h>
  2582.  
  2583.    int getkey()
  2584.    {
  2585.            int ch;
  2586.  
  2587.            /* normal key codes */
  2588.            if ((ch = bdos(KEYIN, 0, 0) & LOBYTE) != '\0')
  2589.                    return (ch);
  2590.  
  2591.            /* convert scan codes to unique internal codes */
  2592.            return ((bdos(KEYIN, 0, 0) & LOBYTE) | XF);
  2593.    }
  2594.  
  2595.       To determine whether a key has been pressed, we would use the run-time
  2596.  library function kbhit(), which returns a nonzero value if a code is
  2597.  available in the keyboard buffer, indicating that at least one key has been
  2598.  pressed. The function keyready(), presented next, does the same job.
  2599.  
  2600.    /*      keyready -- nonzero if the keyboard buffer
  2601.     *      has any codes waiting   */
  2602.  
  2603.    #include <dos.h>
  2604.    #include <local\doslib.h>
  2605.  
  2606.    int keyready()
  2607.    {
  2608.            union REGS inregs, outregs;
  2609.  
  2610.            inregs.h.ah = CH_READY;
  2611.            intdos(&inregs, &outregs);
  2612.            return (outregs.h.al);
  2613.    }
  2614.  
  2615.       The kbhit() or keyready() function should be called before an attempt
  2616.  is made to read the keyboard. This produces a "nonblocking read." If
  2617.  nothing is ready, the program can do something useful while it waits for
  2618.  the user to press a key. That something should be broken up into brief
  2619.  allocations of work (i.e., sending a character to the printer), so that the
  2620.  keyboard provides a timely response to user input. There is, however, no
  2621.  good reason to put the entire computer to sleep waiting for user input. The
  2622.  following code fragment shows a way to handle keyboard processing and a
  2623.  background task:
  2624.  
  2625.    .
  2626.    .
  2627.    .
  2628.    int k, getkey(), keyready();    /* keyboard functions */
  2629.    int do_task(TASK);              /* task dispatcher */
  2630.    TASK taskp;                     /* task pointer */
  2631.    .
  2632.    .
  2633.    .
  2634.    while (1) {
  2635.            /* get user's input */
  2636.            if (keyready()) {
  2637.                    k = getkey();
  2638.                    switch (k) {
  2639.                    case K_ESC:
  2640.                            do_exit();
  2641.                    .
  2642.                    .
  2643.                    .
  2644.                    }
  2645.            /* do background task */
  2646.            do_task(taskp);
  2647.            }
  2648.    .
  2649.    .
  2650.    .
  2651.  
  2652.       For example, if the task dispatcher is calling a routine that dumps
  2653.  characters into a printer spooling buffer, the printer appears to run
  2654.  continuously while not burdening the main program at all. User commands and
  2655.  input will be honored immediately. Other background tasks might include
  2656.  doing a hard disk-to-tape backup, checking timed alarms, and running a
  2657.  sprinkler system.
  2658.  
  2659.       The makefile, dos.mk, automatically builds the library dos.lib:
  2660.  
  2661.  # makefile for the DOS library
  2662.  LINC = c:\include\local
  2663.  LLIB = c:\lib\local
  2664.  
  2665.  # --- inference rules ---
  2666.  .c.obj:
  2667.          msc $*;
  2668.  
  2669.  # --- objects ---
  2670.  OBJS = getkey.obj keyready.obj ver.obj
  2671.  
  2672.  # --- compile sources ---
  2673.  getkey.obj:     getkey.c $(LINC)\std.h $(LINC)\doslib.h $(LINC)\keydefs.h
  2674.  
  2675.  
  2676.  keyready.obj:  keyready.c $(LINC)\doslib.h
  2677.  
  2678.  
  2679.  ver.obj:       ver.c $(LINC)\doslib.h
  2680.  
  2681.  
  2682.  # --- create and install the library ---
  2683.  $(LLIB)\dos.lib: $(OBJS)
  2684.          del $(LLIB)\dos.lib
  2685.          lib $(LLIB)\dos +$(OBJS);
  2686.  
  2687.  
  2688.  BIOS Library Routines
  2689.  
  2690.       The ROM BIOS in a PC offers a wide range of services that we can use
  2691.  to our advantage. We will write routines that let us determine how the host
  2692.  computer is configured, control the cursor, check the status of special
  2693.  keyboard keys (such as Caps Lock and Num Lock), and read and write
  2694.  characters and video attributes anywhere on the screen.
  2695.       The header file bioslib.h contains the constants needed to call
  2696.  various BIOS interrupts and services by symbolic names.
  2697.  
  2698.  
  2699.               ╔══════════════════════════════════════════╗
  2700.               ║                                          ║
  2701.               ║ Click to view the listing found on page ║
  2702.               ║ 82 in the printed version of the book.   ║
  2703.               ║                                          ║
  2704.               ╚══════════════════════════════════════════╝
  2705.  
  2706.  
  2707.       Equipment Determination Functions
  2708.  
  2709.       We'll start with a pair of functions that help us determine what
  2710.  equipment is installed in a PC and how much memory it has.
  2711.       The first of these functions, equipchk(), uses BIOS interrupt 11H to
  2712.  determine what devices are installed. A structure is used to hold the data
  2713.  obtained by equipchk(), which declares the structure globally and fills it
  2714.  when called. Other functions can also access the data if they contain the
  2715.  following external definition:
  2716.  
  2717.       extern struct EQUIP Eq;
  2718.  
  2719.       Here is the source code for equip.h and equipchk().
  2720.  
  2721.    /*      equip.h -- header for equipment determination/inventory   */
  2722.  
  2723.    struct EQUIP {
  2724.            short   disksys,        /* 1 if disks installed */
  2725.                    game_io,        /* 1 if game i/o adapter installed */
  2726.                    nprint,         /* number of printer ports */
  2727.                    nrs232,         /* number of serial ports */
  2728.                    vmode,          /* initial video mode (from switches) */
  2729.                    ndrive,         /* number of disk drives
  2730.                                       (from switches) */
  2731.                    basemem;        /* amount of base memory in Kbytes */
  2732.    };
  2733.  
  2734.  
  2735.               ╔══════════════════════════════════════════╗
  2736.               ║                                          ║
  2737.               ║ Click to view the listing found on page ║
  2738.               ║ 84 in the printed version of the book.   ║
  2739.               ║                                          ║
  2740.               ╚══════════════════════════════════════════╝
  2741.  
  2742.  
  2743.       To find out whether a game port is installed, call equipchk() and then
  2744.  look at the game_io member of the structure.
  2745.  
  2746.    #include <local\equip.h>
  2747.    extern struct EQUIP Eq;
  2748.            .
  2749.            .
  2750.            .
  2751.    if (equipchk())
  2752.            /* handle the error */;
  2753.    if (Eq.game_io == 1)
  2754.            printf("Game adapter installed\n");
  2755.  
  2756.       Although equipchk() can tell us how much memory is installed on the
  2757.  main system board, it cannot help us get the total installed memory value.
  2758.  The memsize() function calls BIOS interrupt 12H to get the system memory
  2759.  size, including any memory in the I/O channel on adapter cards (exclusive
  2760.  of display memory and that which is not contiguous with the main system-
  2761.  board memory).
  2762.  
  2763.    /*      memsize -- get memory size   */
  2764.  
  2765.    #include <dos.h>
  2766.    #include <local\std.h>
  2767.    #include <local\bioslib.h>
  2768.  
  2769.    int memsize()
  2770.    {
  2771.            union REGS inregs, outregs;
  2772.  
  2773.            return (int86(MEM_SIZE, &inregs, &outregs));
  2774.    }
  2775.  
  2776.       The size of main memory is reported as a 16-bit integer that
  2777.  represents the total number of kilobytes found.
  2778.  
  2779.  
  2780.       Keyboard Status
  2781.  
  2782.       Older PC keyboards did not have status lights on the Caps Lock and Num
  2783.  Lock keys. It is helpful to the user if the status of these keys is
  2784.  reported on the screen in applications in which the status is important.
  2785.  The same holds true for the Ins key and, in some situations, the Scroll
  2786.  Lock key. Also, we can use the status of the Shift, Ctrl, and Alt keys to
  2787.  alter the meaning of the function keys to do other jobs.
  2788.       The kbd--stat() function lets us obtain the needed information. It
  2789.  calls BIOS interrupt 16H, service 2 (status), and returns the value of the
  2790.  AL register. The header file keybdlib.h contains definitions of masks that
  2791.  are used to test the return value from kbd_stat() to determine which keys
  2792.  were pressed.
  2793.  
  2794.    /*      keybdlib.h   */
  2795.  
  2796.    #define KBD_READ        0       /* keyboard routine numbers */
  2797.    #define KBD_READY       1
  2798.    #define KBD_STATUS      2
  2799.  
  2800.    #define KS_RSHIFT       0x0001  /* bit masks for keys and states */
  2801.    #define KS_LSHIFT       0x0002
  2802.    #define KS_CONTROL      0x0004
  2803.    #define KS_ALT          0x0008
  2804.    #define KS_SL_STATE     0x0010
  2805.    #define KS_NL_STATE     0x0020
  2806.    #define KS_CL_STATE     0x0040
  2807.    #define KS_INS_STATE    0x0080
  2808.  
  2809.  
  2810.  
  2811.    /*      kbd_stat -- return the keyboard status
  2812.     *      word (bit-significant)   */
  2813.  
  2814.    #include <dos.h>
  2815.    #include <local\bioslib.h>
  2816.    #include <local\keybdlib.h>
  2817.  
  2818.    unsigned char kbd_stat()
  2819.    {
  2820.            union REGS inregs, outregs;
  2821.  
  2822.            inregs.h.ah = KBD_STATUS;
  2823.            int86(KEYBD_IO, &inregs, &outregs);
  2824.            return ((unsigned char)(outregs.h.al));
  2825.    }
  2826.  
  2827.  
  2828.       Video Access
  2829.  
  2830.       One of the most frequently used ROM BIOS interrupts is 10H, the access
  2831.  point for video services. We will use many of these services in our
  2832.  programs.
  2833.       The header file video.h contains some data structures used to hold
  2834.  important video-mode and cursor data. It also contains definitions of color
  2835.  and attribute values, special and drawing-character codes, and constants
  2836.  used by the mode and cursor structures.
  2837.  
  2838.  
  2839.               ╔══════════════════════════════════════════╗
  2840.               ║                                          ║
  2841.               ║ Click to view the listing found on page ║
  2842.               ║ 87 in the printed version of the book.   ║
  2843.               ║                                          ║
  2844.               ╚══════════════════════════════════════════╝
  2845.  
  2846.  
  2847.       The primary functions in interrupt 10H are those used to get the
  2848.  current video state data (mode, screen width, and visual page) and to set
  2849.  the video mode.
  2850.       The getstate() function invokes video service 15 to get the values of
  2851.  the video mode, the screen width (redundant information, because the mode
  2852.  number implies a width, but screen width is directly accessible), and the
  2853.  visual page number. Although the IBM Technical Reference manual calls this
  2854.  the active page, it is really the visual page. To be consistent with the
  2855.  way BASIC defines pages, the active page is defined as the one being
  2856.  written to and the visual page as the one being viewed. The page number
  2857.  returned by service 15 is the visual page. Most of the time, the visual and
  2858.  active page numbers are the same.
  2859.  
  2860.  
  2861.               ╔══════════════════════════════════════════╗
  2862.               ║                                          ║
  2863.               ║ Click to view the listing found on page ║
  2864.               ║ 91 in the printed version of the book.   ║
  2865.               ║                                          ║
  2866.               ╚══════════════════════════════════════════╝
  2867.  
  2868.  
  2869.       Because getstate() contains the declarations of the video structures,
  2870.  it should be called before you use any of the other video functions
  2871.  (clrscrn(), clrw(), scroll(), and setctype()) that depend on the data
  2872.  in those structures.
  2873.       The setvmode() function is used to set the video mode. It cannnot be
  2874.  used, however, to switch from one adapter to another. That task must be
  2875.  handled in another way, which is described in Chapter 11. The mode numbers
  2876.  are defined in video.h. The mode number presented to setvmode() is not
  2877.  value-checked.
  2878.  
  2879.  
  2880.               ╔══════════════════════════════════════════╗
  2881.               ║                                          ║
  2882.               ║ Click to view the listing found on page ║
  2883.               ║ 92 in the printed version of the book.   ║
  2884.               ║                                          ║
  2885.               ╚══════════════════════════════════════════╝
  2886.  
  2887.  
  2888.       For the IBM Monochrome Adapter, and for the standard Color Graphics
  2889.  Adapter (CGA) in any of its graphics modes, only page 0 is valid. On the
  2890.  CGA, the 40-column text modes permit up to eight screen pages in display
  2891.  memory and the 80-column text modes up to four screen pages. On the
  2892.  Enhanced Graphics Adapter (EGA), page ranges are a function of the mode as
  2893.  follows: mode 13, 0 through 7; mode 14, 0 through 3; modes 15 and 16, 0 and
  2894.  1.
  2895.       The job of setting the visual page falls to setpage(). The pagenumber
  2896.  argument must be valid for the current video mode because its value is not
  2897.  checked.
  2898.  
  2899.  
  2900.               ╔══════════════════════════════════════════╗
  2901.               ║                                          ║
  2902.               ║ Click to view the listing found on page ║
  2903.               ║ 94 in the printed version of the book.   ║
  2904.               ║                                          ║
  2905.               ╚══════════════════════════════════════════╝
  2906.  
  2907.  
  2908.       Next we have four functions that are used for cursor control. Two of
  2909.  them, readcur() and putcur(), deal with cursor positioning. The other two,
  2910.  getctype() and setctype(), are used to determine the cursor shape and to
  2911.  set it to a particular shape, respectively.
  2912.       The function readcur() gets the cursor position for the specified page
  2913.  and passes back the row and column position data to the calling
  2914.  routine.
  2915.  
  2916.  
  2917.               ╔══════════════════════════════════════════╗
  2918.               ║                                          ║
  2919.               ║ Click to view the listing found on page ║
  2920.               ║ 94 in the printed version of the book.   ║
  2921.               ║                                          ║
  2922.               ╚══════════════════════════════════════════╝
  2923.  
  2924.  
  2925.       The row and col arguments are pointers to simulate "call-by-reference"
  2926.  parameter passing, which allows readcur() to pass back more than a single
  2927.  value by directly accessing the storage locations of the variables in the
  2928.  calling function.
  2929.       The putcur() function moves the cursor to an absolute screen location
  2930.  on the specified screen page. The specified row, column, and screen-page
  2931.  values are not checked.
  2932.  
  2933.  
  2934.               ╔══════════════════════════════════════════╗
  2935.               ║                                          ║
  2936.               ║ Click to view the listing found on page ║
  2937.               ║ 95 in the printed version of the book.   ║
  2938.               ║                                          ║
  2939.               ╚══════════════════════════════════════════╝
  2940.  
  2941.  
  2942.       The cursor on a PC screen is a "hardware cursor" formed by one or
  2943.  more raster scan lines in the cell at the current cursor position. The
  2944.  cursor always blinks. A hardware cursor is available only in text modes and
  2945.  not in graphics modes, although it is quite easy to fabricate one in
  2946.  software.
  2947.       To determine the starting and ending scan lines for the cursor, we use
  2948.  getctype(). The function uses the same video service as readcur() but
  2949.  returns cursor type data, not position data. We can use getctype() in a
  2950.  program to save the starting and ending scan lines so that we can restore
  2951.  them with setctype() before the program terminates.
  2952.  
  2953.  
  2954.               ╔══════════════════════════════════════════╗
  2955.               ║                                          ║
  2956.               ║ Click to view the listing found on page ║
  2957.               ║ 96 in the printed version of the book.   ║
  2958.               ║                                          ║
  2959.               ╚══════════════════════════════════════════╝
  2960.  
  2961.  
  2962.       The inverse operation, setting the cursor type, is a bit more
  2963.  difficult than finding out what type it is. The job is done by setctype().
  2964.  The setctype() function sets the starting and ending scan lines to the
  2965.  values given by its arguments. It is incumbent upon the calling function to
  2966.  specify correct values for the prevailing display mode.
  2967.  
  2968.  
  2969.               ╔══════════════════════════════════════════╗
  2970.               ║                                          ║
  2971.               ║ Click to view the listing found on page ║
  2972.               ║ 97 in the printed version of the book.   ║
  2973.               ║                                          ║
  2974.               ╚══════════════════════════════════════════╝
  2975.  
  2976.  
  2977.       The video functions described next are used to read and write
  2978.  characters and attributes in any video mode, and dots--individual pixel
  2979.  values--in graphics modes only.
  2980.       The function readca() gets the character and video attribute of the
  2981.  character cell at the cursor position without regard for the video mode.
  2982.  This is a neat trick in graphics modes when you realize that, to do this,
  2983.  the BIOS video routine has to do a pattern-matching operation on the
  2984.  displayed image to find out what the character is.
  2985.  
  2986.  
  2987.               ╔══════════════════════════════════════════╗
  2988.               ║                                          ║
  2989.               ║ Click to view the listing found on page ║
  2990.               ║ 98 in the printed version of the book.   ║
  2991.               ║                                          ║
  2992.               ╚══════════════════════════════════════════╝
  2993.  
  2994.  
  2995.       When we are operating in one of the graphics modes, we can read the
  2996.  values of individual picture elements--pixels, or dots. The function
  2997.  readdot() returns the color number for the pixel at the specified row and
  2998.  column coordinates.
  2999.  
  3000.    /*      readdot -- read the value of a pixel (in graphics mode only) */
  3001.  
  3002.    #include <dos.h>
  3003.    #include <local\std.h>
  3004.    #include <local\bioslib.h>
  3005.  
  3006.    int readdot(row, col, dcolor)
  3007.    int row, col;
  3008.    int *dcolor;   /* pointer to dot color */
  3009.    {
  3010.            union REGS inregs, outregs;
  3011.  
  3012.            inregs.h.ah = READ_DOT;
  3013.            inregs.x.cx = col;
  3014.            inregs.x.dx = row;
  3015.            int86(VIDEO_IO, &inregs, &outregs);
  3016.  
  3017.            *dcolor = outregs.h.al;
  3018.  
  3019.            return (outregs.x.cflag);
  3020.    }
  3021.  
  3022.       The allowed row and column values depend on the graphics mode:
  3023.  
  3024.  ═══════════════════════════════════════════════════════════════════════════
  3025.   MODE            ROW RANGE           COLUMN RANGE
  3026.  ───────────────────────────────────────────────────────────────────────────
  3027.    4           │  0-319            │  0-199
  3028.    5           │  0-319            │  0-199
  3029.    6           │  0-639            │  0-199
  3030.   13           │  0-319            │  0-199
  3031.   14           │  0-639            │  0-199
  3032.   15           │  0-639            │  0-349
  3033.   16           │  0-639            │  0-349
  3034.  ───────────────────────────────────────────────────────────────────────────
  3035.  
  3036.       The function writec() writes a character or a string of identical
  3037.  characters, starting at the current cursor position. It does not change the
  3038.  cursor position. The number of repetitions must not cause the function to
  3039.  write past the end of the current line.
  3040.       This function is particularly handy for quickly drawing lines and
  3041.  boxes on the screen. We will also use it as a building block in functions
  3042.  that manipulate higher-level objects, such as text strings that are
  3043.  presented in fixed-length fields and scrolling windows.
  3044.  
  3045.    /*      writec -- write a character and attribute to the screen   */
  3046.  
  3047.    #include <dos.h>
  3048.    #include <local\std.h>
  3049.    #include <local\bioslib.h>
  3050.  
  3051.    int writec(ch, count, pg)
  3052.    unsigned char ch;       /* character */
  3053.    unsigned char attr;     /* attribute */
  3054.    int count;              /* number of repetitions */
  3055.    int pg;                 /* screen page for writes */
  3056.    {
  3057.            union REGS inregs, outregs;
  3058.  
  3059.            inregs.h.ah = WRITE_CHAR_ATTR;
  3060.            inregs.h.al = ch;
  3061.            inregs.h.bh = pg;
  3062.            inregs.h.bl = attr;
  3063.            inregs.x.cx = count;
  3064.            int86(VIDEO_IO, &inregs, &outregs);
  3065.  
  3066.            return (outregs.x.cflag);
  3067.    }
  3068.  
  3069.       The writeca() function is the same as writec(), except that it also
  3070.  writes the video attribute at the same location or region.
  3071.  
  3072.  
  3073.               ╔══════════════════════════════════════════╗
  3074.               ║                                          ║
  3075.               ║ Click to view the listing found on page ║
  3076.               ║ 100 in the printed version of the book.  ║
  3077.               ║                                          ║
  3078.               ╚══════════════════════════════════════════╝
  3079.  
  3080.  
  3081.       To simulate the printing behavior of a simple terminal, use the
  3082.  writetty() function to place characters on the screen. This function writes
  3083.  characters only, but it automatically does newline and screen-scrolling
  3084.  operations.
  3085.  
  3086.    /*      writetty -- write to screen using TTY interface   */
  3087.  
  3088.    #include <dos.h>
  3089.    #include <local\std.h>
  3090.    #include <local\bioslib.h>
  3091.  
  3092.    int writetty(ch, attr, pg)
  3093.    unsigned char ch;       /* character */
  3094.    unsigned char attr;     /* video attribute */
  3095.    int pg;                 /* screen page for writes */
  3096.    {
  3097.            union REGS inregs, outregs;
  3098.  
  3099.            inregs.h.ah = WRITE_TTY;
  3100.            inregs.h.al = ch;
  3101.            inregs.h.bl = attr;
  3102.            inregs.h.bh = pg;
  3103.            int86(VIDEO_IO, &inregs, &outregs);
  3104.  
  3105.            return (outregs.x.cflag);
  3106.    }
  3107.  
  3108.       The writedot() function writes a dot of the specified color at the row
  3109.  and column position passed as arguments. Valid color numbers are a function
  3110.  of the current graphics mode:
  3111.  
  3112.  ═══════════════════════════════════════════════════════════════════════════
  3113.    MODE                        COLOR NUMBERS
  3114.  ───────────────────────────────────────────────────────────────────────────
  3115.    4, 5, 10                │   0-3
  3116.    6                       │   0 and 1
  3117.    8, 9, 13, 14            │   0-15
  3118.    15                      │   0-1
  3119.    16                      │   0-4 or 0-15
  3120.  ───────────────────────────────────────────────────────────────────────────
  3121.  
  3122.  If bit 7 of the AL register is a 1, the dot color is exclusive-ORed (XOR)
  3123.  with the current dot color value.
  3124.  
  3125.    /*      writedot -- display a dot at the specified position   */
  3126.  
  3127.    #include <dos.h>
  3128.    #include <local\std.h>
  3129.    #include <local\bioslib.h>
  3130.  
  3131.    int writedot(r, c, color)
  3132.    int r, c;       /* row and column coordinate */
  3133.    int color;      /* dot (pixel) color */
  3134.    {
  3135.            union REGS inregs, outregs;
  3136.  
  3137.            inregs.h.ah = WRITE_DOT;
  3138.            inregs.h.al = color;
  3139.            inregs.x.cx = c;
  3140.            inregs.x.dx = r;
  3141.            int86(VIDEO_IO, &inregs, &outregs);
  3142.  
  3143.            return (outregs.x.cflag);
  3144.    }
  3145.  
  3146.       The following miscellaneous video functions are used to clear the
  3147.  current screen page, or a portion of it, scroll a region of the screen up
  3148.  or down, set the graphics palette or text border color, and write video
  3149.  attributes.
  3150.       The clrscrn() function uses the BIOS video scroll routine to
  3151.  initialize the visual screen page. The clrw() function clears a window of
  3152.  specified dimensions on the visual screen page.
  3153.  
  3154.  
  3155.               ╔══════════════════════════════════════════╗
  3156.               ║                                          ║
  3157.               ║ Click to view the listing found on page ║
  3158.               ║ 102 in the printed version of the book.  ║
  3159.               ║                                          ║
  3160.               ╚══════════════════════════════════════════╝
  3161.  
  3162.  
  3163.               ╔══════════════════════════════════════════╗
  3164.               ║                                          ║
  3165.               ║ Click to view the listing found on page ║
  3166.               ║ 103 in the printed version of the book.  ║
  3167.               ║                                          ║
  3168.               ╚══════════════════════════════════════════╝
  3169.  
  3170.  
  3171.       The palette() function serves two purposes. In text modes, it sets the
  3172.  screen border color. In graphics modes, it sets the color palette from
  3173.  which drawing colors are selected.
  3174.  
  3175.    /*      palette -- set graphics color values or border color   */
  3176.  
  3177.    #include <dos.h>
  3178.    #include <local\bioslib.h>
  3179.  
  3180.    int palette(id, color)
  3181.    unsigned int id, color;
  3182.    {
  3183.            union REGS inregs, outregs;
  3184.  
  3185.            inregs.h.ah = PALETTE;
  3186.            inregs.h.bh = id;
  3187.            inregs.h.bl = color;
  3188.            int86(VIDEO_IO, &inregs, &outregs);
  3189.  
  3190.            return(outregs.x.cflag);
  3191.    }
  3192.  
  3193.       To scroll the entire visual screen page or a rectangular portion of it
  3194.  up or down, use scroll(). The function takes a signed number: negative
  3195.  numbers scroll lines down the screen, nonzero positive numbers scroll lines
  3196.  up, and zero initializes the specified region. Vacated lines are set to
  3197.  blanks in the specified attribute.
  3198.  
  3199.  
  3200.               ╔══════════════════════════════════════════╗
  3201.               ║                                          ║
  3202.               ║ Click to view the listing found on page ║
  3203.               ║ 104 in the printed version of the book.  ║
  3204.               ║                                          ║
  3205.               ╚══════════════════════════════════════════╝
  3206.  
  3207.  
  3208.       The next function is really a composite of two BIOS video services. To
  3209.  change the attribute of a range of character positions without disturbing
  3210.  the characters, use writea(). This function reads the character and
  3211.  attribute at each affected position and writes back the same character in
  3212.  the new attribute while leaving the current cursor position unchanged.
  3213.  
  3214.  
  3215.               ╔══════════════════════════════════════════╗
  3216.               ║                                          ║
  3217.               ║ Click to view the listing found on page ║
  3218.               ║ 105 in the printed version of the book.  ║
  3219.               ║                                          ║
  3220.               ╚══════════════════════════════════════════╝
  3221.  
  3222.  
  3223.  Demonstration Program
  3224.  
  3225.       Now that we have a collection of DOS and BIOS interface routines, what
  3226.  can we do with them? Perhaps the best way to answer the question is to
  3227.  demonstrate their use in a program. A vehicle for showing off some of the
  3228.  routines is CURSOR, a program that magnifies the current cursor and lets us
  3229.  change its shape. CURSOR is proof that carefully written and applied BIOS
  3230.  video routines can produce startlingly fast screen manipulation--far from
  3231.  instantaneous, but fast enough for demanding commercial applications.
  3232.       Before examining the source code for CURSOR, we will prepare several
  3233.  functions that are built on the current set of BIOS video routines. The
  3234.  functions put_ch(), putstr(), and drawbox() can be viewed as a layer above
  3235.  the writec() and putcur() functions of the BIOS library just described.
  3236.  They provide often-used capabilities that are not provided in a convenient
  3237.  way by the primary BIOS functions.
  3238.       The put_ch() function displays a single character at the current
  3239.  cursor position and then advances the cursor to the next position. The name
  3240.  includes the underscore to distinguish put_ch() from putch(), a standard
  3241.  console I/O routine in the Microsoft run-time library. The putstr()
  3242.  function displays a text string and advances the cursor to the next
  3243.  displayable position. Combining these two functions and several of the low-
  3244.  level BIOS functions in drawbox() lets us create fine-ruled boxes for
  3245.  highlighted text and graphical elements of our screens. The drawbox()
  3246.  function uses the single-line drawing characters that are part of the IBM
  3247.  extended-ASCII character set. We could also use double-line and other
  3248.  combinations of line-drawing characters to produce distinctive box shapes,
  3249.  as we will do in later chapters.
  3250.       Here are the C source listings for put_ch(), putstr(), and
  3251.  drawbox():
  3252.  
  3253.    /*      put_ch -- display a character in the prevailing video
  3254.     *      attribute and advance the cursor position   */
  3255.  
  3256.    #include <local\video.h>
  3257.  
  3258.    int put_ch(ch, pg)
  3259.    register char ch;
  3260.    int pg;
  3261.    {
  3262.            int r, c, c0;
  3263.  
  3264.            readcur(&r, &c, pg);
  3265.            writec(ch, 1, pg);
  3266.            putcur(r, ++c, pg);
  3267.            return (1);
  3268.    }
  3269.  
  3270.  
  3271.  
  3272.    /*      putstr -- display a character string in the
  3273.     *      prevailing video attribute and return number
  3274.     *      characters displayed   */
  3275.  
  3276.    int putstr(s, pg)
  3277.    register char *s;
  3278.    unsigned int pg;
  3279.    {
  3280.            unsigned int r, c, c0;
  3281.  
  3282.            readcur(&r, &c, pg);
  3283.            for (c0 = c; *s != '\0'; ++s, ++c) {
  3284.                    putcur(r, c, pg);
  3285.                    writec(*s, 1, pg);
  3286.            }
  3287.            putcur(r, c, pg);
  3288.            return (c - c0);
  3289.    }
  3290.  
  3291.  
  3292.               ╔══════════════════════════════════════════╗
  3293.               ║                                          ║
  3294.               ║ Click to view the listing found on page ║
  3295.               ║ 108 in the printed version of the book.  ║
  3296.               ║                                          ║
  3297.               ╚══════════════════════════════════════════╝
  3298.  
  3299.  
  3300.       Now we can write cursor.c. The goal is to design a program that
  3301.  presents a magnified view of the cursor and that allows us to interactively
  3302.  adjust the cursor shape. The source for CURSOR is quite long. It is one of
  3303.  several programs I wrote to test the DOS and BIOS interface functions. The
  3304.  program shows how the DOS and BIOS interface functions are used in a
  3305.  working example. By writing "driver" programs as I wrote the interface
  3306.  functions, I was able to both anticipate what functions might be needed and
  3307.  test the functions as they were written.
  3308.       The pseudocode for CURSOR shows how the program works.
  3309.  
  3310.    get video information
  3311.    save current attribute/color values
  3312.    draw basic screen (header, cursor image, instructions)
  3313.    while (key is not Return)
  3314.           switch selection modes on left or right arrow
  3315.                clear start/stop selection pointer
  3316.                display new pointer
  3317.           adjust scan line position on up or down arrow
  3318.                clear current scan-line pointer
  3319.                calculate new pointer value
  3320.                update cursor image display
  3321.                     (scan lines, pointers)
  3322.    set new cursor type
  3323.    restore original attribute/color
  3324.    clear screen
  3325.  
  3326.       This simple description does not reveal a few details in the
  3327.  calculations needed to accurately display images for cursors on various
  3328.  types of hardware. For example, CURSOR automatically adjusts the displayed
  3329.  image to compensate for 80- and 40-column screen widths, and differing
  3330.  numbers of scan lines and widths in cursors on IBM monochrome display
  3331.  systems (9 by 14) and color/graphics systems (8 by 8). The C source, which
  3332.  shows how these details are handled, is in the file cursor.c.
  3333.  
  3334.  
  3335.  
  3336.               ╔══════════════════════════════════════════╗
  3337.               ║                                          ║
  3338.               ║ Click to view the listing found on page ║
  3339.               ║ 110 in the printed version of the book.  ║
  3340.               ║                                          ║
  3341.               ╚══════════════════════════════════════════╝
  3342.  
  3343.  
  3344.       The makefile, bios.mk, automatically builds the library bios.lib using
  3345.  the inference rules supplied by tools.ini. The makefile for CURSOR is
  3346.  contained in cursor.mk:
  3347.  
  3348.  
  3349.               ╔══════════════════════════════════════════╗
  3350.               ║                                          ║
  3351.               ║ Click to view the listing found on page ║
  3352.               ║ 116 in the printed version of the book.  ║
  3353.               ║                                          ║
  3354.               ╚══════════════════════════════════════════╝
  3355.  
  3356.  
  3357.  # makefile for CURSOR program
  3358.  
  3359.  LLIB=c:\lib\local
  3360.  
  3361.  cursor.obj:    cursor.c
  3362.       msc $*;
  3363.  
  3364.  cursor.exe:    cursor.obj $(LLIB)\bios.lib  $(LLIB)\dos.lib
  3365.          link $*, $*, nul, $(LLIB)\bios $(LLIB)\dos;
  3366.  
  3367.       When you run the CURSOR program, some computers will exhibit
  3368.  unexpected behavior as a function of the installed hardware. The AT&T
  3369.  PC6300, for example, does not permit "split" cursors (the stop scan line
  3370.  is less than the start scan line). CURSOR will allow you to fashion a split
  3371.  cursor, but on the 6300, it disables the cursor instead. Also, I have not
  3372.  modified the program to work with EGA-compatible boards. EGA boards use
  3373.  different numbers of scan lines per character cell than standard CGA boards
  3374.  operating in the same (or equivalent) modes.
  3375.  
  3376.       Before moving on to user interfaces, we will add one more function to
  3377.  the BIOS video library. The writestr() function is not used in this
  3378.  chapter, but it is called by a user-input function in Chapter 6. It
  3379.  displays a two-part message in a fixed field and leaves the cursor at its
  3380.  current position. The design of writestr() lets us concatenate strings
  3381.  visually and prevents them from extending beyond the available display
  3382.  area. In addition, if the resulting string does not completely fill the
  3383.  display field, writestr() pads the field with spaces in the prevailing
  3384.  attribute to clear any residue from the previous contents of the field.
  3385.  
  3386.  
  3387.               ╔══════════════════════════════════════════╗
  3388.               ║                                          ║
  3389.               ║ Click to view the listing found on page ║
  3390.               ║ 118 in the printed version of the book.  ║
  3391.               ║                                          ║
  3392.               ╚══════════════════════════════════════════╝
  3393.  
  3394.  
  3395.  Recommendations
  3396.  
  3397.       We need to address a few library management issues. In my work and in
  3398.  this book, I have elected to keep the number of functions at one per file
  3399.  except in rare circumstances where two or more functions have a symbiotic
  3400.  relationship. The cost of doing this is increased disk use. Although most
  3401.  of the source files are less than 500 bytes in size, they each require from
  3402.  2 to 8 KB of storage capacity on a typical hard disk. The amount used is a
  3403.  function of the block or cluster size of the disk, which is in turn a
  3404.  function of its capacity and the version of DOS used to format the
  3405.  disk.
  3406.       The primary advantage of separate functions in libraries is that the
  3407.  linker program adds the object code only for the functions used in a
  3408.  program, thus avoiding the possibility of dragging in a lot of unneeded
  3409.  instructions.
  3410.       The alternative--packaging related functions in a single object module
  3411.  in a library--can be effective too, as long as the functions so grouped are
  3412.  likely to be used together in a program. We could, for example, group the
  3413.  getctype(), setctype(), and other cursor-related functions in a single
  3414.  object module because they are usually employed together, as in the CURSOR
  3415.  example.
  3416.       The issue of compatibility across a wide range of machines deserves
  3417.  comment. To obtain the broadest PC compatibility for your programs, use the
  3418.  higher-level DOS function calls. They are often slower and less versatile
  3419.  than the BIOS functions, but programs built on them can be run on virtually
  3420.  any machine that runs DOS.
  3421.       Using BIOS interfaces will erode your market a little, but probably
  3422.  not enough to fret about for more than a few seconds. If a large enough
  3423.  market exists for a non-compatible machine (the Zenith Z-100, for example),
  3424.  you can create a customized version of the program because the dependencies
  3425.  can be limited to a few interface library functions.
  3426.       Compatibility of PC/DOS-based programs to UNIX and XENIX is another
  3427.  matter. Some fundamental differences in architecture make transporting a
  3428.  program developed under DOS on a PC to a UNIX-based system difficult, but
  3429.  not impossible. The primary difficulty is that users in a multi-user
  3430.  environment are typically not using the console; they are connected by
  3431.  dial-up or hard-wired lines to the host computer. Those connections are a
  3432.  bottleneck that reduces the available "bandwidth" of the user interface to
  3433.  a tiny fraction of what a typical PC has available on a dedicated machine.
  3434.  Despite this difficulty, most programs can be written for portability
  3435.  between DOS and UNIX/XENIX. In fact, I have done most of my development
  3436.  work under XENIX and then converted to DOS afterward.
  3437.       Going from DOS to UNIX is tougher because I tend to take advantage of
  3438.  all that the PC and DOS offer for program performance reasons. To gain some
  3439.  needed portability to UNIX, we can use the Termcap/Curses virtual terminal
  3440.  interface packages that permit UNIX to work with just about any
  3441.  alphanumeric video terminal. Termcap is a set of low-level functions that
  3442.  get terminal information from a database of terminal capabilities and
  3443.  provide control over cursor positioning and other video terminal
  3444.  functions.
  3445.       Curses is a high-level screen-management package that provides
  3446.  optimization of cursor movements and control over displayed text, video
  3447.  attributes, and keyboard interactions. Curses also supports buffers that
  3448.  are larger than the screen and provides modest windowing features.
  3449.       It is outside the scope of this book to provide detailed information
  3450.  on Termcap/Curses software. You may want use the virtual-terminal interface
  3451.  in your programs for one important reason: A program written under DOS on a
  3452.  PC using a Curses interface for screen management is easily ported to
  3453.  UNIX/XENIX. Give it a shot. A very good Curses implementation for the PC
  3454.  from Aspen Scientific works well with Microsoft C and other C compilers
  3455.  under DOS and is compatible with UNIX System V Curses. Lattice offers a
  3456.  non-optimizing version of Berkeley Curses as an adjunct to its C compiler.
  3457.  Other Curses implementations are available, but I have not tested
  3458.  them.
  3459.       There is much more to the DOS/BIOS interface than we have covered in
  3460.  this chapter. As we need additional functions, we can use what we have done
  3461.  so far as models for the needed functions. Next, we look at one of the most
  3462.  important aspects of any program--the user interface.
  3463.  
  3464.  
  3465.  
  3466.  Chapter 6  The User Interface
  3467.  
  3468.  
  3469.  
  3470.       The user interface has received a lot of attention in recent years.
  3471.  Human factors engineering (ergonomics)--the endeavor that is one part each
  3472.  of engineering, science, and "black magic"--is best known for dealing with
  3473.  such topics as how to design safe, comfortable car seats and how to lay out
  3474.  the cockpit of a jet aircraft. However, it also offers much information
  3475.  about how people use machines that have neither wheels nor wings. Although
  3476.  by no means a dissertation on human factors engineering, this chapter
  3477.  focuses on the primary points of contact between the computer and the
  3478.  user--the keyboard, the display screen, and the speaker--and on how to use
  3479.  these communications pathways effectively.
  3480.       As programmers, we must consider both the application and the audience
  3481.  for the application. In addition, we need to know about the environment in
  3482.  which a program is to be used, in order to provide the best possible match
  3483.  between the program and its intended users. Some programs lend themselves
  3484.  to a command-oriented type of operation. Frequently used tools that may
  3485.  become components of pipeline commands are examples of this category. Other
  3486.  programs are well suited to windowing and menus, or at least benefit from
  3487.  their use. Programs that are infrequently used and that may involve complex
  3488.  step-by-step procedures fall into this category. Still other programs can
  3489.  successfully blend the two approaches or switch easily between them. In
  3490.  Chapter 13, we will develop a program that provides convenient screen
  3491.  control with instructions from either the DOS command line or a simple menu
  3492.  of commands. The mode of operation depends on how the program is
  3493.  called.
  3494.       The use of pleasing visual effects and appropriate sound can do a lot
  3495.  to enhance both the appeal and the utility of a program. However,
  3496.  effectively using visual effects and sound in programs is no less an art
  3497.  than playing a musical instrument. Later in this chapter, we will explore
  3498.  the use of sound and text-oriented visual effects in our programs. But we
  3499.  will start with a view toward standardizing the way programs gather and
  3500.  process command-line options. Then we will develop some timer functions
  3501.  that are needed to do several important jobs.
  3502.  
  3503.  
  3504.  Standard Command-line Processing
  3505.  
  3506.       The standard user interface of unadorned UNIX/XENIX and DOS is the
  3507.  command line. One of the major modes of communication from the user to a
  3508.  program is via command-line options and other arguments. For years, there
  3509.  has been a need to standardize command-line option processing by programs
  3510.  in order to provide a clean and consistent user interface. This was evident
  3511.  long before CP/M and DOS were conceived in the fertile minds of their
  3512.  developers; the problem exists in Multics, UNIX, XENIX, and many other
  3513.  minicomputer and mainframe operating systems. Here is an example of the
  3514.  problem.
  3515.       Under UNIX, command-line options are introduced by a leading dash (-).
  3516.  An option letter selects a particular program option and may require an
  3517.  option argument. Therefore, -w80 might be used to select a page width (w)
  3518.  of 80 columns when used with a printer program. The option argument--80, in
  3519.  this case, but other values may be allowed--must be entered if the w option
  3520.  letter is used.
  3521.       An early version of UNIX (Version 7 in commercial circles) requires
  3522.  that options to the print command, r, be invoked individually, each having
  3523.  its own option flag and letter. Thus, printing a file in multiple columns
  3524.  without the usual header and footer lines requires a command constructed
  3525.  according to the following template:
  3526.  
  3527.       pr -2 -t filename
  3528.  
  3529.  in which the -2 and -t arguments specify the desired options, and filename
  3530.  is the lone filename argument.
  3531.       Later versions of UNIX, starting with the release of System III,
  3532.  permit the option letters to be combined and introduced by a single option
  3533.  flag, so a command of the form
  3534.  
  3535.       pr -2t filename
  3536.  
  3537.  does precisely the same job as the first one shown. A command typed
  3538.  according to the older specification (separate option flags) is also
  3539.  allowed.
  3540.       To complicate matters, some programs enforce the requirement that
  3541.  option letters that take arguments must have a space or tab between the
  3542.  letter and the argument while other programs require no white space (-w 80
  3543.  vs -w80, for example). These variations are the result of no small amount
  3544.  of anarchy among the developers and are the source of the confusion from
  3545.  which standards eventually evolve.
  3546.       So why should this matter to DOS programmers and users? Well, for one
  3547.  thing, DOS resembles UNIX in many ways and emulates many UNIX features and
  3548.  principles. For another, the same kinds of problems already exist under
  3549.  DOS. Some commands take option switches, usually indicated by the forward
  3550.  slash (/), but some programs accept the dash (-) as well. The option
  3551.  switches usually are placed after the command name but before any
  3552.  filenames, yet some commands require that option switches follow everything
  3553.  else on the command line. Still other programs don't care where the option
  3554.  flags and arguments go--they simply scan the entire command line before
  3555.  doing anything.
  3556.       Under recent versions of UNIX and XENIX, a utility function named
  3557.  getopt() is used to process option flags and arguments in a consistent way.
  3558.  The getopt() function can be used by any DOS program that accepts command-
  3559.  line options and arguments. We will apply it to a sample utility program,
  3560.  CAT, which concatenates files (one or more) onto the standard output
  3561.  stream.
  3562.       Figure 6-1 on the next page shows us how getopt() takes command
  3563.  lines and sensibly parses options and arguments. We have already seen
  3564.  in Chapter 3 how information in the DOS command tail is made available
  3565.  to a program via the argc and argv parameters to the main() function and
  3566.  how a program can be written to use the information in the command tail. We
  3567.  have not yet, however, agreed on a format for commands.
  3568.       For the programs in this book, we will present command lines according
  3569.  to a common template. The command name always appears first (this is an
  3570.  operating system requirement), followed by options, if any, followed by
  3571.  other data, typically filenames, if any. Options are introduced by a
  3572.  leading dash. Such a template works well for most of the commands we will
  3573.  encounter. However, with the more complicated commands, we probably will
  3574.  provide a menu-style interface instead of a command-oriented interface.
  3575.  
  3576.  
  3577.     C> cat -s *.c linebuf .h
  3578.            ░░░░░░░░░░░░░░░░░ the command tail
  3579.                               is processed by setargv()
  3580.  ══════════════════════════════════════════════════════════════════════════
  3581.     argc
  3582.    ┌────┐
  3583.    │▒6▒▒│           ┌────┬────┬────┬────┐
  3584.    └────┘  ┌───────│ C  │ A  │ T  │ \O │   (DOS version 3.0 and later)
  3585.            │        └────┴────┴────┴────┘
  3586.            │
  3587.            │             ┌────┬────┬────┐
  3588.    ┌────┐  │  ┌─────────│ -  │ s  │ \O │
  3589.  0 │▒▒■─┼──┘  │          └────┴────┴────┘
  3590.    ├────┤     │
  3591.  1 │▒▒■─┼─────┘                    ┌────┬────┬────┬────┬────┬────┬────┬────┐
  3592.    ├────┤        ┌────────────────│ F  │ I  │ L  │ E  │ 1  │ .  │ C  │ \O │
  3593.  2 │▒▒■─┼────────┘                 └────┴────┴────┴────┴────┴────┴────┴────┘
  3594.    ├────┤
  3595.  3 │▒▒■─┼────────┐                 ┌────┬────┬────┬────┬────┬────┬────┬────┐
  3596.    ├────┤        └────────────────│ F  │ I  │ L  │ E  │ 2  │ .  │ C  │ \O │
  3597.  4 │▒▒■─┼─────┐                    └────┴────┴────┴────┴────┴────┴────┴────┘
  3598.    ├────┤     │
  3599.  5 │NULL│     │          ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
  3600.    └────┘     └─────────│ l  │ i  │ n  │ e  │ b  │ u  │ f  │ .  │ h  │ \O │
  3601.                          └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
  3602.  
  3603.       FIGURE 6-1 ▐ Expanding ambiguous filename specifications
  3604.  
  3605.  
  3606.       Ambiguous filename arguments are expanded automatically by the setargv
  3607.  routine if it is included in the link list. The argument vector array,
  3608.  argv, contains pointers to strings in memory. The DOS command tail is
  3609.  everything that follows the command name on the DOS command line, including
  3610.  the initial space or tab that separates the first option or argument from
  3611.  the command name. Although the DOS command tail is limited to 128 bytes,
  3612.  ambiguous file names can be expanded to produce effective command tails of
  3613.  much greater length. The figure illustrates what the pointers and strings
  3614.  would look like for the command line
  3615.  
  3616.       cat -s *.c linebuf.h
  3617.  
  3618.  if the current directory contains the two C source files, file1.c and
  3619.  file2.c, the header file linebuf.h, and possibly other files. Consider what
  3620.  this command might produce if invoked in a directory with many C source
  3621.  files.
  3622.       The two C source-file names are stored with all letters converted to
  3623.  uppercase because DOS always converts to uppercase when it expands names.
  3624.  The -s option and the header file name are stored as literal copies because
  3625.  no expansions are required. The command name, available only under DOS 3.00
  3626.  and later versions, is presented with all letters converted to uppercase
  3627.  regardless of how they are typed by the user, again because of a DOS
  3628.  convention.
  3629.       The following source for getopt() was provided by AT&T. It is the
  3630.  current version of getopt() that is available from the AT&T UNIX System
  3631.  Toolchest.
  3632.  
  3633.  
  3634.               ╔══════════════════════════════════════════╗
  3635.               ║                                          ║
  3636.               ║ Click to view the listing found on page ║
  3637.               ║ 127 in the printed version of the book.  ║
  3638.               ║                                          ║
  3639.               ╚══════════════════════════════════════════╝
  3640.  
  3641.  
  3642.       The following synopsis and description of getopt() are based on its
  3643.  observed behavior under UNIX System V, Release 2, the current UNIX
  3644.  standard. If we use this standard, programs developed under DOS that do not
  3645.  employ any hardware-dependent code will be readily transportable to
  3646.  UNIX/XENIX (and vice versa).
  3647.       The getopt() function receives an argument count and an array of
  3648.  pointers to argument strings (usually copies of the argc and argv
  3649.  parameters of main()) and a string variable that is a list of allowable
  3650.  option flags. Single letters (uppercase and lowercase letters are unique)
  3651.  and  single-digit numbers are acceptable option letters. If a valid option
  3652.  letter in the list is followed by a colon, getopt() expects to find a
  3653.  following option argument.
  3654.       In addition to the parameters passed to the function, the interface to
  3655.  getopt() involves three global variables. The optind variable, the option
  3656.  index, is an integer that is initialized to 1; it keeps track of which
  3657.  option is currently being processed. A character pointer, optarg, is set to
  3658.  NULL unless the option being processed takes an argument, in which case
  3659.  optarg points to what should be the required argument. The third global
  3660.  variable is opterr. It is initialized to 1, which causes getopt() to report
  3661.  errors such as an option flag that is not a member of the passed list or a
  3662.  missing argument. Our programs can shut off error messages from getopt() by
  3663.  setting opterr to 0. Although optind and optarg must be declared as
  3664.  external variables in our programs, we need to declare opterr only if we
  3665.  intend to change its value.
  3666.  
  3667.                                COMMENT
  3668.            The UNIX and XENIX manual pages for getopt() contain an
  3669.            error. The value of optind does not default to 0. It is
  3670.            initialized to 1. Also, previous versions of getopt()
  3671.            emitted error messages when opterr was 0. The latest
  3672.            versions of getopt() have reversed the sense of the
  3673.            opterr variable and emit error messages if opterr is 1
  3674.            (the default value).
  3675.  
  3676.       The pseudocode for getopt() reveals the complexities of handling user
  3677.  input in a general way. The primary difficulty is in parsing optional
  3678.  arguments that may or may not be separated from the associated option
  3679.  letter by white space.
  3680.  
  3681.            index to first argument following command name
  3682.                         (done before the first call to getopt())
  3683.            if (no arguments OR
  3684.              arg not an option OR
  3685.              flag without option letter)
  3686.                    return EOF
  3687.            else if (special end-of-options indicator)
  3688.                    skip over argument
  3689.                    return EOF
  3690.            if (option letter not in option string)
  3691.                    if (opterr is non-zero)
  3692.                            print error message
  3693.                    return '?'
  3694.            if (option letter followed by ':' in option string)
  3695.                    if (option argument found)
  3696.                            set optarg to start of argument
  3697.                            return option letter
  3698.                    else
  3699.                            if (opterr is non-zero)
  3700.                                    print error message
  3701.                            return '?'
  3702.            else
  3703.                    set optarg to NULL
  3704.                    return option letter
  3705.  
  3706.       To show how the getopt() function is used in practice, we will now
  3707.  write a program that uses getopt() in its simplest form. Later in this
  3708.  chapter and in other chapters, we will use getopt() in more complex
  3709.  argumentprocessing situations.
  3710.       The CAT program, presented next, uses getopt() to process a single
  3711.  option. CAT is used to concatenate files. It accepts a list of zero or more
  3712.  files and produces a continuous data stream on its output. If only a single
  3713.  file is named, CAT simply lists the file's contents on the screen. Figure
  3714.  6-2 on the next page is the manual page for CAT.
  3715.  
  3716.  
  3717.               ╔══════════════════════════════════════════╗
  3718.               ║                                          ║
  3719.               ║ Click to view the figure found on page  ║
  3720.               ║ 132 in the printed version of the book.  ║
  3721.               ║                                          ║
  3722.               ╚══════════════════════════════════════════╝
  3723.  
  3724.       FIGURE 6-2 ▐  Manual page for CAT
  3725.  
  3726.  
  3727.       The source for CAT follows:
  3728.  
  3729.  
  3730.               ╔══════════════════════════════════════════╗
  3731.               ║                                          ║
  3732.               ║ Click to view the listing found on page ║
  3733.               ║ 132 in the printed version of the book.  ║
  3734.               ║                                          ║
  3735.               ╚══════════════════════════════════════════╝
  3736.  
  3737.  
  3738.       CAT duplicates its input stream on its output stream by calling
  3739.  fcopy(). The input comes from files named on the command line or from stdin
  3740.  if no files are named. Output is to the standard output stream, stdout.
  3741.  Therefore, CAT is minimally a filter. The transformation it performs is to
  3742.  merge data streams into a single sequential output stream. CAT is most
  3743.  frequently used to list the contents of one file on the console screen or
  3744.  to combine a group of files into a single file. It may be used as the
  3745.  source node of a pipeline command, either taking its input from named files
  3746.  or the console.
  3747.       Following is the C source for fcopy().
  3748.  
  3749.    /*
  3750.     *      fcopy -- copy input stream (fin) to output stream (fout)
  3751.     *      and return an indication of success or failure
  3752.     */
  3753.  
  3754.    #include <stdio.h>
  3755.  
  3756.    int fcopy(fin, fout)
  3757.    FILE *fin, *fout;
  3758.    {
  3759.            int errcount = 0;
  3760.            char line[BUFSIZ];
  3761.            char *s;
  3762.  
  3763.            while ((s = fgets(line, BUFSIZ, fin)) != NULL)
  3764.                    if (fputs(s, fout) == EOF)
  3765.                            ++errcount;
  3766.            if (ferror(fin))
  3767.                    ++errcount;
  3768.            return (errcount);      /* 0 if all went well */
  3769.    }
  3770.  
  3771.  Since both getopt() and fcopy() are going to be useful in other programs,
  3772.  add them to the utility library.
  3773.       Now that we have a function that lets us process command lines in a
  3774.  consistent way, we can move on to other aspects of the user-machine
  3775.  interface. We will now look at methods of producing machine-independent
  3776.  time measurements and delays.
  3777.  
  3778.  
  3779.  Timing Functions
  3780.       The routines and programs in this and the next subsections show how to
  3781.  time events, produce time delays, and create sounds. All depend in some way
  3782.  upon the PC's built-in timer circuits. We begin with a program called
  3783.  TIMER, which uses some of the ctime subroutines in the standard library
  3784.  that we examined in Chapter 4.
  3785.       TIMER is an external program that lets us time intervals from the DOS
  3786.  command level and from within batch files. The program is handy for timing
  3787.  events lasting from a few seconds to a few days. It is not suitable for
  3788.  shorter intervals because of the way it is implemented. A typical use
  3789.  consists of starting a timer, running a program or performing a task, and
  3790.  then calling the timer again to check the elapsed time. Figure 6-3 is the
  3791.  manual page for TIMER, and its source code is in timer.c.
  3792.  
  3793.  
  3794.               ╔══════════════════════════════════════════╗
  3795.               ║                                          ║
  3796.               ║ Click to view the figure found on page  ║
  3797.               ║ 136 in the printed version of the book.  ║
  3798.               ║                                          ║
  3799.               ╚══════════════════════════════════════════╝
  3800.  
  3801.       FIGURE 6-3 ▐  Manual page for TIMER
  3802.  
  3803.  
  3804.       The source for TIMER follows:
  3805.  
  3806.  
  3807.               ╔══════════════════════════════════════════╗
  3808.               ║                                          ║
  3809.               ║ Click to view the listing found on page ║
  3810.               ║ 137 in the printed version of the book.  ║
  3811.               ║                                          ║
  3812.               ╚══════════════════════════════════════════╝
  3813.  
  3814.  
  3815.       The source for TIMER illustrates a slightly more complicated use of
  3816.  getopt() than we saw in the CAT program. In addition to handling more
  3817.  option flags, TIMER has one option flag that requires an argument. The
  3818.  option -f takes an argument that specifies a filename; if -f is present,
  3819.  the next argument is presumed to be the required argument. The user can
  3820.  insert extra white space characters between the option flag and the
  3821.  argument, but it's not required. When getopt() detects the -f option, it
  3822.  immediately sets optarg to point to the start of the argument string. The
  3823.  program calling getopt() then interprets the argument.
  3824.       The TIMER program uses a special area in memory that is called the
  3825.  intra-application communications area (ICA). The ICA resides in an area of
  3826.  memory (addresses 4F0--4FF hex) that is reserved by DOS. The ICA is a mere
  3827.  16 bytes in length, but it's big enough to store four long integers, one
  3828.  for each of four separate timers. Few commercial programs use the ICA, but
  3829.  TIMER should protect itself against corruption of its data. Therefore, each
  3830.  timer value is represented by the lower 30 bits (0--29) of a four-byte
  3831.  long. The top two bits are used for status and identification purposes. The
  3832.  ID bit, bit 30, must be a logical value of 1. The most significant bit, bit
  3833.  31, is also set to a logical value of 1 to indicate that a timer value has
  3834.  been stored. If these bits do not have the correct values, attempts to show
  3835.  an elapsed time produce an error message.
  3836.       The time() library function returns a long integer that is the number
  3837.  of seconds since the epoch (00:00:00 on January 1, 1970 for ctime
  3838.  subroutines). There are 31,536,000 seconds in a normal year (add one day,
  3839.  86,400 seconds, for a leap year). Using the first 30 bits of a long to hold
  3840.  the time value gives us a range of 34 years, so TIMER will function
  3841.  correctly until 2004--by which time I hope to have a new computer and
  3842.  operating system. We can extend the span to 68 years by not using a
  3843.  separate ID bit, but that increases our risk of returning incorrect elapsed
  3844.  time values if some other program happens to use the ICA.
  3845.       TIMER is a small-model program because it requires little code space
  3846.  and even less data space. However, the ICA is in the BIOS segment, not the
  3847.  program's data space. TIMER uses the movedata() library function to read
  3848.  and write data in the ICA. Because movedata() requires two segmented
  3849.  addresses, TIMER also calls the segread() library function to get the data
  3850.  segment register value. The other address needed by movedata() is in the
  3851.  BIOS data segment. I chose to use 0x4F as the segment value (ICA_SEG) and
  3852.  an offset of 0 for the ICA, but these can be reversed if you prefer.
  3853.       When the -e flag is given, TIMER calls interval() with the number of
  3854.  seconds between "now" and when the timer was started. The saved timer is
  3855.  not altered. The interval() function translates the elapsed seconds into
  3856.  ASCII text using the form hh:mm:ss. If no timer number is specified, timer
  3857.  0 is used. The user may restart an interval timer by using the -s (start)
  3858.  option along with the timer number.
  3859.  
  3860.  
  3861.               ╔══════════════════════════════════════════╗
  3862.               ║                                          ║
  3863.               ║ Click to view the listing found on page ║
  3864.               ║ 141 in the printed version of the book.  ║
  3865.               ║                                          ║
  3866.               ╚══════════════════════════════════════════╝
  3867.  
  3868.  
  3869.       I have found TIMER to be most useful for recording work time against
  3870.  project accounts in my consulting practice and also as a means of measuring
  3871.  program execution times. Using the -f option, all data that would normally
  3872.  appear on the console screen is instead appended to the named file, which
  3873.  becomes a handy record of activities. When testing program execution
  3874.  speeds, I minimize the effects of disk loading times by setting up a
  3875.  virtual disk for the batch file, the TIMER program, and the data file.
  3876.  Everything else is run from a hard disk or a floppy disk so that loading
  3877.  and access times for the program will be taken into account. This beats
  3878.  using a stopwatch and ink on paper.
  3879.  
  3880.  
  3881.       The PC Timer and Time Delays
  3882.  
  3883.       Following is a brief description of the timer circuits in the PC and a
  3884.  look at some of the internal programs that keep track of time in various
  3885.  ways. Refer to Figure 6-4 while reading through this description. The
  3886.  figure shows the basic elements of the PC's timer and speaker-control
  3887.  circuits. We can ignore the speaker elements (shaded boxes) for now
  3888.  and concentrate our attention on the clock signal generator and
  3889.  timer/counters 0 and 1.
  3890.  
  3891.  
  3892.                 ╔═════════════════╗
  3893.                 ║  ┌───────────┐  ║
  3894.  ┌───────────┐┌─╫─│clk        │0 ║
  3895.  │1.19318 MHz││ ║  │        out├──╫───────── timer interrupt
  3896.  │from system││┌╫─│gate       │  ║
  3897.  │clock      │││║  └───────────┘  ║ ┌─────── RAM refresh
  3898.  └─────┬─────┘││║                 ║ │
  3899.              ││║  ┌───────────┐  ║ │
  3900.        ──────┼╫─┤clk        │1 ║ │  AND gate              Low-pass     /│
  3901.        │ tied  │║  │        out├──╫─┘   ┌──       driver     filter      /▒│
  3902.        │ high ─╫─│gate       │  ║┌───│▒▒▒\     ┌──────┐  ┌──────┐  ┌─┐▒▒│
  3903.        │        ║  └───────────┘  ║│    │▒▒▒▒)───│▒▒▒▒▒▒│─│▒▒▒▒▒▒│─│▒│▒▒│
  3904.        │        ║                 ║│ ┌─│▒▒▒/     │▒▒▒▒▒▒│  │▒▒▒▒▒▒│  └─┘▒▒│
  3905.        │        ║  ┌───────────┐  ║│ │  └─        └──────┘  └──────┘     \▒│
  3906.        └────────╫─│clk ▒▒▒▒▒▒▒│2 ║│ └──────────────────────┐             \│
  3907.                 ║  │▒▒▒▒▒▒▒▒out├──╫┘   ╔════════════════════╪══════╗
  3908.               ┌─╫─│gate▒▒▒▒▒▒▒│  ║    ║  port 61 hex       │      ║ speaker
  3909.               │ ║  └───────────┘  ║    ║ ┌─────────────────┬┴─┬──┐ ║
  3910.               │ ╚═════════════════╝    ║ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│1 │0 │ ║
  3911.               │ 8253-5 timer/counter   ║ └─────────────────┴──┴┬─┘ ║
  3912.               │                        ╚═══════════════════════╪═══╝
  3913.               └────────────────────────────────────────────────┘
  3914.                                          8255 programmable
  3915.                                          peripheral interface (PPI)
  3916.  
  3917.       FIGURE 6-4 ▐  Timer and speaker control circuits
  3918.  
  3919.  
  3920.       The primary clock rates for the system unit and peripheral interfaces
  3921.  are derived from a high speed crystal-controlled oscillator, an 8284A
  3922.  device. One of the output signals is divided down to 1.19318 megahertz
  3923.  (MHz) and is fed to the 8253-5 timer/counter on the CLK input of all three
  3924.  channels. (An 8254-2 timer/counter circuit is used in the PC/AT.) Channel 1
  3925.  is used to refresh main memory; we should not alter this channel in any
  3926.  way. Channel 2 will be described when we cover sound generation. Of primary
  3927.  interest to us now is channel 0, which is used to provide a system-wide
  3928.  timer interrupt (interrupt 8). This interrupt is called a clock tick.
  3929.       A clock tick occurs at a rate of about 18.2 per second, or one
  3930.  approximately every 55 milliseconds. The number of ticks per second is the
  3931.  timer input clock rate, 1.19381 MHz, divided by 65536, which is the
  3932.  divisor on timer channel 0. Other divisors may be used to produce different
  3933.  interrupt frequencies.
  3934.       Each timer/counter channel contains a 16-bit counter, a pair of 8-bit
  3935.  latches to hold the starting count, a pair of 8-bit output latches, and
  3936.  control logic. Each input clock pulse decrements the value held in the 16-
  3937.  bit counter until the value is 0. Setting a count of FFFF hex gives us a
  3938.  divisor of 65535, which is one less than needed. If, however, we set the
  3939.  starting count to 0, the first input clock pulse will cause the counter to
  3940.  go from 0 to 65535 (0-1=-1; all bits set to logical 1). This technique
  3941.  produces an effective divisor of 65536, the correct value, because the
  3942.  counter is treated as an unsigned integer. Consult the Intel timer/counter
  3943.  documentation for more information on how it works and how it may be
  3944.  programmed.
  3945.       The ticks are used by the ROM BIOS to update the time-of-day (TOD)
  3946.  clock, which is stored as the number of ticks since midnight. Therefore,
  3947.  any program that modifies the count on timer channel 0 must compensate for
  3948.  the change to maintain the tick rate seen by BIOS. We can use the same 18.2
  3949.  ticks per second as the basis of some machine-independent timing functions
  3950.  to produce delays and to create sound.
  3951.       We may want to build delays into our programs for a variety of
  3952.  reasons. A program that has a banner frame (I like to call it the "ego
  3953.  frame") must be seen to be appreciated, so it is appropriate to hold it on
  3954.  screen for a few seconds. Automated "slide shows" need controllable delays
  3955.  between frames to pace the presentation. Accurate short-duration delays are
  3956.  also needed to produce audible sound effects.
  3957.       When there was only a single PC model, many programmers were not too
  3958.  careful about how time delays were produced. We often depended upon the
  3959.  characteristics of the machine by using loops that went through the right
  3960.  number of iterations to waste the required time. Of course, faster
  3961.  processors and higher clock rates in many of the newer computers have
  3962.  shrunk delays produced this way to as little as an eighth of their former
  3963.  selves. We are now in need of a way to produce reliable, machine
  3964.  independent time delays.
  3965.       The delay() function uses the computer's timer to generate delays that
  3966.  are consistent with the entire family of IBM PCs and portable to most
  3967.  compatible machines. In order to produce the required waiting period, the
  3968.  delay() function converts the specified period into a number of timer
  3969.  ticks. It does so by multiplying the period by TICKRATE, which is defined
  3970.  in timer.h in the \include\local directory. It then adds that amount to the
  3971.  current number of clock ticks obtained from the time-of-day counter and
  3972.  stores the sum. Then getticks() is called repeatedly by delay(). The
  3973.  delay() function exits when the value returned by getticks() equals or
  3974.  exceeds the target value.
  3975.       The getticks() function queries the TOD clock and returns the current
  3976.  time of day as a count of clock ticks. One modification is made when the
  3977.  TOD clock rolls over from one day to the next: Since the TOD clock count is
  3978.  reset to zero and a flag is set to indicate the day increment, the
  3979.  getticks() function adds a day's worth of ticks (1,573,040 [1800B0 hex]) to
  3980.  the count it returns if the flag is nonzero. Since all machines in the IBM
  3981.  product line (and most compatibles) use the 1.19381 MHz timer clock rate,
  3982.  the delay period is the same on all of the machines and is totally
  3983.  independent of the CPU speed and master clock rate of the machine.
  3984.       The following files contain the sources for the header file, timer.h,
  3985.  and the functions delay() and getticks():
  3986.  
  3987.    /*
  3988.     *      timer.h -- header for timer control routines
  3989.     */
  3990.  
  3991.    /* timer clock and interrupt rates */
  3992.    #define TIMER_CLK       1193180L
  3993.    #define TIMER_MAX       65536L
  3994.    #define TICKRATE        TIMER_CLK / TIMER_MAX
  3995.  
  3996.    /* timer port access for frequency setting */
  3997.    #define TIMER_CTRL      0x43
  3998.    #define TIMER_COUNT     0x42
  3999.    #define TIMER_PREP      0xB6
  4000.  
  4001.  
  4002.               ╔══════════════════════════════════════════╗
  4003.               ║                                          ║
  4004.               ║ Click to view the listing found on page ║
  4005.               ║ 145 in the printed version of the book.  ║
  4006.               ║                                          ║
  4007.               ╚══════════════════════════════════════════╝
  4008.  
  4009.  
  4010.               ╔══════════════════════════════════════════╗
  4011.               ║                                          ║
  4012.               ║ Click to view the listing found on page ║
  4013.               ║ 146 in the printed version of the book.  ║
  4014.               ║                                          ║
  4015.               ╚══════════════════════════════════════════╝
  4016.  
  4017.  
  4018.       The delay() function has general applicability and will be used in
  4019.  other programs. We need to put it someplace that is easy to access. Because
  4020.  delay() is based on the getticks() function, which in turn uses the ROM
  4021.  BIOS, we will add both of these functions to the bios library. And, of
  4022.  course, timer.h takes a permanent home in \include\local.
  4023.  
  4024.  
  4025.       Audible Feedback
  4026.  
  4027.       We now turn our attention to sound generation. The first thing to
  4028.  notice about sound is that it irritates some users. Also, sound is totally
  4029.  inappropriate in some settings. Therefore, we must provide a way for the
  4030.  user to disable sound. The programs in this book will use a variable,
  4031.  usually the global variable Silent, to record the user's preference
  4032.  regarding sound. If Silent is logically TRUE, our program will be mute.
  4033.       The shaded boxes in Figure 6-4 on page 143 show the PC
  4034.  sound system. Notice how the sound system is built upon channel 2 of the
  4035.  timer subsystem and a couple of I/O ports in the 8255-5 programmable
  4036.  peripheral interface (PPI) device. The timer/counter produces squarewave
  4037.  signals that are amplified and filtered, then passed to the speaker. The
  4038.  AND gate ahead of the driver is controlled by bit 1 of the PPI port 61 hex.
  4039.  In addition, bit 0 of the same port controls the gate lead of timer channel
  4040.  2. Thus, the PPI can be used to turn sound on (both bits a logical 1) or
  4041.  off (either bit a logical 0). The advantage of using the timer to produce
  4042.  sound is that the sound plays in the "background," and does not steal
  4043.  precious cycles from the CPU.
  4044.       Alternatively, we can disable the timer by setting port 61 hex, bit 0
  4045.  low, and pulse the speaker directly under program control via bit 1. The
  4046.  problem with this approach is that the program can do nothing else while it
  4047.  attends to the speaker. We will not use this approach.
  4048.       The file sound.h, also in \include\local, is the header file for our
  4049.  sound routines. It contains definitions and macros used to control the
  4050.  speaker's state. SPKR_ON sets the low two bits in the peripheral interface
  4051.  port to logical 1s. This enables the timer and passes signals through to
  4052.  the speaker. The SPKR_OFF macro sets both bits low, turning the speaker off
  4053.  by robbing it of input signals.
  4054.  
  4055.    /*
  4056.     *      sound.h -- header for sound routines
  4057.     */
  4058.  
  4059.    #define PPI       0x61
  4060.    #define SPKR      0x03
  4061.    #define SPKR_ON   outp(PPI, inp(PPI) | SPKR)
  4062.    #define SPKR_OFF  outp(PPI, inp(PPI) & ~SPKR)
  4063.  
  4064.       As noted already, the speaker can operate independently, letting us
  4065.  put sounds in the background. We can demonstrate this with a simple control
  4066.  program, SPKR. This program turns the speaker off if it is called with no
  4067.  arguments and on if it receives any other number of arguments. The source
  4068.  for this test driver is contained in spkr.c (on the following page).
  4069.  
  4070.    /*
  4071.     *      spkr -- turn speaker ON/OFF
  4072.     *
  4073.     *              no args => OFF
  4074.     *              any arg(s) => ON
  4075.     */
  4076.  
  4077.    #include <local\sound.h>
  4078.  
  4079.    main(argc, argv)
  4080.    int argc;
  4081.    char **argv;
  4082.    {
  4083.            /* turn speaker on or off */
  4084.            if (argc == 1)
  4085.                    SPKR_OFF;
  4086.            else
  4087.                    SPKR_ON;
  4088.            exit(0);
  4089.    }
  4090.  
  4091.       The program has one problem--it does not set the pitch of the tone the
  4092.  speaker emits. This must be done by another program, TONE, a simple test
  4093.  driver that lets us set the pitch from the DOS command line. TONE takes a
  4094.  single argument that specifies the desired frequency in hertz. The usable
  4095.  range for the PC's internal speaker is about 40 Hz to a little more than 6
  4096.  KHz. Higher frequencies can also be used; however, these may cause some
  4097.  speakers to produce barely audible tones or clicking sounds. TONE calls
  4098.  upon a low-level routine, setfreq(), to calculate the needed divisor for
  4099.  the specified frequency and to set up the timer's channel 2, from which the
  4100.  speaker derives its input. The setfreq() function includes the timer.h
  4101.  header file which contains the declarations for the port addresses and
  4102.  values needed to set the frequency of the timer.
  4103.       The code for TONE and setfreq() follow:
  4104.  
  4105.    /*
  4106.     *      tone -- set the frequency of the sound generator
  4107.     */
  4108.  
  4109.    #include <stdio.h>
  4110.  
  4111.    main(argc, argv)
  4112.    int argc;
  4113.    char **argv;
  4114.    {
  4115.            extern void setfreq(unsigned int);
  4116.  
  4117.            if (argc != 2) {
  4118.                    fprintf(stderr, "Usage: tone hertz\n");
  4119.                    exit(1);
  4120.            }
  4121.  
  4122.            /* set the frequency in hertz */
  4123.            setfreq(atoi(*++argv));
  4124.            exit(0);
  4125.    }
  4126.  
  4127.  
  4128.  
  4129.    /*
  4130.     *      setfreq -- sets PC's tone generator to run
  4131.     *      continuously at the specified frequency
  4132.     */
  4133.  
  4134.    #include <conio.h>
  4135.    #include <local\timer.h>
  4136.  
  4137.    void
  4138.    setfreq(f)
  4139.    unsigned f;     /* frequency in hertz (approximate) */
  4140.    {
  4141.            unsigned divisor = TIMER_CLK / f;
  4142.  
  4143.            outp(TIMER_CTRL, TIMER_PREP);           /* prepare timer */
  4144.            outp(TIMER_COUNT, (divisor & 0xFF));    /* low byte of divisor */
  4145.            outp(TIMER_COUNT, (divisor >> 8));      /* high byte of divisor */
  4146.    }
  4147.  
  4148.       To use these programs to demonstrate some of the capabilities of the
  4149.  sound system, compile and link them, and then type
  4150.  
  4151.       TONE 1000
  4152.       SPKR ON
  4153.  
  4154.  This sets an audible pitch and turns on the speaker. You can do other work
  4155.  and the tone will continue to play unless some other program turns off the
  4156.  speaker. Typing
  4157.  
  4158.       SPKR
  4159.  
  4160.  without any arguments turns off the speaker.
  4161.       You can simulate a burglar alarm sound if you have a perverse
  4162.  nature. Key in the code for sweep.c, compile it and link it with the
  4163.  setfreq.obj file, and type
  4164.  
  4165.       SWEEP
  4166.  
  4167.  To stop the sound before the police arrive, press any key.
  4168.  
  4169.  
  4170.               ╔══════════════════════════════════════════╗
  4171.               ║                                          ║
  4172.               ║ Click to view the listing found on page ║
  4173.               ║ 150 in the printed version of the book.  ║
  4174.               ║                                          ║
  4175.               ╚══════════════════════════════════════════╝
  4176.  
  4177.  
  4178.       All this noise making is wonderful for something, I'm sure, but we
  4179.  will want finer control of the sound subsystem in our programs. We obtain
  4180.  that control via the sound() function, our high-level sound interface
  4181.  function. Using sound(), we can produce distinctive sounds such as a
  4182.  confirmation signal (confirm()), a warning signal (warn()), and many
  4183.  others. The source for the sound() function is contained in sound.c.
  4184.  
  4185.  
  4186.               ╔══════════════════════════════════════════╗
  4187.               ║                                          ║
  4188.               ║ Click to view the listing found on page ║
  4189.               ║ 151 in the printed version of the book.  ║
  4190.               ║                                          ║
  4191.               ╚══════════════════════════════════════════╝
  4192.  
  4193.  
  4194.       Notice that the speaker is turned on, continues to sound during the
  4195.  specified interval, and is then turned off for each tone being generated.
  4196.  Earlier demonstration programs turned on the speaker once, issued a series
  4197.  of pitch changes, and then turned off the speaker. Either way is OK, but
  4198.  the approach used by the sound() function is more versatile for our
  4199.  purposes.
  4200.       The program SOUNDS contains a few sample audible signals selected via
  4201.  a simple menu. You can use these as a starting point and create some of
  4202.  your own.
  4203.  
  4204.  
  4205.               ╔══════════════════════════════════════════╗
  4206.               ║                                          ║
  4207.               ║ Click to view the listing found on page ║
  4208.               ║ 152 in the printed version of the book.  ║
  4209.               ║                                          ║
  4210.               ╚══════════════════════════════════════════╝
  4211.  
  4212.  
  4213.       Don't forget to check the user's preference before "making a joyful
  4214.  noise." Give the user a command-line argument to disable sound: Use -s, for
  4215.  example, to set Silent to TRUE if it is Boolean, or to a nonzero value if
  4216.  it is a simple integer. Then a simple conditional such as
  4217.  
  4218.       if (Silent == FALSE)
  4219.              warn();
  4220.  
  4221.  will prevent the sound produced by warn() from being heard by an ungrateful
  4222.  audience!
  4223.  
  4224.  
  4225.  Getting User Input
  4226.  
  4227.       Next we turn our attention to the topic of getting input from the
  4228.  user. In our programs, we sometimes need to ask the user for a filename, a
  4229.  drive letter, or some other piece of important information. The goal of our
  4230.  next task is to design a general purpose input routine that prompts the
  4231.  user with a brief instruction and gathers a reply. It sounds simple enough:
  4232.  Just use printf() to display the prompt and scanf() to collect the
  4233.  response. This may sound reasonable, but this approach has some unnecessary
  4234.  limitations and some really serious problems.
  4235.       The scanf() function is not appropriate because it is designed for use
  4236.  with formatted data--the kind that is produced automatically by a program,
  4237.  not entered by hand. Besides, users are notorious for breaking things,
  4238.  accidentally or on purpose, and scanf() can be easily broken, even by the
  4239.  well-intentioned user who simply makes a typing mistake. We need something
  4240.  more versatile.
  4241.       Programs that have a lot of information on the screen may run short of
  4242.  screen space, so to make things interesting, we will require that the
  4243.  response field be able to take a response that is larger than the
  4244.  displayable field width. The response field must, therefore, be a window
  4245.  onto the user's response and must scroll sideways to permit the user to
  4246.  view and edit the response text.
  4247.       Figures 6-5A and 6-5B depict what we are trying to do in terms of
  4248.  screen presentation. We will use the BIOS video routines developed in
  4249.  Chapter 5 to control screen appearance and the DOS keyboard routines
  4250.  (also Chapter 5) to gather the user's input. Editing operations include
  4251.  the usual destructive backspace (<-) and single-character delete (Del). A
  4252.  character is inserted into the buffer by pushing all characters from the
  4253.  cursor to the end of the line, to the right one position, and by putting
  4254.  the input character in the vacated spot. The cursor moves to the right one
  4255.  position.
  4256.  
  4257.  
  4258.            prompt field      reply field
  4259.           ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
  4260.           │░│░│░│░│░│░│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│
  4261.           └─┴─┴─┴─┴─┴─┼─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
  4262.                       │                                     │
  4263.  LINEBUF              └───┐                                 └───┐
  4264.  headers  line buffers    │                                     │
  4265.  ┌─┬─┬─┐  ┌─┬─┬─┬─┬─┬─┬─┬─┼─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┼─┐
  4266.  │■│■│■┼─│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│
  4267.  └┴┬┴─┘  └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
  4268.   │ │
  4269.  ┌┼┬┬─┐  ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
  4270.  │■│■│■┼─│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│▒│
  4271.  └─┴─┴─┘  └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
  4272.  
  4273.       FIGURE 6-5A ▐ Memory and screen of getreply()
  4274.  
  4275.  
  4276.  
  4277.  TSTREPLY                                    File: c:\book\06user\timer.c
  4278.  ───────────────────────────────────────────────────────────────────────────
  4279.  
  4280.       FIGURE 6-5B ▐ Sample screen: Getting a pathname
  4281.  
  4282.  
  4283.       Cursor motions include moving to the beginning of text (Home), to the
  4284.  end of text (End), and left and right by one character (left and right
  4285.  arrows). Pressing Esc aborts the input operation and causes getreply() to
  4286.  return a NULL pointer to the calling function; pressing Enter anywhere in
  4287.  the line returns a pointer to the reply string. The calling function is
  4288.  responsible for validating the reply it receives.
  4289.       If getreply() is to be used more than once for a given reply field in
  4290.  a form or an interactive program, it would be wonderful if it could recall
  4291.  the most recent response, or better yet, scroll back and forth through a
  4292.  list of previous responses. Rather than put all of the complications
  4293.  associated with list management into getreply(), we will have the calling
  4294.  function pass a pointer to an array of line buffers that it sets up. Then
  4295.  getreply() permits the user to scan up and down the list of line buffers by
  4296.  using the up and down arrow keys.
  4297.       This design may seem to be a tall order to fill, but it is actually
  4298.  not that difficult. Several cooperating functions give us a very nice user
  4299.  interface routine and teach us a few things about windows and editing, too.
  4300.  We will name the routine getreply() and try to generalize it enough to make
  4301.  it useful in many programs.
  4302.       We will call on the standard library to help us with a few tasks. The
  4303.  memory function, memcpy(), copies strings within a single segment. The
  4304.  source and destination strings will be allowed to overlap to permit
  4305.  sideways scrolling. The memcpy() function guarantees that no characters
  4306.  will be lost in an overlapping copy operation. The reply string buffer
  4307.  within getreply() is initialized to null bytes (\0) before it is allowed to
  4308.  receive any user data. This action guarantees that the resulting string
  4309.  will be properly terminated.
  4310.       After getting a preliminary version of getreply() working and grousing
  4311.  over a few of its idiosyncracies, I arrived at the following sources for
  4312.  the function, which behaves rather well when challenged by even the most
  4313.  hostile users. The line buffer data structure is defined in linebuf.h, and
  4314.  the source for the getreply() function is in getreply.c.
  4315.  
  4316.    /*
  4317.     *  linebuf.h
  4318.     */
  4319.  
  4320.    typedef struct lb_st {
  4321.            struct lb_st *l_next;
  4322.            struct lb_st *l_prev;
  4323.            char *l_buf;
  4324.    } LINEBUF;
  4325.  
  4326.  
  4327.               ╔══════════════════════════════════════════╗
  4328.               ║                                          ║
  4329.               ║ Click to view the listing found on page ║
  4330.               ║ 157 in the printed version of the book.  ║
  4331.               ║                                          ║
  4332.               ╚══════════════════════════════════════════╝
  4333.  
  4334.  
  4335.       A few parts of the code appear to be a bit dense and need some
  4336.  explanation. The dimension of the reply field is calculated by subtracting
  4337.  the prompt field width from the working area allowed to getreply() by the
  4338.  calling function. Values are not error-checked, so be sure to pass
  4339.  something that makes sense.
  4340.       The bulk of the work is done in the while statement that loops as long
  4341.  as the Return key (or Enter as IBM prefers to call it) is not pressed. If a
  4342.  key is a printable ASCII character, it is inserted into the buffer. If not,
  4343.  it is checked to see whether it is an editing or cursor movement command.
  4344.  Valid commands are executed and invalid keypresses evoke an error response.
  4345.       Controlling the window position relative to the input text is handled
  4346.  by a pair of pointers. The character pointer cp tracks the current
  4347.  input/editing position in the buffer, and wp, also a character pointer,
  4348.  points to the first character in the buffer that will be displayed in the
  4349.  reply field window. A test within the loop assures that the editing/input
  4350.  position is always within the displayed reply window.
  4351.       The getreply() function uses an external error message routine. This
  4352.  practice assures the calling routine that nothing will be displayed
  4353.  arbitrarily on the screen. The calling function has complete control as to
  4354.  when and where the error message will appear.
  4355.       One thing that angers me when I use some programs is having old error
  4356.  messages still displayed on the screen. The code for getreply() prevents
  4357.  messages from being displayed past their time. A message flag is set when a
  4358.  new error message is issued and is cleared with the next keystroke. The
  4359.  message handler is sent a null message, which is its hint to clean up the
  4360.  message display area. It can ignore the hint, but it usually should not.
  4361.       To see getreply() in action, compile and link TSTREPLY, and run it.
  4362.  Although the program does little more than call getreply(), it gives us a
  4363.  way to test getreply() and demonstrate its operation. It also provides all
  4364.  of the code necessary to set up a message handler and manage a line buffer
  4365.  array. The C source is in tstreply.c:
  4366.  
  4367.  
  4368.               ╔══════════════════════════════════════════╗
  4369.               ║                                          ║
  4370.               ║ Click to view the listing found on page ║
  4371.               ║ 161 in the printed version of the book.  ║
  4372.               ║                                          ║
  4373.               ╚══════════════════════════════════════════╝
  4374.  
  4375.  
  4376.  
  4377.  Chapter 7  Automatic Program Configuration
  4378.  
  4379.  
  4380.  
  4381.       We saw in Chapters 3 and 6 how programs can be given optional
  4382.  arguments to control their behavior in order to meet special needs. In the
  4383.  case of CAT in Chapter 6, for example, the -s option causes CAT to remain
  4384.  silent about missing files. The use of such a command-line option is a
  4385.  manual means of configuring a program.
  4386.       However, the use of command-line options is often an impractical
  4387.  approach, particularly if a program uses many options. Users often will
  4388.  avoid reading the manual page and therefore may give up on a perfectly good
  4389.  program. What can be done about this?
  4390.       One solution is to treat an additional program feature as you would an
  4391.  extra nose on your face--avoid it if you can. No sense cluttering up a
  4392.  simple and effective program with lots of little gargoyle-like appendages.
  4393.  Try to keep a program simple and geared to doing a single task as well as
  4394.  possible. If you believe that a program needs another feature to round out
  4395.  its capabilities, see whether other users of the program agree. But avoid
  4396.  the temptation to put in every feature requested by every user; the result
  4397.  will usually be a program that nobody uses.
  4398.       Assuming that there is no way to avoid adding that slick new feature
  4399.  to your program, let's at least look at ways of automating things to make
  4400.  using your software product easier for the "liveware." We can provide
  4401.  automatic program configuration in several tidy, user-controlled ways,
  4402.  including, but not limited to, using the program name and using external
  4403.  configuration files.
  4404.  
  4405.  
  4406.  Using the Program Name
  4407.  
  4408.       As we learned earlier, the name used to invoke a program under
  4409.  DOS version 3.00 and later is available to the program. We could,
  4410.  therefore, use a program's name to alter its behavior. For example, if some
  4411.  additional code were put into our CAT program from the previous chapter, we
  4412.  could cause the program to become a "silent" CAT simply by making a copy of
  4413.  CAT.EXE with the name SCAT.EXE. Then the command
  4414.  
  4415.       C> scat filename
  4416.  
  4417.  would have the same effect as the command
  4418.  
  4419.       C> cat -s filename
  4420.  
  4421.  used in our current design.
  4422.       All we need to do is add these lines to cat.c just after line 46,
  4423.  following the optional argument-processing loop:
  4424.  
  4425.       if (strcmp(pgm, "SCAT") == 0)
  4426.                   Silent = TRUE;
  4427.  
  4428.       Many other programs lend themselves to this form of configuration.
  4429.  Under UNIX and XENIX, giving a program another name adds only a link to the
  4430.  original file. No additional space is required for the program file's
  4431.  contents. However, there is a price for doing this under DOS. Every
  4432.  additional name for the same program file requires an additional directory
  4433.  entry, a file allocation table entry, and storage space. Using aliases,
  4434.  then, is not recommended except in rare situations in which the convenience
  4435.  outweighs the lost disk space.
  4436.  
  4437.  
  4438.  Using Configuration Files
  4439.  
  4440.       There is another convenient way to configure a program. Like the use
  4441.  of the program's name, it too provides automatic operation. Although it
  4442.  also requires additional disk space, it typically uses only a single
  4443.  cluster, not a complete duplication of the original program file.
  4444.       In this method, variables and values are read from a file and are used
  4445.  to initialize or update selected program variables at any time during the
  4446.  operation of the program. Several levels of configuration files may be
  4447.  used. An arrangement that works well in practice uses a global data file
  4448.  that is located in a directory pointed to by a DOS environment variable.
  4449.       For example, on my hard-disk-based systems, I use a single
  4450.  subdirectory called c:\config as a convenient location for all
  4451.  configuration files for the programs that accept external configuration.
  4452.  Each of these files bears the name of the program it configures plus a .CNF
  4453.  extension. Thus, c:\config\progname.cnf contains the configuration data for
  4454.  the PROGNAME program. The DOS variable CONFIG is defined by the statement
  4455.  
  4456.       set config=c:\config
  4457.  
  4458.  in my AUTOEXEC.BAT file.
  4459.       A local configuration file--one located in the current directory--
  4460.  may override the global file. This permits directory-by-directory control
  4461.  over program behavior. For instance, a user of a program that prints the
  4462.  contents of a text file may want one type of behavior for program source
  4463.  files and quite another for a "dumb-things-I-gotta-do" list.
  4464.       The pseudocode for handling the configuration layers just described is
  4465.  as follows:
  4466.  
  4467.            initialize program control variables to built-in values
  4468.            if (local configuration file is found)
  4469.                    overlay defaults with local configuration values
  4470.            else if (specified DOS variable found)
  4471.                    if (global configuration file found)
  4472.                            overlay defaults with global values
  4473.            else
  4474.                    return NULL file pointer
  4475.            update variables per command-line instructions, if any
  4476.  
  4477.       It is not an error for either the local or the global configuration
  4478.  file, or both, to be missing, but it is an error for program-control
  4479.  variables not to be initialized by the program before they are used. The
  4480.  responsibility for the first step in the process--providing default
  4481.  initialization--is left to the main program. The local and global steps can
  4482.  be performed by a new addition to our utility library, the fconfig()
  4483.  function. We still permit command-line arguments to override other settings
  4484.  on a per-invocation basis, as indicated by the last step in the process
  4485.  described by the pseudocode.
  4486.       Here is the C source code for the fconfig() function, which seeks a
  4487.  configuration file and, if it finds one, returns a FILE pointer to the
  4488.  file, which is open and ready to read from the top.
  4489.  
  4490.  
  4491.               ╔══════════════════════════════════════════╗
  4492.               ║                                          ║
  4493.               ║ Click to view the listing found on page ║
  4494.               ║ 168 in the printed version of the book.  ║
  4495.               ║                                          ║
  4496.               ╚══════════════════════════════════════════╝
  4497.  
  4498.  
  4499.       The calling function must specify the name of an environment variable,
  4500.  which may contain a global-configuration directory name, and the name of
  4501.  the configuration data file itself.
  4502.       The name of the DOS variable that may hold a configurationdirectory
  4503.  pathname is passed as a parameter to fconfig(), not hard-coded into the
  4504.  function. This gives a calling program the latitude of specifying any
  4505.  environment-variable name it wants. The pname string variable within
  4506.  fconfig() is large enough (MAXPATH plus room for a terminating null byte)
  4507.  to hold the longest pathname permissible under DOS.
  4508.       The environment variable, if it exists, holds the pathname that
  4509.  represents the chain of directories leading up to the place where the
  4510.  global configuration file would be located. The fconfig() function still
  4511.  must append a backslash and the filename to complete the full pathname of
  4512.  the configuration file. (Because the backslash is the C "escape" character,
  4513.  two backslashes are needed to get one out, the first turning off the
  4514.  special meaning of the second.)
  4515.       Nothing we've done so far places any restrictions on the type of data
  4516.  that can be stored in and read from the configuration data file. The best
  4517.  format is highly dependent on the amount of data and what will be done with
  4518.  it. If a lot of Boolean flags are used to control a program, the best
  4519.  approach might be to use a binary format with all bits significant. If the
  4520.  data will be typed in by a casual user, the best approach might be to use
  4521.  strictly ASCII text. In practice, it turns out that the ordinary text file
  4522.  is best because it is more easily transported from one system to another
  4523.  without concerns about the native size of data words or the ordering of
  4524.  bytes within data words.
  4525.       All data read into a program from a configuration file should be
  4526.  qualified in some way before being used to update program data. Data files
  4527.  created by mere mortals often will contain errors, such as typos, incorrect
  4528.  data types, out-of-range values, or too many or too few values. Data files
  4529.  created by a custom-designed setup program are less prone to such problems;
  4530.  however, checking all data wastes little time compared with the time it
  4531.  takes to reboot a system that chokes on its input.
  4532.  
  4533.  
  4534.  Printer Control Functions
  4535.  
  4536.       Now we'll look at an example of program configuration that nearly
  4537.  every PC user has a need for at one time or another. In this section of the
  4538.  chapter, we will develop a set of printer-interface routines that are aimed
  4539.  primarily at controlling the fonts of Epson and IBM dot-matrix printers.
  4540.  Then, in the next section, we will use the printer routines as the basis of
  4541.  a printer-control program that enables us to control the printer from the
  4542.  DOS command line and from batch files.
  4543.       Let's begin with a look at Epson and compatible printers, including
  4544.  the original IBM PC printer. Many other printers use Epson-compatible
  4545.  control codes, so the following routines are applicable without change to a
  4546.  wide range of printers. For those printers that are not Epson compatible,
  4547.  our interface accepts user-supplied configuration data from a file.
  4548.       To simplify matters a bit, we will make a few rules. First, the
  4549.  printer must be able to produce emphasized text without having to backspace
  4550.  and retype the text repeatedly. Second, the printer must be able to
  4551.  underline without having to back up and restrike the text with underscores.
  4552.  Third, if the printer does not offer a particular mode (double-strike,
  4553.  compressed, or expanded, for example), initializing the control codes for
  4554.  the mode to null strings ("") must effectively turn off the mode in the
  4555.  interface routines; no attempt is made to synthesize a mode from
  4556.  combinations of other modes.
  4557.       Limitations such as these would be deadly to a commercial printer-
  4558.  control program, but they are acceptable here in our demonstration of
  4559.  printer control because they simplify our task and make it more manageable.
  4560.  If you need something more elaborate, you can use this program as a base
  4561.  and add controls for paper length, top-of-form position, line-to-line
  4562.  spacing, and many other features. The principles are the same as for the
  4563.  font-control set described here.
  4564.       The file printer.h in the \include\local directory contains the
  4565.  default values for Epson MX/FX-series printers and a set of printer
  4566.  variables used by the interface routines.
  4567.  
  4568.  
  4569.               ╔══════════════════════════════════════════╗
  4570.               ║                                          ║
  4571.               ║ Click to view the listing found on page ║
  4572.               ║ 170 in the printed version of the book.  ║
  4573.               ║                                          ║
  4574.               ╚══════════════════════════════════════════╝
  4575.  
  4576.  
  4577.       The ASCII codes are used directly to control printer modes (SO, SI,
  4578.  and so forth) or to build up control-code strings (ESC in conjunction with
  4579.  other characters).
  4580.       A set of font-type constants is defined so that each font type is
  4581.  mapped to a single bit. This permits fonts to be "stacked" to form
  4582.  composite fonts by using the bitwise OR. For example, ITALICS | UNDERLINE
  4583.  combines the values 0x10 and 0x20 to request an underlined italic
  4584.  font.
  4585.       The file printer.c contains a set of three related printer-control
  4586.  functions: setprnt(), clrprnt(), and setfont(). These functions use the
  4587.  variables defined in printer.h to determine what control strings to send to
  4588.  the printer. Here is the text of printer.c:
  4589.  
  4590.  
  4591.               ╔══════════════════════════════════════════╗
  4592.               ║                                          ║
  4593.               ║ Click to view the listing found on page ║
  4594.               ║ 172 in the printed version of the book.  ║
  4595.               ║                                          ║
  4596.               ╚══════════════════════════════════════════╝
  4597.  
  4598.  
  4599.       The setprnt() function has the Epson default-control strings hard-
  4600.  coded into it. However, if the user wishes to use some other printer,
  4601.  setprnt() calls fconfig() to look for a local or global printer.cnf file to
  4602.  override these values. If a configuration file is found, each line is read
  4603.  in and assigned to the next printer control-code variable. If too many or
  4604.  too few codes are obtained, an error is indicated by a return value of -1.
  4605.  A return value of 0 tells the caller all went well, but it is still
  4606.  possible that the codes were received in the wrong sequence or that bad
  4607.  codes were given in the configuration file. The setprnt() function cannot
  4608.  detect such errors; the printer will simply not operate correctly if it
  4609.  gets bad control strings.
  4610.       The next function, clrprnt(), resets the printer to the normal font.
  4611.  It does so by turning off each special font individually rather than by
  4612.  using the hardware reset-control string. The hardware reset on most
  4613.  printers resets the top-of-form and causes the paper to creep a bit each
  4614.  time it is called. Because of this, I have elected to avoid using the reset
  4615.  command entirely, although its value is contained in the configuration data
  4616.  structure for possible future use. The clrprnt() function calls upon the
  4617.  standard library function fputs() with the control strings needed to turn
  4618.  off each printer font mode. It sends the strings to the output stream
  4619.  specified by its only argument.
  4620.       The setfont() function accepts two arguments: one specifies the
  4621.  desired font combination, and the other specifies the destination stream.
  4622.  The first thing setfont() does is call clrprnt() to cancel all currently
  4623.  active print modes. Then it issues control strings for each of the desired
  4624.  modes.
  4625.       Most print-mode combinations are legal, but a few are not. In this
  4626.  design, two font combinations are excluded. The setfont() function returns
  4627.  a FAILURE indication if the caller requests the compressed mode combined
  4628.  with either a double-strike or emphasized mode. Epson printers don't allow
  4629.  these, but some other printers might. All other combinations of supported
  4630.  fonts and print modes are permitted.
  4631.       The control strings are emitted one at a time for each requested mode.
  4632.  Thus the request
  4633.  
  4634.       setfont(EMPHASIZED | EXPANDED);
  4635.  
  4636.  in a program results in the control string for emphasized mode being sent
  4637.  to the destination stream, followed by the control string for expanded
  4638.  mode.
  4639.       To permit ready access to the printer interface, we will compile it by
  4640.  typing
  4641.  
  4642.       msc print;
  4643.  
  4644.  and then add the print.obj file to our utility library using the
  4645.  command
  4646.  
  4647.       lib\lib\local\util +print;
  4648.  
  4649.       Now we can proceed to an example of how to use this simple but
  4650.  effective printer interface.
  4651.  
  4652.  
  4653.       Printer Control Program
  4654.  
  4655.       My original purpose in designing the printer interface just described
  4656.  was to gain some control over the printer from within my C programs.
  4657.  (Chapter 9 is devoted to a general-purpose printer program that uses this
  4658.  interface.) The approach I took gave me reasonable printer control from C
  4659.  programs, and, with the small amount of extra work that we'll do next, from
  4660.  the DOS command line and from batch files as well.
  4661.       We now have the tools needed to control print modes, but getting
  4662.  access to the printer from DOS involves one additional step: packaging the
  4663.  basic control functions in a DOS program file that accepts option flags to
  4664.  set desired modes. In addition, we will write a supplementary program that
  4665.  allows us to send arbitrary text strings to the printer.
  4666.       The MX program, originally named for its use with my trusty old MX-80
  4667.  printer, controls the basic print modes via command-line arguments. The
  4668.  program also works nicely with the Epson FX- and JX-series printers and
  4669.  with many other compatible printers. The configurability of MX permits its
  4670.  use with many other printers, too. A look at the manual page (Figure 7-1)
  4671.  and source listing for MX shows that it is simple to use and equally simple
  4672.  to program.
  4673.  
  4674.  
  4675.               ╔══════════════════════════════════════════╗
  4676.               ║                                          ║
  4677.               ║ Click to view the figure found on page  ║
  4678.               ║ 177 in the printed version of the book.  ║
  4679.               ║                                          ║
  4680.               ╚══════════════════════════════════════════╝
  4681.  
  4682.       FIGURE 7-1 ▐ Manual page for MX
  4683.  
  4684.  
  4685.               ╔══════════════════════════════════════════╗
  4686.               ║                                          ║
  4687.               ║ Click to view the listing found on page ║
  4688.               ║ 178 in the printed version of the book.  ║
  4689.               ║                                          ║
  4690.               ╚══════════════════════════════════════════╝
  4691.  
  4692.  
  4693.       Each time MX is invoked, it checks for a configuration file. This can
  4694.  take a little time on a floppy disk-only system. If you will be using MX
  4695.  with nothing but Epson-compatible printers on a floppy-based system, you
  4696.  may want to bypass the configuration step and build in the Epson default-
  4697.  control strings for faster execution.
  4698.       MX uses getopt(), which is now part of our utility library, plus all
  4699.  of the printer-interface routines in the printer object file. Most of the
  4700.  option flags are obvious; however, two require some explanation. The -o
  4701.  option takes as an argument the name of a destination stream, presumably a
  4702.  disk file, to be used in place of the standard output stream. This allows
  4703.  us to capture the output of MX for diagnostic purposes or to create files
  4704.  that can drive the printer directly as formatting scripts.
  4705.       The -t option uses the ASCII formfeed character to eject a page (go to
  4706.  top-of-form). This will work on nearly all modern printers. To accommodate
  4707.  all printers, you may instead want to permit the use of a series of
  4708.  newlines to get to the top of the next page. The PR program described in
  4709.  Chapter 9 shows how to use either control mechanism.
  4710.       The text of mx.mk tells the Microsoft MAKE command how to assemble the
  4711.  MX program.
  4712.  
  4713.    # makefile for mx program
  4714.  
  4715.    LINC=c:\include\local
  4716.    LLIB=c:\lib\local
  4717.  
  4718.    mx.obj:         mx.c $(LINC)
  4719.            msc $*;
  4720.  
  4721.    mx.exe:         mx.obj $(LLIB)\util.lib
  4722.            link $*, $*,, $(LLIB)\util;
  4723.  
  4724.       The PRTSTR program is an auxiliary program that prints an arbitrary
  4725.  string. PRTSTR also permits optional newline suppression, which gives users
  4726.  the ability to construct printed lines in a mixture of print modes to
  4727.  obtain various textual effects. The default for PRTSTR is to end the string
  4728.  with a newline, which causes the printer to return to the beginning of the
  4729.  current line and then move down one line. In addition, if it receives no
  4730.  arguments, PRTSTR issues a single newline. Think of PRTSTR as an
  4731.  intelligent substitute for the DOS ECHO command. The only advantage that
  4732.  ECHO has over PRTSTR is that ECHO is built into the DOS COMMAND processor,
  4733.  so it operates faster. However, because printers tend to move like molasses
  4734.  in the Arctic, PRTSTR poses no problem in typical uses.
  4735.       The manual page for PRTSTR (Figure 7-2) describes its
  4736.  operation and application. The source code and makefile (prtstr.mk) are, by
  4737.  now, quite predictable.
  4738.  
  4739.  
  4740.               ╔══════════════════════════════════════════╗
  4741.               ║                                          ║
  4742.               ║ Click to view the figure found on page  ║
  4743.               ║ 183 in the printed version of the book.  ║
  4744.               ║                                          ║
  4745.               ╚══════════════════════════════════════════╝
  4746.  
  4747.       FIGURE 7-2 ▐  Manual page for PRTSTR
  4748.  
  4749.  
  4750.               ╔══════════════════════════════════════════╗
  4751.               ║                                          ║
  4752.               ║ Click to view the listing found on page ║
  4753.               ║ 184 in the printed version of the book.  ║
  4754.               ║                                          ║
  4755.               ╚══════════════════════════════════════════╝
  4756.  
  4757.  
  4758.       This very simple program has only one wrinkle that requires close
  4759.  examination. When the newline-suppression option is selected, we suppress
  4760.  not only the trailing newline, but also the single space that follows the
  4761.  last argument string. This permits us to mix print modes within text
  4762.  strings. We could, for example, print part of a word in boldface and
  4763.  another part in italics to distinguish the fixed and variable parts of a
  4764.  user's response.
  4765.  
  4766.    # makefile for prtstr program
  4767.  
  4768.    LINC=c:\include\local
  4769.    LLIB=c:\lib\local
  4770.  
  4771.    prtstr.obj:     prtstr.c $(LINC)
  4772.            msc $*;
  4773.  
  4774.    prtstr.exe:     prtstr.obj $(LLIB)\util.lib
  4775.            link $*, $*,, $(LLIB)\util;
  4776.  
  4777.       The SAMPLE.BAT file is a demonstration of printer control from a DOS
  4778.  batch file. The sample text (Figure 7-3 on the next page) shows
  4779.  off some of the many text combinations that can easily be obtained with
  4780.  simple commands. Note the use of mixed print modes in some of the
  4781.  lines.
  4782.  
  4783.  
  4784.               ╔══════════════════════════════════════════╗
  4785.               ║                                          ║
  4786.               ║ Click to view the listing found on page ║
  4787.               ║ 186 in the printed version of the book.  ║
  4788.               ║                                          ║
  4789.               ╚══════════════════════════════════════════╝
  4790.  
  4791.  
  4792.               ╔══════════════════════════════════════════╗
  4793.               ║                                          ║
  4794.               ║    Figure 7-3 is found on page 186       ║
  4795.               ║    in the printed version of the book.   ║
  4796.               ║                                          ║
  4797.               ╚══════════════════════════════════════════╝
  4798.  
  4799.       FIGURE 7-3 ▐  Output from SAMPLE.BAT
  4800.  
  4801.  
  4802.       Please keep in mind that this is a minimal printer interface intended
  4803.  to demonstrate means of automatic program configuration. Modern dot-matrix
  4804.  and ink-jet printers offer many additional font and print-mode selections,
  4805.  and laser printers permit even greater selections. Have fun making the most
  4806.  of them!
  4807.  
  4808.  
  4809.  Selection Functions
  4810.  
  4811.       In configuring programs, some types of input require fairly elaborate
  4812.  processing to get the input data into an internal form that is suitable for
  4813.  program use. For example, the file select.c contains several functions that
  4814.  work together to manage input data presented in the form of lists.
  4815.       The next section of this chapter uses the SELECT package to process
  4816.  tab settings. Our objective is to take the user-supplied list of columns
  4817.  that represents tab stops and convert it into an array of integers that can
  4818.  be used by a program to initialize its internal tab stops to something
  4819.  other than the hardware tabs that occur at multiples of eight columns,
  4820.  starting in column one (1, 9, 17, and so on). (Users generally count the
  4821.  leftmost column as one, not zero.) The SELECT routines can be used to
  4822.  process other types of lists, too. For instance, the routines will be used
  4823.  in Chapter 9 to select pages to be printed by the PR program.
  4824.       By way of example, one of our programs might receive a list such as 6,
  4825.  20, 71--80 as a command-line argument. To be used by the program, the data
  4826.  must be extracted from the list, which is a character string, converted to
  4827.  integer form, and stored somehow for easy access. We'll deal with internal
  4828.  data access soon. For now, let's just worry about the process of extracting
  4829.  and converting the data.
  4830.       The example specification shown has twelve members. Technically
  4831.  speaking, each entry in the list represents a range, although some ranges
  4832.  have the same minimum and maximum values. Thus the 6 entry may be
  4833.  represented internally as min = 6, max = 6, whereas the 71--80 entry is
  4834.  represented as min = 71, max = 80. We will use an array of structures to
  4835.  hold these values in data storage that is private to the routines within
  4836.  the SELECT module.
  4837.       The SELECT module contains three related functions: mkslist() creates
  4838.  a selection list from user data; save_range() is an internal function
  4839.  (static) called by mkslist() to extract starting and ending values from
  4840.  explicit and implicit range specifications; and selected() returns a
  4841.  nonzero value to its caller to indicate that a given value is contained in
  4842.  the  selection list.
  4843.       The routines in the SELECT module communicate through a global data
  4844.  structure, Slist, which is defined as
  4845.  
  4846.       struct slist_st {
  4847.               long int s_min;
  4848.               long int s_max;
  4849.       } Slist[NRANGES + 1];
  4850.  
  4851.  This allocates an array of NRANGES + 1 structures, each of which can hold a
  4852.  minimum and a maximum value for a single range. NRANGES is currently
  4853.  defined to be 10, but any reasonable number may be used. I have had no need
  4854.  to specify more than 10 ranges in a list. Slist is declared globally here
  4855.  for testing and demonstration purposes, but in practice it should be
  4856.  declared static so that only the functions in the SELECT module can access
  4857.  it.
  4858.       The pseudocode description of mkslist() shows how a selection list is
  4859.  parsed. The manifest constant BIGGEST is defined in \local\std.h to be the
  4860.  largest number (65535) that can be stored in an unsigned integer. A global
  4861.  variable, Highest, is initially 0 and is updated to the highest value
  4862.  received in the list.
  4863.  
  4864.    if (list in null)
  4865.            set min = 0
  4866.            set max = BIGGEST
  4867.    else
  4868.            while (next token of list is not NULL)
  4869.                    save_range(token)
  4870.            set min in next to -1 (terminate list)
  4871.  
  4872.       The save_range() function could be coded right in mkslist() because it
  4873.  is only called once. However, by separating out the task of converting a
  4874.  string token to a range of numbers, the apparent complexity of mkslist() is
  4875.  reduced. The save_range() function is described as follows:
  4876.  
  4877.    copy first number to a buffer
  4878.    convert to long int
  4879.    if (only one number received)
  4880.            set max = min
  4881.    else
  4882.            if (second number is null)
  4883.                    set max = BIGGEST
  4884.            else
  4885.                    copy second number into buffer
  4886.                    convert to long int
  4887.                    save in max
  4888.    return (max)
  4889.  
  4890.       This design lets the user specify open ranges such as 40--, which
  4891.  gives a firm minimum number but lets the maximum default to a large number.
  4892.  This is handy for telling a program to operate on all lines starting at 40
  4893.  and continuing until the last line (of unknown number) is reached.
  4894.       To determine whether a number is a member of the selection list, we
  4895.  can call the function selected(), which returns a nonzero value if the
  4896.  specified entry is a member of a range in the list. The selected() function
  4897.  scans the selection list (Slist) one array element at a time until it finds
  4898.  a range that contains its argument or until it finds a -1 flag that
  4899.  terminates the list.
  4900.       The source code for the selection functions is contained in
  4901.  select.c:
  4902.  
  4903.  
  4904.               ╔══════════════════════════════════════════╗
  4905.               ║                                          ║
  4906.               ║ Click to view the listing found on page ║
  4907.               ║ 189 in the printed version of the book.  ║
  4908.               ║                                          ║
  4909.               ╚══════════════════════════════════════════╝
  4910.  
  4911.  
  4912.       In mkslist(), strtok() is called with a pointer that is initially set
  4913.  to the start of the user-supplied list, to extract the first (possibly the
  4914.  only) token. The pointer is then set to NULL for subsequent calls to
  4915.  strtok() to extract additional tokens, if any, from the list. Acceptable
  4916.  separators are comma, space, and tab. This gives the list-maker
  4917.  considerable flexibility in forming the list string. If the list contains
  4918.  embedded spaces or tabs, it must be quoted, so that DOS sees it as a single
  4919.  argument. A range specification must not have any white space around the
  4920.  hyphen (-), so that strtok() can parse it as a single token.
  4921.       TSTSEL is a program that I used to debug the SELECT functions. It
  4922.  accesses the Slist data structure directly, so that we can see what gets
  4923.  stored in there for various list specifications. The operation of this
  4924.  program is straightforward.
  4925.  
  4926.  
  4927.               ╔══════════════════════════════════════════╗
  4928.               ║                                          ║
  4929.               ║ Click to view the listing found on page ║
  4930.               ║ 192 in the printed version of the book.  ║
  4931.               ║                                          ║
  4932.               ╚══════════════════════════════════════════╝
  4933.  
  4934.  
  4935.       The TSTSEL program produced the following output for one of my test
  4936.  runs invoked with the command line
  4937.  
  4938.       tstsel 1,2,3,5--10
  4939.  
  4940.  
  4941.               ╔══════════════════════════════════════════╗
  4942.               ║                                          ║
  4943.               ║ Click to view the listing found on page ║
  4944.               ║ 193 in the printed version of the book.  ║
  4945.               ║                                          ║
  4946.               ╚══════════════════════════════════════════╝
  4947.  
  4948.  
  4949.       The first line shows the argument containing the user's list. The next
  4950.  block of lines displays the contents of the select list, Slist. Note the -1
  4951.  sentinel that marks the list's end. The next block of lines shows the
  4952.  results when selected() is used to query the select list to determine if a
  4953.  value is a member of a range. We have shown the results as YES/NO
  4954.  strings.
  4955.  
  4956.  
  4957.       Setting Tab Stops
  4958.  
  4959.       In processing files that contain text, it is sometimes necessary  or
  4960.  desirable to alter tab-stop positions, or to convert tabs to spaces or the
  4961.  reverse.
  4962.       The formula for determining regular tab-stop positions is 1 + kn,
  4963.  where k is the requested interval and n is an integer in the range of 0
  4964.  through some maximum number determined by the line length. Thus, for the
  4965.  value k = 8, tabs fall at 1, 9, 17, and so on.
  4966.       Variable tab stops are a bit more difficult to set up, but they have
  4967.  their uses. We might appreciate being able to set the tab stops for a
  4968.  FORTRAN program source file to 1, 7, 11, 15, 19, and 23, for example. We
  4969.  can apply the selection-list technique just described to the setting of tab
  4970.  stops at variable column positions in a line.
  4971.       The file tabs.c contains the source code for three functions used to
  4972.  set and test tab stops. A private array of characters holds the tab-stop
  4973.  data. Each character represents a column position in a line and contains 1
  4974.  if the associated column is a tab stop and 0 if it is not.
  4975.       Two of the functions in the TABS module are used to set the tab stops.
  4976.  The fixtabs() function installs tab stops at regular (fixed) intervals. The
  4977.  interval is given as an integer argument to fixtabs(). The vartabs()
  4978.  function works from a list to set tabs at variable intervals. We use the
  4979.  SELECT functions to gather data from a user-supplied list and then convert
  4980.  it to the needed form. A third function, tabstop(), is used to query the
  4981.  Tabstops array.
  4982.  
  4983.  
  4984.               ╔══════════════════════════════════════════╗
  4985.               ║                                          ║
  4986.               ║ Click to view the listing found on page ║
  4987.               ║ 195 in the printed version of the book.  ║
  4988.               ║                                          ║
  4989.               ╚══════════════════════════════════════════╝
  4990.  
  4991.  
  4992.       The TABS module uses C's natural base of 0 as the first element of an
  4993.  array; therefore, all calculations are based on 0 as the leftmost column.
  4994.  We accommodate the difference between the TABS module's view of the world
  4995.  and the user's view of the world in the programs that handle the collection
  4996.  and presentation of tab data.
  4997.       The best way to see how this all works is to demonstrate it. The
  4998.  showtabs.mk file tells MAKE how to put it all together. The SHOWTABS
  4999.  program gets a user specification of the needed tab stops. A -f option flag
  5000.  means SHOWTABS is getting a fixed interval specification, and a -v option
  5001.  flag signals a variable tab list. These options are mutually exclusive, and
  5002.  the parsing of the command-line options enforces the use of only one of
  5003.  them.
  5004.  
  5005.    #makefile for SHOWTABS program
  5006.  
  5007.    LLIB=c:\lib\local
  5008.  
  5009.    tabs.obj:        tabs.c
  5010.            msc $*;
  5011.  
  5012.    select.obj:      select.c
  5013.            msc $*;
  5014.  
  5015.    showtabs.obj:    showtabs.c
  5016.            msc $*;
  5017.  
  5018.    showtabs.exe:    showtabs.obj select.obj tabs.obj $(LLIB)\util.lib
  5019.            link $* select tabs, $*, , $(LLIB)\util;
  5020.  
  5021.  
  5022.               ╔══════════════════════════════════════════╗
  5023.               ║                                          ║
  5024.               ║ Click to view the listing found on page ║
  5025.               ║ 197 in the printed version of the book.  ║
  5026.               ║                                          ║
  5027.               ╚══════════════════════════════════════════╝
  5028.  
  5029.  
  5030.       If SHOWTABS receives a variable list, it calls the SELECT functions to
  5031.  do the necessary conversion from the string form of the program argument to
  5032.  an array of integers that vartabs() understands. In the case of a -f
  5033.  option, SHOWTABS uses atoi() to produce a single number, which is taken to
  5034.  be a fixed interval specifier that is passed to fixtabs().
  5035.       Once the Tabstops array is populated, SHOWTABS calls upon tabstop() to
  5036.  display the status of every column in the first 80 columns of a line. A
  5037.  plus sign (+) in the display marks columns that are nonzero multiples of
  5038.  10. A T marks a tab stop, and a minus sign (-) marks all other positions.
  5039.  Figure 7-4 on the next page shows the results of various tab-setting calls
  5040.  to SHOWTABS.
  5041.  
  5042.  
  5043.  C> showtabs -v 1,9,17,61-68
  5044.  T-------T+------T--+---------+---------+---------+---------+TTTTTTTT-+
  5045.  
  5046.  
  5047.  C>
  5048.  
  5049.       FIGURE 7-4 ▐  Output from SHOWTABS
  5050.  
  5051.  
  5052.       Because the functions in both the SELECT and TABS modules are
  5053.  generally useful, we will add select.obj and tabs.obj to util.lib in
  5054.  \lib\local.
  5055.       In the next chapter, we will develop a group of utilities for DOS that
  5056.  emulate some of the UNIX utilities; this will help ease the transition of
  5057.  new users from one system to the other and will help those who are
  5058.  constantly moving between the two operating systems, as is the case for
  5059.  many software developers.
  5060.  
  5061.  
  5062.  
  5063.  SECTION III  File-Oriented Programs
  5064.  
  5065.  
  5066.  
  5067.  Chapter 8  Basic File Utilities
  5068.  
  5069.  
  5070.  
  5071.       This chapter presents a set of file-oriented utilities that satisfy
  5072.  two primary goals. First, the commands give programmers who divide their
  5073.  time between UNIX/XENIX and DOS a set of programs that make it easier to
  5074.  switch back and forth between the two environments. The programs
  5075.  essentially implement a UNIX/XENIX-style interface under DOS. Second, the
  5076.  programs serve as models for other utility programs that you might like to
  5077.  add to your toolkit. The programs employ many of the functions and
  5078.  techniques we have developed thus far in the book plus a few that are
  5079.  new.
  5080.       The programs in this set include the following utilities:
  5081.  
  5082.  ═══════════════════════════════════════════════════════════════════════════
  5083.    UTILITY                  ACTION
  5084.  ───────────────────────────────────────────────────────────────────────────
  5085.    TOUCH                │   update the modification times of files
  5086.    TEE                  │   split a stream into two separate streams
  5087.    PWD                  │   print the working directory pathname
  5088.    RM                   │   remove files
  5089.    LS                   │   list the files in a directory
  5090.  ───────────────────────────────────────────────────────────────────────────
  5091.  
  5092.       Some of these programs perform functions already provided by DOS, but
  5093.  in a way that is more familiar to UNIX/XENIX users. LS is similar to the
  5094.  DOS DIR command. PWD does the job of the DOS CD command, when CD is typed
  5095.  without an argument. The RM command provides some enhancements over the
  5096.  standard DOS ERASE/DEL command. Other programs in this set are tools used
  5097.  by programmers that have no DOS equivalents.
  5098.  
  5099.  
  5100.  Update File Modification Time (TOUCH)
  5101.  
  5102.       We have been using the MAKE command to assist us in creating and
  5103.  maintaining programs. The MAKE command uses a file's last modification time
  5104.  and instructions in a "makefile" as the basis of its decision-making
  5105.  process. MAKE compares the modification time of an object with that of its
  5106.  sources (specified in a dependency list in the makefile) to determine
  5107.  whether an object is older than one or more of its respective sources. So,
  5108.  when we modify a source file, every object that lists that source file in
  5109.  its dependency list will be remade.
  5110.       At times, we may want to force an object to be remade. We can do this
  5111.  in several ways. We can edit a source file; even if we make no changes to
  5112.  the file, the act of saving the file with an editor updates its
  5113.  modification time. The source will then be newer than the object that is
  5114.  created from it, so the object will be remade.
  5115.       Another way to force an object to be remade is to remove it. MAKE will
  5116.  discover that the object does not exist and will remake it from the recipe
  5117.  in the makefile.
  5118.       A third way is to use a program called TOUCH to update the file
  5119.  modification time of the source file without making any other changes to
  5120.  it. This will also cause MAKE to remake the object. TOUCH is particularly
  5121.  handy when you want to force all the files in an entire project or
  5122.  subproject to be remade. A single TOUCH command does the work of many
  5123.  separate DEL or editor commands.
  5124.       The following pseudocode describes how TOUCH works.
  5125.  
  5126.            for (each named file)
  5127.                    update time
  5128.                    if (update not successful)
  5129.                            increment error count
  5130.                            if (verbose)
  5131.                                    print error message
  5132.                    else
  5133.                            if (verbose)
  5134.                                    print confirmation message
  5135.  
  5136.       The manual page for TOUCH is presented in Figure 8-1.
  5137.  
  5138.  
  5139.               ╔══════════════════════════════════════════╗
  5140.               ║                                          ║
  5141.               ║ Click to view the figure found on page  ║
  5142.               ║ 207 in the printed version of the book.  ║
  5143.               ║                                          ║
  5144.               ╚══════════════════════════════════════════╝
  5145.  
  5146.       FIGURE 8-1 ▐ Manual Page for TOUCH
  5147.  
  5148.  
  5149.       The source code for the TOUCH program is in the file touch.c.
  5150.  
  5151.  
  5152.               ╔══════════════════════════════════════════╗
  5153.               ║                                          ║
  5154.               ║ Click to view the listing found on page ║
  5155.               ║ 208 in the printed version of the book.  ║
  5156.               ║                                          ║
  5157.               ╚══════════════════════════════════════════╝
  5158.  
  5159.  
  5160.       Note the use of two dummy functions, _setenvp() and _nullcheck().
  5161.  These functions reduce the final program code size slightly by eliminating
  5162.  the code for environment processing and for checking attempts to reference
  5163.  data through null pointers. The _nullcheck() function is inside a
  5164.  preprocessor directive block that includes the code during debugging and
  5165.  omits it when the program is finally compiled.
  5166.       If we are really serious about minimizing code size, we will also
  5167.  eliminate calls to printf-family functions because the formatting code is
  5168.  rather large (about 2.5--8 KB, depending on the compiler and memory model
  5169.  used). Using fputs() and fputc() to synthesize the fprintf() function, when
  5170.  it is used to process only strings, will save several kilobytes in the
  5171.  executable program. But be on the lookout for "hidden" formatting.
  5172.  Depending on how the compiler supplier implements perror(), for example,
  5173.  you may find that a call to fprintf() gets dragged in anyway, even though
  5174.  you didn't use one directly in your code.
  5175.  
  5176.  
  5177.  Tapping a Pipeline (TEE)
  5178.  
  5179.       The TEE program is often called the "pipefitters' dream." The manual
  5180.  page for TEE (Figure 8-2 on the next page) tells you why.
  5181.  
  5182.  
  5183.               ╔══════════════════════════════════════════╗
  5184.               ║                                          ║
  5185.               ║ Click to view the figure found on page  ║
  5186.               ║ 212 in the printed version of the book.  ║
  5187.               ║                                          ║
  5188.               ╚══════════════════════════════════════════╝
  5189.  
  5190.       FIGURE 8-2 ▐ Manual page for TEE
  5191.  
  5192.  
  5193.       TEE always writes a copy of its input to the standard output stream.
  5194.  In addition, TEE attempts to open for writing any files given as command-
  5195.  line arguments. If the option -a is given, TEE attempts to open the files
  5196.  in append mode instead of write mode. By naming output files, the user
  5197.  creates a multi-way split and sends copies of the same input stream to two
  5198.  or more output streams. The pseudocode for TEE is
  5199.  
  5200.            set file mode string
  5201.            for (each name file)
  5202.                    open file per mode string
  5203.            for each character received from stdin
  5204.                    copy the character to all open output streams
  5205.            close all streams
  5206.  
  5207.       TEE is especially useful in debugging programs. I use it to view the
  5208.  output of a program on the screen while capturing a copy of the data stream
  5209.  in a file for detailed inspection using DUMP (Chapter 10) or a text
  5210.  editor. It is surprising what kind of garbage finds its way into the output
  5211.  data stream of an ill-behaved program.
  5212.  
  5213.  
  5214.               ╔══════════════════════════════════════════╗
  5215.               ║                                          ║
  5216.               ║ Click to view the listing found on page ║
  5217.               ║ 213 in the printed version of the book.  ║
  5218.               ║                                          ║
  5219.               ╚══════════════════════════════════════════╝
  5220.  
  5221.  
  5222.       The source code for TEE introduces the use of the standard library
  5223.  function fcloseall(), which can be used to shut down all open streams
  5224.  except the standard streams that were opened for you by the C run-time
  5225.  startup system. The function returns -1 in the event that it cannot close a
  5226.  stream. We report this to the operating system via the exit() function. The
  5227.  ERRORLEVEL feature of batch files can be used to test the return value, but
  5228.  DOS ignores it.
  5229.  
  5230.  
  5231.  Print Working Directory Path (PWD)
  5232.  
  5233.       The manual page for PWD (Figure 8-3) says all you need to
  5234.  know about "what" but may leave you wondering "why." What's wrong with
  5235.  typing CD without an argument to find out the name of the current
  5236.  directory?
  5237.       Nothing, really. But those of us who switch back and forth between DOS
  5238.  and UNIX/XENIX have certain reflex reactions that cause problems. Under
  5239.  UNIX/XENIX, a bare CD command reports nothing. Instead, it takes you back
  5240.  to your "home" directory from anywhere in the directory hierarchy. The PWD
  5241.  command is used to display the current directory pathname. I wrote a DOS
  5242.  PWD command to make the two environments a bit more alike for some often-
  5243.  used commands.
  5244.  
  5245.  
  5246.               ╔══════════════════════════════════════════╗
  5247.               ║                                          ║
  5248.               ║ Click to view the figure found on page  ║
  5249.               ║ 215 in the printed version of the book.  ║
  5250.               ║                                          ║
  5251.               ╚══════════════════════════════════════════╝
  5252.  
  5253.       FIGURE 8-3 ▐ Manual page for PWD
  5254.  
  5255.  
  5256.       The source for PWD, pwd.c, uses the standard library function getcwd()
  5257.  to get the current "working" directory pathname. The first argument can be
  5258.  the address of a user-supplied buffer that is long enough to hold a DOS
  5259.  pathname (64 characters). If NULL is used instead, getcwd() creates its own
  5260.  buffer, the length of which is specified by the second argument.
  5261.  
  5262.  
  5263.               ╔══════════════════════════════════════════╗
  5264.               ║                                          ║
  5265.               ║ Click to view the listing found on page ║
  5266.               ║ 216 in the printed version of the book.  ║
  5267.               ║                                          ║
  5268.               ╚══════════════════════════════════════════╝
  5269.  
  5270.  
  5271.  Remove Files (RM)
  5272.  
  5273.       One of the limitations of the DOS ERASE/DEL command is that it accepts
  5274.  only a single file specification; if we want to remove several different
  5275.  types of files, a series of ERASE commands must be issued. Another
  5276.  limitation is the lack of an interactive deletion feature. RM does the job
  5277.  of ERASE while adding these two useful features.
  5278.       The manual page for RM is presented in Figure 8-4. The basic
  5279.  behavior of RM is the same as ERASE except that you may provide any
  5280.  number of exact and ambiguous filename specifications up to the limits
  5281.  imposed by the DOS command line. Each command-line argument (following
  5282.  command options, if any) is expanded if necessary. The -i option, when
  5283.  used, puts RM into an interactive mode that is a great help in avoiding
  5284.  the unwanted loss of files. Each deletion must be confirmed by a Y
  5285.  response and a Return--more work for the user, but safer.
  5286.  
  5287.  
  5288.               ╔══════════════════════════════════════════╗
  5289.               ║                                          ║
  5290.               ║ Click to view the figure found on page  ║
  5291.               ║ 217 in the printed version of the book.  ║
  5292.               ║                                          ║
  5293.               ╚══════════════════════════════════════════╝
  5294.  
  5295.       FIGURE 8-4 ▐ Manual page for RM
  5296.  
  5297.  
  5298.       The pseudocode for the heart of RM is
  5299.  
  5300.            for each file
  5301.                    if (interactive mode selected)
  5302.                            print file name
  5303.                            prompt user for reply (y/n)
  5304.                            read a line of input
  5305.                            if (no)
  5306.                                    break
  5307.                    else
  5308.                            unlink file
  5309.                    if (error occurred)
  5310.                            print error message
  5311.  
  5312.       The source for RM is composed of three functions: main(), do_rm(), and
  5313.  affirm(). The return value of the call to unlink(), a standard library
  5314.  function, is compared to -1, which flags an error condition. The perror()
  5315.  library function is used to print an error message. There is no exit made
  5316.  at this point because other files may still need to be removed.
  5317.       The most likely error is an attempt to remove a non-existent file.
  5318.  Also likely is the typical failure caused by attempts to access a floppy
  5319.  drive that has no disk or has its door open. RM simply reports about non-
  5320.  existent files; it lets the DOS error handler deal with drive problems.
  5321.  
  5322.  
  5323.               ╔══════════════════════════════════════════╗
  5324.               ║                                          ║
  5325.               ║ Click to view the listing found on page ║
  5326.               ║ 218 in the printed version of the book.  ║
  5327.               ║                                          ║
  5328.               ╚══════════════════════════════════════════╝
  5329.  
  5330.  
  5331.       That's it for the easy utilities. The next is more powerful and useful
  5332.  than the utilities we have just written and is appropriately more
  5333.  complicated and more difficult to write as well. As the weight lifters say,
  5334.  "No pain, no gain."
  5335.  
  5336.  
  5337.  List Directories (LS)
  5338.  
  5339.       Let's start with the manual page for LS (Figure 8-5 on the next
  5340.  page); it shows us where we are headed. Note that this LS command has
  5341.  six options. That may sound like a lot, but the UNIX/XENIX ls command has
  5342.  more than three times as many! To keep things simple and usable, I elected
  5343.  to leave many bells and whistles out of LS. Anyone who wants another 15
  5344.  options is free to put them in.
  5345.       The pseudocode for LS only hints at the complications of doing this
  5346.  job in a DOS environment. Ignoring startup tasks (version control, option
  5347.  parsing, setting up a Ctrl-Break handler, and so forth), LS follows this
  5348.  general blueprint:
  5349.  
  5350.            allocate a buffer for file data
  5351.            allocate a buffer for directory data
  5352.            if (no arguments)
  5353.                    get current directory pathname
  5354.            else
  5355.                    for each named item
  5356.                            if (it's a directory)
  5357.                                    add directory name to directory buffer
  5358.                            else if (it's a file)
  5359.                                    add file name and data to file buffer
  5360.            if (any files in file buffer)
  5361.                    sort entries
  5362.                    send file list to output
  5363.            if (any directories in directory buffer)
  5364.                    for (each directory)
  5365.                            expand it to a list of files
  5366.                            sort entries
  5367.                            output list
  5368.  
  5369.       The LS program resides in several files that are devoted exclusively
  5370.  to LS-related chores. In addition, several DOS functions of more
  5371.  farreaching application are used. These are new, and will be added to the
  5372.  local DOS library that we created in Chapter 5.
  5373.       The header file, ls.h, shows the data structures that describe file
  5374.  data and the manifest constants needed to request and interpret file data.
  5375.  Note that the LS program deals with information about files, rather than
  5376.  the information that is in them.
  5377.  
  5378.  
  5379.               ╔══════════════════════════════════════════╗
  5380.               ║                                          ║
  5381.               ║ Click to view the figure found on page  ║
  5382.               ║ 223 in the printed version of the book.  ║
  5383.               ║                                          ║
  5384.               ╚══════════════════════════════════════════╝
  5385.  
  5386.       FIGURE 8-5 ▐ Manual page for LS
  5387.  
  5388.  
  5389.               ╔══════════════════════════════════════════╗
  5390.               ║                                          ║
  5391.               ║ Click to view the listing found on page ║
  5392.               ║ 223 in the printed version of the book.  ║
  5393.               ║                                          ║
  5394.               ╚══════════════════════════════════════════╝
  5395.  
  5396.  
  5397.       The OUTBUF structure template is used to allocate storage for an
  5398.  output buffer that contains the name of a file, its modification data and
  5399.  time, the file size in bytes, and the file mode for each file in the
  5400.  default directory and any named directories.
  5401.       The basic substance of the LS program is in the file ls.c. It contains
  5402.  a slew of include file directives, a set of global configuration variables,
  5403.  the main() function, and a Ctrl-Break handler function. LS can produce
  5404.  mountains of output; therefore, we must provide a convenient way to break
  5405.  out if the user gets bored or finds the sought-after information part way
  5406.  through the listing.
  5407.       The main() function orchestrates everything, calling on other
  5408.  functions to do most of the hard work. Its job is to collect the user's
  5409.  requests and preferences and then to parcel out the work of getting file
  5410.  data, expanding directory names to the contents of the directories, and
  5411.  much more. Here is the ls.c file. Think what it might look like with 15
  5412.  additional options!
  5413.  
  5414.  
  5415.               ╔══════════════════════════════════════════╗
  5416.               ║                                          ║
  5417.               ║ Click to view the listing found on page ║
  5418.               ║ 224 in the printed version of the book.  ║
  5419.               ║                                          ║
  5420.               ╚══════════════════════════════════════════╝
  5421.  
  5422.  
  5423.       Global variables are used to hold control information that is needed
  5424.  by other functions in the LS program. Global variables should not be used
  5425.  to pass data between functions.
  5426.       The main() function calls on malloc() to allocate memory for the file
  5427.  data structure and for an array of pointers to directory names. The
  5428.  technique used here allocates a fixed amount. If that amount proves to be
  5429.  too small to handle the job, additional space is obtained from realloc().
  5430.  This guarantees that the expanded allocation is contiguous with the earlier
  5431.  allocation (even if the whole block has to be moved to a new location in
  5432.  memory), which makes it possible to use pointers and array indexes to
  5433.  access elements in the data areas.
  5434.       Two DOS functions, getdrive() and drvpath(), are called by main(). The
  5435.  first gets the current drive number (0 = A, 1 = B, and so forth), and the
  5436.  second appends to a copy of the current pathname onto a drive
  5437.  specification.
  5438.  
  5439.    /*
  5440.     *      getdrive -- return the number of the default drive
  5441.     */
  5442.  
  5443.    #include <dos.h>
  5444.    #include <local\doslib.h>
  5445.  
  5446.    int getdrive()
  5447.    {
  5448.            return (bdos(CURRENT_DISK));
  5449.    }
  5450.  
  5451.  
  5452.               ╔══════════════════════════════════════════╗
  5453.               ║                                          ║
  5454.               ║ Click to view the listing found on page ║
  5455.               ║ 231 in the printed version of the book.  ║
  5456.               ║                                          ║
  5457.               ╚══════════════════════════════════════════╝
  5458.  
  5459.  
  5460.       These two files should be compiled and the resulting object files
  5461.  should be added to the dos.lib in the \lib\local directory.
  5462.       After the file and directory buffers have been filled, main()
  5463.  sequences the outputting of the data. All file data goes out first,
  5464.  followed by the file and directory names associated with each named
  5465.  directory. The simplest case is a list of files produced by a request
  5466.  like
  5467.  
  5468.       ls
  5469.  
  5470.  which produces a one-column listing of the file and directory names in the
  5471.  current directory. The output is done in a multi-column format if the
  5472.  MULTICOLUMN variable is set to the value of logical 1 by using -C on the
  5473.  command line or by renaming the LS program to either LC or LF under DOS
  5474.  version 3.00 or later.
  5475.       The output of LS is directed to the standard output stream, so it
  5476.  appears on the screen unless it is redirected.
  5477.       A default of 80 columns maximum is used to determine line length. This
  5478.  fits easily on most display screens and printers. You might want to add a
  5479.  feature that allows the line length to be controlled, either from the
  5480.  command line or by a configuration file or an environment variable.
  5481.       Output lists are sorted lexically by default. Options allow the user
  5482.  to select reverse sorting (-r) and sorting by last modification time (-t).
  5483.  The standard library sort routine, qsort(), does the sorting and calls on
  5484.  ls_fcomp() to compare items being sorted. The file ls_fcomp.c contains the
  5485.  source for the comparison function.
  5486.  
  5487.  
  5488.               ╔══════════════════════════════════════════╗
  5489.               ║                                          ║
  5490.               ║ Click to view the listing found on page ║
  5491.               ║ 233 in the printed version of the book.  ║
  5492.               ║                                          ║
  5493.               ╚══════════════════════════════════════════╝
  5494.  
  5495.  
  5496.       After lists are sorted, they are sent to the output device by
  5497.  functions in the ls_list module. Single-column output is done by
  5498.  ls_single(). Multi-column output is handled by ls_multi(). The latter
  5499.  function is the more interesting one. First, here is the source:
  5500.  
  5501.  
  5502.               ╔══════════════════════════════════════════╗
  5503.               ║                                          ║
  5504.               ║ Click to view the listing found on page ║
  5505.               ║ 234 in the printed version of the book.  ║
  5506.               ║                                          ║
  5507.               ╚══════════════════════════════════════════╝
  5508.  
  5509.  
  5510.       The ls_multi() function receives a sorted list of names, scans it once
  5511.  to find the length of the longest name, and then outputs the list in
  5512.  columns. The number of columns depends on the file data. Optimal column
  5513.  width and number of columns are determined by the length of the longest
  5514.  filename or subdirectory name to be output, thus maximizing the number of
  5515.  columns output and minimizing the number of lines used. All names are
  5516.  output in lowercase. Most people find words in all uppercase letters
  5517.  difficult to read; if you don't, feel free to leave out the call to
  5518.  strlwr().
  5519.       The next function, ls_dirx(), is the workhorse to which falls the task
  5520.  of expanding a directory name into a list of its subordinate file and
  5521.  subdirectory names. This is not a trivial task. The file system under DOS
  5522.  is fractured--part residing in a fixed directory at the "root" level of a
  5523.  disk, and the rest in dynamically allocated subdirectories that are simply
  5524.  special files in the file system. In addition, we must be concerned with
  5525.  the use of a drive name (A, for example) as an alias for the current
  5526.  directory on that drive. Note: The drive name is not an alias for the root
  5527.  directory.
  5528.  
  5529.  
  5530.               ╔══════════════════════════════════════════╗
  5531.               ║                                          ║
  5532.               ║ Click to view the listing found on page ║
  5533.               ║ 238 in the printed version of the book.  ║
  5534.               ║                                          ║
  5535.               ╚══════════════════════════════════════════╝
  5536.  
  5537.  
  5538.       To get the data it needs from DOS, ls_dirx() calls on three
  5539.  DOS functions: setdta() to establish a disk transfer area in memory;
  5540.  first_fm() to obtain the name of the first file that matches an ambiguous
  5541.  filename specification; and next_fm() to get any subsequent matches to the
  5542.  same specification. We will compile these three functions and add the
  5543.  object modules to the DOS library.
  5544.  
  5545.    /*
  5546.     *      setdta -- tell DOS where to do disk transfers
  5547.     */
  5548.  
  5549.    #include <dos.h>
  5550.    #include <local\doslib.h>
  5551.  
  5552.    void
  5553.    setdta(bp)
  5554.    char *bp;       /* pointer to byte-aligned disk transfer area */
  5555.    {
  5556.            union REGS inregs, outregs;
  5557.  
  5558.            inregs.h.ah = SET_DTA;
  5559.            inregs.x.dx = (unsigned int)bp;
  5560.            (void)intdos(&inregs, &outregs);
  5561.    }
  5562.  
  5563.  
  5564.               ╔══════════════════════════════════════════╗
  5565.               ║                                          ║
  5566.               ║ Click to view the listing found on page ║
  5567.               ║ 241 in the printed version of the book.  ║
  5568.               ║                                          ║
  5569.               ╚══════════════════════════════════════════╝
  5570.  
  5571.  
  5572.       In addition, ls--dirx() calls last--ch() to examine the last character
  5573.  in the pathname. If the last character is not a \, a \ is appended to the
  5574.  pathname. The last--ch() function is added to the \lib\local\util.lib
  5575.  library.
  5576.  
  5577.    /*
  5578.     *      last_ch -- return a copy of the last character
  5579.     *      before the NUL byte in a string
  5580.     */
  5581.  
  5582.    char
  5583.    last_ch(s)
  5584.    char *s;
  5585.    {
  5586.            register char *cp;
  5587.  
  5588.            /* find end of s */
  5589.            cp = s;
  5590.            while (*cp != '\0')
  5591.                    ++cp;
  5592.  
  5593.            /* return previous character */
  5594.            --cp;
  5595.            return (*cp);
  5596.    }
  5597.  
  5598.       To assist you in producing and maintaining the LS program, here is a
  5599.  makefile, ls.mk:
  5600.  
  5601.  
  5602.               ╔══════════════════════════════════════════╗
  5603.               ║                                          ║
  5604.               ║ Click to view the listing found on page ║
  5605.               ║ 243 in the printed version of the book.  ║
  5606.               ║                                          ║
  5607.               ╚══════════════════════════════════════════╝
  5608.  
  5609.  
  5610.       The temptation to add features to LS is great. It (and its
  5611.  equivalents, such as DIR) is a high-use program, typically accounting for
  5612.  some 30 to 40 percent of command executions on a given system. Perhaps
  5613.  because of its visibility, it is a frequent target for enhancements. Try to
  5614.  avoid turning LS into a program that no one uses because it is too
  5615.  complicated.
  5616.  
  5617.  
  5618.  
  5619.  Chapter 9  File Printing
  5620.  
  5621.  
  5622.  
  5623.       One essential programmer's tool is a program that displays or prints
  5624.  the contents of text files--program source files, debugging listings,
  5625.  program "map" files, and so on. The PR program developed in this chapter to
  5626.  take care of this task is patterned after the UNIX utility of the same
  5627.  name. It is designed to be flexible yet not encumbered with unnecessary
  5628.  features. It provides a basic file-printing capability supplemented by a
  5629.  collection of helpful options.
  5630.       By default, PR produces output suitable for a generic line printer. It
  5631.  uses only the standard format-effector codes (carriage return and linefeed)
  5632.  and normal displayable characters (including space). The options enable you
  5633.  to use special features of the Epson and Epson-compatible printers,
  5634.  sequentially number lines, print a list of pages from a file, and control
  5635.  basic page layout. Many options of PR allow the program to be used as a
  5636.  filter, reading from its standard input and producing formatted pages on
  5637.  its standard output or other output streams.
  5638.  
  5639.  
  5640.  Program Specification
  5641.  
  5642.       Figure 9-1 on the next page is the manual page that
  5643.  describes the features and applications of PR. During the development of
  5644.  the program, the manual page served as a wish list. A prototype of PR,
  5645.  based on an earlier version of the manual page, was tested on some
  5646.  "friendly users." Their feedback led to changes in the manual page and
  5647.  subsequent revisions of the program that eventually resulted in the final
  5648.  program presented here.
  5649.  
  5650.  
  5651.               ╔══════════════════════════════════════════╗
  5652.               ║                                          ║
  5653.               ║ Click to view the figure found on page  ║
  5654.               ║ 246 in the printed version of the book.  ║
  5655.               ║                                          ║
  5656.               ╚══════════════════════════════════════════╝
  5657.  
  5658.       FIGURE 9-1 ▐ Manual page for PR
  5659.  
  5660.  
  5661.       I will describe the program's basic features and its options in detail
  5662.  as the PR functions are presented, but before doing that, we need to define
  5663.  just what is meant by a "page" of output.
  5664.       Figure 9-2 is a proposed page layout. The assumption that governs
  5665.  choices of default values is that standard document paper is used. A
  5666.  sheet of letter-sized paper is 8-1/2 inches wide by 11 inches long. (An
  5667.  additional half-inch on either side of a sheet of pin-fed paper contains
  5668.  the holes for the tractor feed, resulting in a raw page size of 9-1/2 by 11
  5669.  inches.) A good guideline regarding page appearance is that at least 1/2
  5670.  inch of unused space should be left on both sides and at the top and
  5671.  bottom. Using less white space tends to make a printed page look too
  5672.  crowded.
  5673.  
  5674.  
  5675.                left margin                        right margin
  5676.                 ┌──────┬─────────────────────────────┬──────┐
  5677.                 │      │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│      │║
  5678.         (TOP 1)─┼──────┼────▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│      │║
  5679.                 │      │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│      │║
  5680.                 │      │                             │      │║ header
  5681.                 │      │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│      │║
  5682.         (TOP 2)─┼──────┼────▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│      │║
  5683.                 │      │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│      │║
  5684.                 │      │                             │      │
  5685.                 │      │                             │      │
  5686.                 │      │                             │      │
  5687.                 │      │            text             │      │
  5688.                 │      │            body             │      │
  5689.                 │      │                             │      │
  5690.       (MARGIN)──┼──   │                             │   ──┼──(MARGIN)
  5691.                 │      │                             │      │
  5692.                 │      │                             │      │
  5693.                 │      │                             │      │
  5694.                 │      │                             │      │
  5695.                 │      │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│      │║
  5696.        (BOTTOM)─┼──────┼─▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│      │║ footer
  5697.       ┌──┐      │      │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│      │║     ┌──┐ ┌──┐
  5698.       │  │      └──────┴─────────────────────────────┴──────┘      │▒▒│ │  │
  5699.       │  │ print area                              non-print area  │▒▒│ │  │
  5700.       └──┘                                                         └──┘ └──┘
  5701.  
  5702.       FIGURE 9-2 ▐ Proposed page layout
  5703.  
  5704.  
  5705.       The left side of a page may need to be punched for ring binding, so
  5706.  some additional clear space might be needed there. For most purposes,
  5707.  however, I find that the default of 1/2 inch is enough. This indentation or
  5708.  offset is obtained by spacing in five columns for a 10-pitch font.
  5709.  Condensed type, such as that produced by an Epson printer in condensed
  5710.  mode, requires a larger number of columns (about eight) to obtain the same
  5711.  physical offset.
  5712.       Given the specification presented in the manual page and the values
  5713.  obtained from the proposed page layout, we can now design a program to
  5714.  produce nicely formatted listings of text files. We can start by noting
  5715.  that PR follows a pattern of behavior that is similar to CAT: It takes some
  5716.  input, applies some transformation, and produces some output. However, we
  5717.  will have to treat lines a bit differently to accommodate optional line-
  5718.  numbering, page offset, and other aspects of format.
  5719.       If our design is done well, we should have enough flexibility to
  5720.  handle 90 percent of all the printing needs of a programmer. The behavior
  5721.  of the PR program will be controlled by reasonable built-in defaults that
  5722.  may be modified by configuration files located in the current directory or
  5723.  in a directory selected by the user and by command-line options.
  5724.       After analyzing the requirements for PR and trying several simple
  5725.  prototypes to investigate ways of implementing the features, I settled on
  5726.  an organization for the program. Each program module is packaged in a
  5727.  separate file, usually with one function per file. The program has the
  5728.  following functions and calling hierarchy:
  5729.  
  5730.            main()
  5731.                    getpname()
  5732.                    pr_gcnf()
  5733.                            fconfig()
  5734.                    getopt()
  5735.                    pr_help()
  5736.                    pr_file()
  5737.                            pr_cpy()
  5738.                                    mkslist()
  5739.                                    pr_getln()
  5740.                                    fit()
  5741.                                    lines()
  5742.                                    spaces()
  5743.                                    setfont()
  5744.                                    selected()
  5745.                                    pr_line()
  5746.                    exit()
  5747.  
  5748.       As you can see, many functions that we developed in earlier chapters
  5749.  are called by functions used in PR. These external names are resolved by
  5750.  the linker, using util.lib as one of the searched libraries.
  5751.       If the user gives an erroneous option, main() calls pr_help() to
  5752.  display an abbreviated manual page on the stderr channel. The C source for
  5753.  pr_help() is:
  5754.  
  5755.  
  5756.               ╔══════════════════════════════════════════╗
  5757.               ║                                          ║
  5758.               ║ Click to view the listing found on page ║
  5759.               ║ 250 in the printed version of the book.  ║
  5760.               ║                                          ║
  5761.               ╚══════════════════════════════════════════╝
  5762.  
  5763.  
  5764.       The function uses a static array of string pointers to message lines.
  5765.  The calculation in the line
  5766.  
  5767.       n = sizeof (m_str) / sizeof (char *)
  5768.  
  5769.  sets n to the number of message lines in a way that lets us add message
  5770.  lines without having to count them and declare how many are used. This
  5771.  makes it easier to modify the function as the program evolves, as most
  5772.  programs do.
  5773.       To achieve the desired flexibility, we will use a global data
  5774.  structure to hold the values of the important formatting and program-
  5775.  operation control variables used by the program's various modules. Only
  5776.  those modules that need to know about the data will be given access to the
  5777.  data structure. Here is the header file, print.h, that contains the data
  5778.  structure definition and some manifest constants used to initialize the
  5779.  program:
  5780.  
  5781.  
  5782.               ╔══════════════════════════════════════════╗
  5783.               ║                                          ║
  5784.               ║ Click to view the listing found on page ║
  5785.               ║ 251 in the printed version of the book.  ║
  5786.               ║                                          ║
  5787.               ╚══════════════════════════════════════════╝
  5788.  
  5789.  
  5790.       A typedef is used to create a synonym, PRINT, for the struct pr_st
  5791.  data structure. The structure contains about a dozen integer-sized variable
  5792.  definitions and two string variable definitions. The header file defines
  5793.  the printer data structure, but it does not declare any storage for it.
  5794.  That is done in the configuration function, pr_gcnf(), which we'll examine
  5795.  shortly. First, let's take a look at PR from the top.
  5796.       The main() function is contained in the file pr.c. It declares and
  5797.  initializes global variables, establishes the default output channel,
  5798.  processes command-line arguments, and calls pr_file() with a list of files
  5799.  named as input by the user. If no filenames are specified, PR uses the
  5800.  standard input channel so that data can be routed through PR from a
  5801.  redirected input or from a pipeline. Thus, PR fits the description of a
  5802.  filter in the same way as the DOS programs MORE and SORT, except that PR
  5803.  can be configured to send its output to someplace other than the standard
  5804.  output. In such cases, PR is referred to as a sink instead of a filter,
  5805.  because it is a termination point for data flow.
  5806.  
  5807.  
  5808.               ╔══════════════════════════════════════════╗
  5809.               ║                                          ║
  5810.               ║ Click to view the listing found on page ║
  5811.               ║ 252 in the printed version of the book.  ║
  5812.               ║                                          ║
  5813.               ╚══════════════════════════════════════════╝
  5814.  
  5815.  
  5816.       The main() function calls getpname() to get the program name and then
  5817.  calls pr_gcnf() to get the configuration data needed to initialize the
  5818.  program. Next, it uses getopt() to process user-supplied options, if any,
  5819.  and finally it passes a list of filenames (possibly none) to pr_file(). We
  5820.  have seen the use of getpname() and getopt() before. There should be no
  5821.  surprises here.
  5822.  
  5823.  
  5824.  Option Handling
  5825.  
  5826.       The pr_gcnf() function is specific to PR, but the design can be
  5827.  applied to other programs. The pseudocode for this function is
  5828.  
  5829.            if (a configuration file exists)
  5830.                    open the file
  5831.                    read numeric data
  5832.                    read string data
  5833.                    if (data good)
  5834.                            set flag
  5835.                    close file
  5836.            if (flag is set)
  5837.                    initialize to values just read
  5838.            else
  5839.                    use defaults from the header file
  5840.  
  5841.       Here is the source code for pr_gcnf():
  5842.  
  5843.  
  5844.               ╔══════════════════════════════════════════╗
  5845.               ║                                          ║
  5846.               ║ Click to view the listing found on page ║
  5847.               ║ 255 in the printed version of the book.  ║
  5848.               ║                                          ║
  5849.               ╚══════════════════════════════════════════╝
  5850.  
  5851.  
  5852.       The PRINT type is used to declare a global data structure for the
  5853.  printer, pcnf, which stands for printer configuration. Any other source
  5854.  file that needs access to pcnf must declare it as external data.
  5855.       If no configuration file is found, pr_gcnf() falls through to the
  5856.  default assignments. The fconfig() function we created in Chapter 7 is
  5857.  used to query DOS for an external configuration file. A NULL result
  5858.  indicates that none was found. If a configuration file is found, it is read
  5859.  in a line at a time.
  5860.       Numeric values are obtained by converting the character
  5861.  representations in the file to integers by using the standard library
  5862.  function atoi(). Because atoi() accepts only characters that can be
  5863.  considered part of a number, we can add comments to the lines. Thus, when
  5864.  the line
  5865.  
  5866.       66 number of lines per page
  5867.  
  5868.  is retrieved from the configuration file and is given as the string
  5869.  argument to atoi(), the function merely assigns the integer value 66 to a
  5870.  temporary array.
  5871.       String arguments are handled similarly. A line is read in and the
  5872.  library function strtok() extracts the first token, which is the string
  5873.  value we need to save. The remaining characters on the line, if any, are
  5874.  ignored.
  5875.       An example of a string variable is p--dest, which determines where
  5876.  PR's output will go. This variable is a member of pcnf and is accessed
  5877.  using the dot operator as in pcnf.p_dest. Each line of output is sent by
  5878.  default to the device specified in p_dest. If p_dest is set to CON, or if
  5879.  it is null (""), output is directed to the standard output channel and will
  5880.  be displayed on the user's console screen unless program output is
  5881.  redirected. (MS-DOS and UNIX permit the standard output of a program to be
  5882.  redirected using the greater-than (>) symbol--see Chapter 3 or your DOS
  5883.  manual for details. Thus, output that would otherwise be displayed on the
  5884.  system console or a terminal screen may be sent to a printer port or
  5885.  captured in another file.)
  5886.       In this presentation of the PR program, I have set p_dest to PRN, the
  5887.  default printer device, because that is where I usually want the output to
  5888.  go. We can still send the output to the screen by selecting the -p
  5889.  (preview) option, which uses CON as the destination for a single invocation
  5890.  of PR.
  5891.       To display the contents of a file named myfile.txt on the system
  5892.  console, you would type
  5893.  
  5894.       pr -p myfile.txt
  5895.  
  5896.       To send the output to a printer that is attached to the default
  5897.  printer port, you would instead type
  5898.  
  5899.       pr myfile.txt
  5900.  
  5901.       If PR is compiled with ssetargv.obj included in the linker object list
  5902.  (as shown in the makefile, pr.mk), PR permits the use of wildcards in
  5903.  filenames, letting you use abbreviated command lines to specify print jobs.
  5904.  For example, to print hard copies of all the header files in the include
  5905.  subdirectory and in the include\sys subdirectory, type
  5906.  
  5907.       pr \include\*.h \include\sys\*.h
  5908.  
  5909.       If you have a hardware or software print spooler on line (such as the
  5910.  one provided with Microsoft Windows), you can turn your attention to
  5911.  something productive while your printer dumps a mound of paper on the
  5912.  floor. Otherwise, you'll have to mark time for a while.
  5913.  
  5914.  
  5915.  File Handling
  5916.  
  5917.       The processing loop in pr_file() receives a list of files and tries to
  5918.  get each file formatted and printed. The function must handle errors
  5919.  gracefully. If, for example, a user specifies a file that does not exist or
  5920.  one that cannot be opened, the program sends an error message to the
  5921.  standard error channel and then continues looking for more files to print.
  5922.  The standard error channel is used because messages sent to it will appear
  5923.  on the console screen even when normal program output is directed
  5924.  elsewhere. The standard input channel is used as a data source if no files
  5925.  are named on the command line.
  5926.       The pr_file function is also responsible for connecting the output of
  5927.  PR to the required stream. If the user has configured PR to send output to
  5928.  one of the standard output channels (sdtout, stdprn, or stdaux), there is
  5929.  no need to open the stream, because DOS has already done so when PR started
  5930.  running. Other destinations, such as a named file, must be explicitly
  5931.  opened for writing.
  5932.       The source code for pr_file() is:
  5933.  
  5934.  
  5935.               ╔══════════════════════════════════════════╗
  5936.               ║                                          ║
  5937.               ║ Click to view the listing found on page ║
  5938.               ║ 259 in the printed version of the book.  ║
  5939.               ║                                          ║
  5940.               ╚══════════════════════════════════════════╝
  5941.  
  5942.  
  5943.       Notice the message "Cannot stat (filename)" after the call to pr_cpy()
  5944.  (described next). This tells the user that file statistics could not be
  5945.  obtained for the named file even though it was found and opened. This flags
  5946.  a possible error with the disk directory and should inspire the user to
  5947.  find out why the error occurred. It could be that a bad sector is
  5948.  developing or that a disk formatting or updating problem has occurred.
  5949.  
  5950.  
  5951.       What's My Line?
  5952.  
  5953.       The work of orchestrating the formatting and copying of text pages
  5954.  falls to pr_cpy(). A line in a text file may be thought of as a single
  5955.  logical line that may be represented as one or more physical lines of
  5956.  output. Long lines of output--those that exceed the width of the printable
  5957.  or displayable area--may be handled in at least three ways. We can:
  5958.  
  5959.         let the line run long and leave the treatment up to the printer
  5960.          and its driver software. This approach can result in some
  5961.          bizarre looking output.
  5962.  
  5963.         "fold" a long line so that what cannot be displayed or printed on
  5964.          the current line is moved to the next output line.
  5965.  
  5966.         truncate the input so that anything on a line that would have to
  5967.          be displayed past the last column is effectively lost.
  5968.  
  5969.       The PR program has been designed to fold lines so that nothing is lost
  5970.  on output. If line-numbering is turned on, a logical line number is
  5971.  displayed at the beginning of each line. If a line has to be folded, as
  5972.  shown in Figure 9-3, the additional physical lines needed to
  5973.  print or display it are indented as usual, but they do not have a number
  5974.  field associated with them. Logical lines are numbered starting with one.
  5975.  The number of a line indicates its relative offset from the beginning of
  5976.  the file.
  5977.  
  5978.  
  5979.           .
  5980.           .
  5981.           .
  5982.           46          /* process command-line arguments */
  5983.           47          while ((ch = getopt(argc, argv, "efgh:l:no:ps:w:")) !=
  5984.       EOF) {
  5985.           48                  switch (ch) {
  5986.           49                  case 'e':
  5987.           .
  5988.           .
  5989.           .
  5990.  
  5991.       FIGURE 9-3 ▐ A folded line, as displayed by PR
  5992.  
  5993.  
  5994.       Before going any further, here are the pseudocode and C source code
  5995.  for pr_cpy():
  5996.  
  5997.            set up source and destination streams
  5998.            if (source is stdin)
  5999.                    get current time
  6000.            else
  6001.                    get file modification time
  6002.            for each line in source file or stream
  6003.                    if (line won't fit on page OR formfeed)
  6004.                            eject page
  6005.                    if (at top of a page)
  6006.                            print header
  6007.                    print the logical line
  6008.            if (not at top of page)
  6009.                    eject page
  6010.  
  6011.  
  6012.               ╔══════════════════════════════════════════╗
  6013.               ║                                          ║
  6014.               ║ Click to view the listing found on page ║
  6015.               ║ 262 in the printed version of the book.  ║
  6016.               ║                                          ║
  6017.               ╚══════════════════════════════════════════╝
  6018.  
  6019.  
  6020.       The pr_cpy() function handles a few other tasks in addition to fold-
  6021.  ing long lines. It detects that an input stream has ended before the output
  6022.  page is full and emits enough extra blank lines to get back to the top of a
  6023.  fresh page. That's so the next file will start printing in the right place.
  6024.  It makes sure that there is enough room remaining on a page to accept the
  6025.  next logical line. The static function fit() within the pr_cpy.c file does
  6026.  the needed calculations.
  6027.       The function also needs to handle a special situation that results
  6028.  from a fairly widespread practice. Programmers often create source files
  6029.  with two or more functions in them. Sometimes a literal formfeed control
  6030.  character (Ctrl-L) is placed between functions. This causes most printers
  6031.  to eject a page and to begin printing anew at the top of the next page.
  6032.  Care must be taken to keep the file-relative line and page numbers
  6033.  accurate.
  6034.       The functions mkslist() and selected() obtain a selection list and
  6035.  determine whether an item is in the list; they were explained fully in
  6036.  Chapter 7. These functions are called by pr_cpy() to process a list of
  6037.  selected pages, the Pagelist variable, to be formatted and printed by PR.
  6038.  The special page number BIGGEST is used to supply the high end of an open
  6039.  range specification (like 20--), which causes PR to print all pages between
  6040.  20 and the end of the file.
  6041.       The pr_getln() function obtains a line of text from the input stream
  6042.  and expands it while reading it into the line array. The expansion alluded
  6043.  to here is that of converting each tab character in the input into the
  6044.  right number of output spaces so that column alignments are retained. The
  6045.  expansion is necessary because we will be doing indenting (page offset) and
  6046.  other formatting operations upon output, and because some older printers do
  6047.  not handle tabs correctly. All positions in the line array that are used to
  6048.  hold the text must be filled with characters that have a single unit of
  6049.  displacement in the output. This facilitates character counting so that
  6050.  fit() can determine how much space is required for the expanded line.
  6051.       This is the source code for pr_getln():
  6052.  
  6053.  
  6054.               ╔══════════════════════════════════════════╗
  6055.               ║                                          ║
  6056.               ║ Click to view the listing found on page ║
  6057.               ║ 266 in the printed version of the book.  ║
  6058.               ║                                          ║
  6059.               ╚══════════════════════════════════════════╝
  6060.  
  6061.  
  6062.       The tab expansion is done by the functions in tabs.c that were
  6063.  described in Chapter 7.
  6064.       Top-of-page processing is done directly by pr_cpy(), which prints the
  6065.  page header composed of some blank lines, a header line that contains the
  6066.  name of the current file (or a substitute text string), the output page
  6067.  number, and the date and time when the file was created or last modified.
  6068.  The blank lines are produced by a call to lines(), a function that emits a
  6069.  series of newlines to the specified stream.
  6070.  
  6071.    /*
  6072.     *      lines -- send newlines to the output stream
  6073.     */
  6074.  
  6075.    #include <stdio.h>
  6076.    #include <stdlib.h>
  6077.  
  6078.    int
  6079.    lines(n, fp)
  6080.    int n;
  6081.    FILE *fp;
  6082.    {
  6083.            register int i;
  6084.  
  6085.            for (i = 0; i < n; ++i)
  6086.                    if (putc('\n', fp) == EOF && ferror(fp))
  6087.                            break;
  6088.  
  6089.            /* return number of newlines emitted */
  6090.            return (i);
  6091.    }
  6092.  
  6093.       The filename as typed by the user is converted to uppercase. This is
  6094.  done because DOS automatically converts filenames returned in response to
  6095.  an ambiguous specification (using wildcards) in uppercase letters. We could
  6096.  just as easily convert everything to lowercase.
  6097.       The formatting of the header line differs from the text body when the
  6098.  Epson-compatible mode is selected. The setprnt(), setfont(), and clrprnt()
  6099.  functions described in Chapter 7 are employed to control the printer so
  6100.  that headers may be printed in a different typeface than the main text.
  6101.  (Although not a necessary feature, this demonstrates a practical use of
  6102.  different typefaces on a single page.) The text body can be printed in a
  6103.  compressed form, to permit long lines (up to about 137 characters) to be
  6104.  printed on standard letter-sized paper.
  6105.       The function pr_line() is called to output a logical line of text
  6106.  composed of two or three parts: a left margin produced by a call to
  6107.  spaces(), an optional file-relative line-number field, and finally the text
  6108.  of the expanded line. If additional physical lines are needed, spaces() is
  6109.  called at the beginning of each to obtain a uniform page offset.
  6110.       Here is the source code for pr_line():
  6111.  
  6112.  
  6113.               ╔══════════════════════════════════════════╗
  6114.               ║                                          ║
  6115.               ║ Click to view the listing found on page ║
  6116.               ║ 268 in the printed version of the book.  ║
  6117.               ║                                          ║
  6118.               ╚══════════════════════════════════════════╝
  6119.  
  6120.  
  6121.       The pr_line() function returns the number of physical lines that were
  6122.  used to print the single logical input line.
  6123.       The spaces() function (on the next page) is almost identical to
  6124.  lines(), except for the character that it emits (space instead of
  6125.  newline).
  6126.  
  6127.    /*
  6128.     *      spaces -- send spaces (blanks) to the output stream
  6129.     */
  6130.  
  6131.    #include <stdio.h>
  6132.    #include <stdlib.h>
  6133.  
  6134.    int
  6135.    spaces(n, fp)
  6136.    int n;
  6137.    FILE *fp;
  6138.    {
  6139.            register int i;
  6140.  
  6141.            for (i = 0; i < n; ++i)
  6142.                    if (putc(' ', fp) == EOF && ferror(fp))
  6143.                            break;
  6144.  
  6145.            /* return number of spaces emitted */
  6146.            return (i);
  6147.    }
  6148.  
  6149.       Since the tasks performed by lines() and spaces() are needed
  6150.  frequently in programs that send text to the screen or some other output
  6151.  device, we will add the object files lines.obj and spaces.obj to our
  6152.  utility library, util.lib, by compiling them and using the command
  6153.  
  6154.       lib util +lines spaces;
  6155.  
  6156.  Remember to copy the updated library into the \lib\local subdirectory so
  6157.  that LINK can find it.
  6158.  
  6159.  
  6160.  Program Maintenance
  6161.  
  6162.       A special-purpose library called prlib.lib contains all but a few of
  6163.  the object modules that comprise the PR program. The pr.obj file, which
  6164.  contains the main() function, is kept separate from the rest because of the
  6165.  way the DOS linker, LINK, works. The executable filename defaults to the
  6166.  name of the first object module specified in the link list, and at least
  6167.  one module must be named.
  6168.       The makefile script for PR is in pr.mk. It automatically keeps the
  6169.  needed objects and the prlib.lib file up to date as changes are made to the
  6170.  program source files. Here is the text of pr.mk:
  6171.  
  6172.  
  6173.               ╔══════════════════════════════════════════╗
  6174.               ║                                          ║
  6175.               ║ Click to view the listing found on page ║
  6176.               ║ 271 in the printed version of the book.  ║
  6177.               ║                                          ║
  6178.               ╚══════════════════════════════════════════╝
  6179.  
  6180.  
  6181.  Possible Enhancements
  6182.  
  6183.       As well endowed as PR is already, there is always some new wrinkle or
  6184.  feature that some user would like to see. In trying to avoid making PR into
  6185.  a program that serves everyone in every way but no one particularly well in
  6186.  any way, I have chosen to stick with the feature set provided.
  6187.       However, some users of PR have requested the following features, which
  6188.  you may want to consider adding. First, it might be helpful to have a
  6189.  multi-column output feature. This would take lines from a file and present
  6190.  them in columns across the page. Each line would be truncated if it
  6191.  exceeded the column width minus one character position for a separator. It
  6192.  would be necessary to format an entire page in memory before sending
  6193.  anything to the output.
  6194.       Another feature of interest to some users is a more elaborate way of
  6195.  controlling the appearance of the header and footer. This, to me, smacks of
  6196.  word processing, and I have elected to use Microsoft Word if I need such
  6197.  control over the output.
  6198.       The addition of single-sheet control might be useful to some users. I
  6199.  always use pin-fed, fan-fold paper, so I have not felt a compelling need
  6200.  for this feature. It can be added easily by introducing a new numeric
  6201.  (actually a Boolean) variable in the PRINT data structure and testing it
  6202.  before outputting a new page.
  6203.       One additional feature may make it into my next version of PR. Some
  6204.  users would like to be able to do side-by-side printing of multiple files.
  6205.  This is a kind of visual file comparison. It can also be used to generate,
  6206.  in a somewhat restricted way, scripts for plays and motion pictures. This
  6207.  could be done either a line at a time or by doing full-page makeup before
  6208.  generating output.
  6209.       Next, we will devise a program that lets us peer into nontext files
  6210.  that cause PR and DOS text-file programs (TYPE, for example) to produce
  6211.  strange and not particularly useful output.
  6212.  
  6213.  
  6214.  
  6215.  Chapter 10  Displaying Non-ASCII Text
  6216.  
  6217.  
  6218.  
  6219.       Our kit of programmer's utilities now contains some pretty useful
  6220.  tools: the CAT program for concatenating files and PR to print them out on
  6221.  paper; LS to list a directory in a variety of display formats; TOUCH to
  6222.  assist the MAKE command in building programs; and RM to help us clean up
  6223.  our disk directories easily. In addition, we have a raft of low-level and
  6224.  mid-level routines to help us create other tools.
  6225.       Thus far, we have been concerned only with text files and tools that
  6226.  permit us to view and manipulate them. However, one of the essential tools
  6227.  that a programmer needs to have at the ready is a utility that permits the
  6228.  inspection of nontext files. Nontext files are produced by compilers,
  6229.  tokenizing interpreters (such as Microsoft interpretive BASIC), assemblers,
  6230.  program configurators (see Chapter 7), and many commercial word
  6231.  processors, database-management systems, and spreadsheets.
  6232.       If you were asked to convert a file produced by a word processor that
  6233.  you do not have to a form that is acceptable to one that you do, how would
  6234.  you go about it? Ignoring the possibility of buying the needed word
  6235.  processor, we will have to do some detective work, which is what makes the
  6236.  computer business such a lot of fun for some and such a pain in the neck
  6237.  for others.
  6238.       The form and content of the data file would have to be determined so
  6239.  that it could be converted to a suitable form for the target word
  6240.  processor. A straight ASCII text file would suffice as data to be merged
  6241.  into the input stream of most word processors, so we would first attempt to
  6242.  remove any non-ASCII characters from the data file and save the results of
  6243.  the conversion in an intermediate file for later processing.
  6244.       But how would we examine the data file? Our current tools are suitable
  6245.  only for text files, so we need something else. In this chapter, we will
  6246.  develop a program called DUMP that takes any file and produces an output
  6247.  listing that looks like the dump output of the DOS DEBUG program.
  6248.       Each line of the output listing includes a hexadecimal offset in the
  6249.  leftmost column, a hexadecimal display of 16 (10 hex) bytes from the data
  6250.  file in the center, and the equivalent data in extended ASCII in the
  6251.  rightmost column. A character code that would cause problems on the display
  6252.  and on a printer is converted to a dot (.) before being displayed. These
  6253.  filtered codes include newline, carriage return, and most other control
  6254.  codes.
  6255.       The reason for developing a stand-alone program is that DUMP will
  6256.  allow us to capture the converted output in a file or on paper for easy
  6257.  examination. Our design will also permit DUMP to be used as a filter in
  6258.  pipeline commands.
  6259.       Before designing and programming DUMP, let's review some of the most
  6260.  common data-presentation formats and prepare some functions to do needed
  6261.  conversions.
  6262.  
  6263.  
  6264.  Some Useful Conversion Functions
  6265.  
  6266.       The primary data-presentation formats for numbers are the binary,
  6267.  octal, decimal, and hexadecimal notations. While we humans take more or
  6268.  less naturally to decimal (something to do with fingers and toes), our
  6269.  machines tend to favor binary, in which things are on or off (1 or 0). The
  6270.  octal (base 8) and hexadecimal (base 16) formats are simply notations that
  6271.  make binary data more palatable to us. Octal number representations were
  6272.  widely used for many years and still are in some quarters, but octal
  6273.  notation has lost favor with the majority of programmers, who use
  6274.  hexadecimal notation instead. All characters in the computer are
  6275.  represented internally as binary numbers, however. That's all the computer
  6276.  "understands." Our task is to receive data, convert it to the desired form,
  6277.  and send it to the standard output. The output will be displayed on the
  6278.  computer or terminal screen, or sent to some other device if stdout is
  6279.  redirected.
  6280.       Our programs will need to convert the computer's internal
  6281.  representation of numbers into decimal, hexadecimal, or character forms,
  6282.  depending upon what we are trying to see. We will use two functions to do
  6283.  this. They are used in DUMP and will be placed in the utility library
  6284.  (util.lib) for use by other programs. The first function, byte2hex(),
  6285.  converts a byte-sized (8-bit) quantity to hexadecimal. The second,
  6286.  word2hex(), performs the same job on a word-sized (16-bit) quantity. These
  6287.  sizes are appropriate for the IBM PC and other 16-bit machines, but they
  6288.  are not universal, so portability to other machines is not guaranteed.
  6289.       Both byte2hex() and word2hex() are packaged in a single source file
  6290.  named hex.c, which has the following contents:
  6291.  
  6292.  
  6293.               ╔══════════════════════════════════════════╗
  6294.               ║                                          ║
  6295.               ║ Click to view the listing found on page ║
  6296.               ║ 275 in the printed version of the book.  ║
  6297.               ║                                          ║
  6298.               ╚══════════════════════════════════════════╝
  6299.  
  6300.  
  6301.       The hextab array is a conversion lookup table. It uses a number in the
  6302.  range of 0 through 15 as an index into the table, where the corresponding
  6303.  character constant (number or letter) is found. Hence, the numeral 3 is
  6304.  represented by a character constant of 3 (ASCII code 51--be sure you know
  6305.  the difference), and the number 10 is represented by an A. These half-byte
  6306.  quantities are comically referred to as "nibbles" because they're smaller
  6307.  than bytes. The symbolic constant NIBBLE is defined as 0x000F. It is used
  6308.  to mask off all but the four bits of interest in each conversion step.
  6309.  Right-shift operations are used to pull the needed bits into the correct
  6310.  position for conversion to a hexadecimal digit.
  6311.       A byte can be conveniently expressed as two hex characters (two
  6312.  nibbles) that represent values in the range of 0 (00 hex) through 255 (FF
  6313.  hex). The same range in binary is 00000000 through 11111111, which
  6314.  obviously lacks notational convenience. The hex notation is more compact,
  6315.  requiring only two characters to represent any value in the range.
  6316.       Now that we have a simple means of displaying data in hexadecimal, we
  6317.  can move on to the development of the DUMP program.
  6318.  
  6319.  
  6320.  A DEBUG-style File-dump Program
  6321.  
  6322.       The design of DUMP is strongly influenced by the expectation that its
  6323.  output will be easy to feed to other programs and to a generic printer. We
  6324.  want to build in enough flexibility to permit DUMP to be used as a filter
  6325.  or as a stand-alone program. In stand-alone operation, DUMP takes a list of
  6326.  files as arguments and produces the hex/ASCII output for each in sequence
  6327.  on its standard output. As a filter, it receives a stream of arbitrary
  6328.  characters and transforms it to the hex/ASCII output stream.
  6329.       The proposed format of DUMP's output looks a lot like that of DEBUG.
  6330.  One difference is that the offset of each block of 16 bytes will be shown
  6331.  relative to the start of the displayed file. We won't know where the file
  6332.  is located in memory, so the offset value will stand alone. Another
  6333.  difference is more subtle. DEBUG converts all non-ASCII characters in the
  6334.  text display area to dots. DUMP will display all standard ASCII characters
  6335.  plus the IBM extended ASCII character set when output is directed to the
  6336.  screen. A command-line option will allow us to strip all nonprintable
  6337.  characters from the output stream so that we can use any type of printer as
  6338.  an output device.
  6339.       Figure 10-1 on the next page is the manual page for DUMP.
  6340.  Note the overall simplicity of the program. Therein lies its power, because
  6341.  it can be used in a wide range of situations without change.
  6342.  
  6343.  
  6344.               ╔══════════════════════════════════════════╗
  6345.               ║                                          ║
  6346.               ║ Click to view the figure found on page  ║
  6347.               ║ 278 in the printed version of the book.  ║
  6348.               ║                                          ║
  6349.               ╚══════════════════════════════════════════╝
  6350.  
  6351.       FIGURE 10-1 ▐ Manual page for DUMP
  6352.  
  6353.  
  6354.       The code for DUMP is straightforward. DUMP follows the pattern
  6355.  established in Chapter 8, where file-oriented utility programs used
  6356.  similar command-line options to control program operation and write to the
  6357.  standard output channel. Here is the pseudocode description of the main()
  6358.  function of DUMP, excluding the generic items, such as getting the program
  6359.  name from DOS and error checking at each step:
  6360.  
  6361.            if (no files named)
  6362.                    hexdump(stdin)
  6363.            else
  6364.                    for each file
  6365.                            open(file)
  6366.                            hexdump(file)
  6367.                            close(file)
  6368.  
  6369.       The hexdump() function does the work of transforming the input into
  6370.  the desired presentation format:
  6371.  
  6372.            for each block of input [BUFSIZ]
  6373.                    for each block of bytes [NBYTES]
  6374.                            copy byte offset into output buffer
  6375.                                    [word2hex()]
  6376.                            copy hex values into output buffer
  6377.                                    [byte2hex()]
  6378.                            copy ASCII values into output buffer
  6379.                            copy output buffer to stdout
  6380.  
  6381.       The main() function of DUMP uses getopt() to scan the command line for
  6382.  optional arguments and filename arguments. If the -s option is found, the
  6383.  Boolean variable sflag is set to TRUE to indicate that non-ASCII characters
  6384.  should be stripped. If the -v option is found, the Boolean variable vflag
  6385.  (verbose) is set to TRUE and a header consisting of a blank line followed
  6386.  by the name of the file being dumped is output before the formatted data
  6387.  for each file is listed. You may want to add other data to the verbose
  6388.  output, such as file date and time, character count, and so on. If no
  6389.  filenames are specified, DUMP reads from its standard input and the verbose
  6390.  option is ignored because no name is associated with the data stream.
  6391.  
  6392.  
  6393.               ╔══════════════════════════════════════════╗
  6394.               ║                                          ║
  6395.               ║ Click to view the listing found on page ║
  6396.               ║ 279 in the printed version of the book.  ║
  6397.               ║                                          ║
  6398.               ╚══════════════════════════════════════════╝
  6399.  
  6400.  
  6401.       One or more filenames can be specified either directly or by wildcard
  6402.  specifications. Like CAT and PR, DUMP will operate sequentially on a list
  6403.  of files in the order in which they are presented. Ambiguous filenames
  6404.  under Microsoft C are expanded in lexical order, not directory order.
  6405.       Files are opened in binary mode, not translated mode, because we will
  6406.  most likely be reading nontext files. Even if we read a text file, we want
  6407.  to be able to see all characters, so the translation of the CR/LF and Ctrl-
  6408.  Z codes is inappropriate.
  6409.       The hexdump() function uses an internal buffer, outbuf, to form a line
  6410.  in memory before outputting it with the fputs() library function. Although
  6411.  the buffering is not essential, it collects the writing operation into a
  6412.  single function call instead of having five separate calls to several
  6413.  different library routines. This buffering approach makes it easier to
  6414.  change the output code if we decide to use a different means of writing the
  6415.  output to the screen.
  6416.  
  6417.  
  6418.               ╔══════════════════════════════════════════╗
  6419.               ║                                          ║
  6420.               ║ Click to view the listing found on page ║
  6421.               ║ 282 in the printed version of the book.  ║
  6422.               ║                                          ║
  6423.               ╚══════════════════════════════════════════╝
  6424.  
  6425.  
  6426.       Figure 10-2 on the next page is a sample of the output
  6427.  produced by DUMP. The figure shows the hexadecimal and equivalent text
  6428.  output produced by DUMP when it uses the hex.obj object file as
  6429.  input. DUMP was not instructed to strip all nonprintable characters from
  6430.  the output listing.
  6431.  
  6432.  
  6433. ╓┌─────────┌────────────────────────────────────────────────┌────────────────
  6434.  00000000  80 07 00 05 68 65 78 2E 43 BE 88 07 00 00 00 4D  │Ç...hex.C╛ê....M│
  6435.  00000010  53 20 43 6E 88 05 00 00 9F 45 4D 42 88 09 00 00  │S Cnê...ƒEMBê...│
  6436.  00000020  9F 53 4C 49 42 46 50 10 88 08 00 00 9F 53 4C 49  │ƒSLIBFP.ê...ƒSLI│
  6437.  00000030  42 43 64 88 07 00 00 9F 4C 49 42 48 B3 88 06 00  │BCdê...ƒLIBH│ê..│
  6438.  00000040  00 9D 30 73 4F E3 88 06 00 00 A1 01 43 56 37 96  │.¥0sOπê...í.CV7û│
  6439.  00000050  2E 00 00 06 44 47 52 4F 55 50 05 5F 54 45 58 54  │....DGROUP._TEXT│
  6440.  00000060  04 43 4F 44 45 05 5F 44 41 54 41 04 44 41 54 41  │.CODE._DATA.DATA│
  6441.  00000070  05 43 4F 4E 53 54 04 5F 42 53 53 03 42 53 53 3F  │.CONST._BSS.BSS?│
  6442.  00000080  98 07 00 28 CA 00 03 04 01 67 98 07 00 48 10 00  │ÿ..(╩....gÿ..H..│
  6443.  00000090  05 06 01 FD 98 07 00 48 00 00 07 07 01 0A 98 07  │...²ÿ..H......ÿ.│
  6444.  000000A0  00 48 00 00 08 09 01 07 9A 08 00 02 FF 03 FF 04  │.H......Ü... . .│
  6445.  000000B0  FF 02 56 9C 0D 00 00 03 01 02 02 01 03 04 40 01  │ .V£..........@.│
  6446.  000000C0  45 01 C0 8C 2D 00 0A 5F 5F 61 63 72 74 75 73 65  │E.└î-..__acrtuse│
  6447.  000000D0  64 00 09 5F 62 79 74 65 32 68 65 78 00 09 5F 77  │d.._byte2hex.._w│
  6448.  000000E0  6F 72 64 32 68 65 78 00 08 5F 5F 63 68 6B 73 74  │ord2hex..__chkst│
  6449. 000000E0  6F 72 64 32 68 65 78 00 08 5F 5F 63 68 6B 73 74  │ord2hex..__chkst│
  6450.  000000F0  6B 00 A8 A0 14 00 02 00 00 30 31 32 33 34 35 36  │k.¿á.....0123456│
  6451.  00000100  37 38 39 41 42 43 44 45 46 A8 A0 CE 00 01 00 00  │789ABCDEF¿á╬....│
  6452.  00000110  55 8B EC B8 04 00 E8 00 00 56 8A 46 04 2A E4 89  │Uï∞╕..Φ..VèF.*Σë│
  6453.  00000120  46 FE 8B 46 06 89 46 FC 8B D8 FF 46 FC 8B 76 FE  │F■ïF.ëFⁿï╪ Fⁿïv■│
  6454.  00000130  B1 04 D3 EE 81 E6 0F 00 8A 84 00 00 88 07 8B 5E  │▒.╙εüµ..èä..ê.ï^│
  6455.  00000140  FC FF 46 FC 8B 76 FE 81 E6 0F 00 8A 84 00 00 88  │ⁿ Fⁿïv■üµ..èä..ê│
  6456.  00000150  07 8B 5E FC C6 07 00 8B 46 06 5E 8B E5 5D C3 55  │.ï^ⁿ╞..ïF.^ïσ].U│
  6457.  00000160  8B EC B8 04 00 E8 00 00 56 8B 46 04 89 46 FE 8B  │ï∞╕..Φ..VïF.ëF■ï│
  6458.  00000170  46 06 89 46 FC 8B D8 FF 46 FC 8B 76 FE B1 0C D3  │F.ëFⁿï╪ Fⁿïv■▒.╙│
  6459.  00000180  EE 81 E6 0F 00 8A 84 00 00 88 07 8B 5E FC FF 46  │εüµ..èä..ê.ï^ⁿ F│
  6460.  
  6461.       FIGURE 10-2 ▐ Hex dump sample (HEX.DMP)
  6462.  
  6463.  
  6464.       When the -s option is selected, the ASCII text display is surrounded
  6465.  by vertical lines formed from the vertical bar symbol (|). The normal
  6466.  output, which assumes that the destination device is the PC screen, uses
  6467.  the IBM drawing character (code 179) for a single vertical line; this looks
  6468.  pretty on the screen, but cannot be printed correctly by many printers.
  6469.       The makefile for DUMP is contained in dump.mk:
  6470.  
  6471.    # makefile for dump utility
  6472.  
  6473.    LIB=c:\lib
  6474.    LLIB=c:\lib\local
  6475.  
  6476.    dump.obj:       dump.c
  6477.            msc $*;
  6478.  
  6479.    hexdump.obj:    hexdump.c
  6480.            msc $*;
  6481.  
  6482.    dump.exe:       dump.obj hexdump.obj $(LLIB)\util.lib
  6483.            link $* hexdump $(LIB)\ssetargv, $*, nul, $(LLIB)\util;
  6484.  
  6485.       To compile DUMP, first compile hex.c and add the object file to the
  6486.  utility library (\lib\local\util.lib); then type
  6487.  
  6488.       MAKE DUMP.MK
  6489.  
  6490.  The resulting executable file, DUMP.EXE, should be placed, as usual, in a
  6491.  directory that is accessible to DOS via the PATH variable, so that it can
  6492.  be called from anywhere in the directory hierarchy.
  6493.       The DUMP program can now be used for one of its intended purposes--
  6494.  determining the file format of a special-purpose data file. This will help
  6495.  us build another useful tool, SHOW, which lets us see nonprintable
  6496.  characters in files in an unambiguous way and recover text from specially
  6497.  formatted files.
  6498.  
  6499.  
  6500.  The SHOW Program
  6501.  
  6502.       The next program is called SHOW because it shows us things that we
  6503.  might not otherwise see. SHOW closely resembles DUMP in construction and
  6504.  purpose, but its output is very different from DUMP's.
  6505.       If the task at hand is to retrieve the essential text from a document
  6506.  file, rather than to determine how the formatting was done, we need a tool
  6507.  that filters out the nonprintable codes and emits a stream of ASCII-only
  6508.  text. In some situations, we might also want to see the formatting codes
  6509.  represented visibly.
  6510.       The SHOW program takes files that may contain special (usually
  6511.  nonprintable) characters in addition to normal text and produces an ASCII
  6512.  text output. Nonprintable codes are converted to their displayable
  6513.  hexadecimal notation--a backslash followed by two hex digits.
  6514.       Two options allow us to strip out special codes and produce an output
  6515.  that is pure text. The -s option strips all special codes except the usual
  6516.  format effectors (newline, tab, and space).
  6517.       The -w (word-processor) option strips special codes (by setting the -s
  6518.  option), but it handles several additional elements of word-processor data
  6519.  files as well. For example, when forming paragraphs, many word processors
  6520.  use a carriage return as a "soft" return (line break) and a combined
  6521.  carriage return and linefeed as a "hard" return (paragraph terminator). For
  6522.  compatibility with such word-processor file formats, the -w option of SHOW
  6523.  converts a lone carriage return to a newline, thus breaking long lines into
  6524.  a sequence of shorter ones. Also, some word processors use the eighth bit
  6525.  of some bytes to signal formatting options (bold, underline, end-of-word,
  6526.  and so on). To make these bytes visible, SHOW (with the -w option set)
  6527.  turns off the high bit of all bytes and displays the bytes that then fall
  6528.  into the printable ASCII range.
  6529.       The full manual page for SHOW is presented in Figure 10-3.
  6530.  Notice that SHOW is also a filter: It can read from its standard input
  6531.  channel and write the transformed output to the standard output
  6532.  channel.
  6533.       The pseudocode description for SHOW is nearly identical to that of
  6534.  DUMP. The primary differences are found in the showit() function, which is
  6535.  described (without options) by the following pseudeocode:
  6536.  
  6537.            for each input character
  6538.                    if it's ASCII
  6539.                            if it's printable or a format effector
  6540.                                    send it to stdout
  6541.                            else
  6542.                                    send printable equivalent to stdout
  6543.  
  6544.       The options -s and -w add some minor complications to the description.
  6545.  The effects of the options are best seen in the code for the main() and
  6546.  showit() functions. In main() (in the file show.c), getopt() is once again
  6547.  used to process command-line options. A variable, wflag, controls whether
  6548.  SHOW attempts to convert word-processing data files. It is initially FALSE.
  6549.  The call to getopt() requires the addition of w to the allowed option list.
  6550.  The value of wflag is passed to showit() as an argument, rather than being
  6551.  treated as global data. If SHOW had a lot of functions that needed to know
  6552.  about wflag, we would probably make it a global variable.
  6553.  
  6554.  
  6555.               ╔══════════════════════════════════════════╗
  6556.               ║                                          ║
  6557.               ║ Click to view the figure found on page  ║
  6558.               ║ 287 in the printed version of the book.  ║
  6559.               ║                                          ║
  6560.               ╚══════════════════════════════════════════╝
  6561.  
  6562.       FIGURE 10-3 ▐ Manual page for SHOW
  6563.  
  6564.  
  6565.               ╔══════════════════════════════════════════╗
  6566.               ║                                          ║
  6567.               ║ Click to view the listing found on page ║
  6568.               ║ 287 in the printed version of the book.  ║
  6569.               ║                                          ║
  6570.               ╚══════════════════════════════════════════╝
  6571.  
  6572.  
  6573.       The showit() function receives a FILE pointer and the values of sflag
  6574.  and wflag. If neither of the flags is TRUE, showit() passes anything that
  6575.  is printable (including format effectors) to the standard output channel
  6576.  and converts everything else to a displayable hexadecimal byte
  6577.  representation. Thus, the null byte is shown as \00, the ASCII Del
  6578.  character is shown as \7F, and so on. If -s was used on the command line,
  6579.  sflag (and therefore strip) is TRUE, which causes showit() to eliminate the
  6580.  hexadecimal conversions.
  6581.       Setting the -w option sets the wp variable in showit() to TRUE,
  6582.  causing the function to alter the default treatment of carriage returns and
  6583.  extended characters.
  6584.  
  6585.  
  6586.               ╔══════════════════════════════════════════╗
  6587.               ║                                          ║
  6588.               ║ Click to view the listing found on page ║
  6589.               ║ 290 in the printed version of the book.  ║
  6590.               ║                                          ║
  6591.               ╚══════════════════════════════════════════╝
  6592.  
  6593.  
  6594.       The makefile for SHOW is similar to the one for DUMP:
  6595.  
  6596.    # makefile for the show program
  6597.  
  6598.    LIB=c:\lib
  6599.    LLIB=c:\lib\local
  6600.  
  6601.    show.obj:       show.c
  6602.            msc $*;
  6603.  
  6604.    showit.obj:     showit.c
  6605.            msc $*;
  6606.  
  6607.    show.exe:       show.obj showit.obj $(LLIB)\util.lib
  6608.            link $* showit $(LIB)\ssetargv, $*, nul, $(LLIB)\util;
  6609.  
  6610.       To see the output of SHOW, try the program on a data file from a word
  6611.  processor or spreadsheet with and without the optional processing features.
  6612.  The results should be at least intriguing and will probably be enlightening
  6613.  to those who have not snooped around in such files before.
  6614.       In the next section, we move from the line-by-line realm of
  6615.  file oriented programs to the intensely visually oriented programs that
  6616.  make the PC so popular with the average user.
  6617.  
  6618.  
  6619.  
  6620.  SECTION IV  Screen-Oriented Programs
  6621.  
  6622.  
  6623.  
  6624.  Chapter 11  Screen Access Routines
  6625.  
  6626.  
  6627.  
  6628.       In this chapter and the next two, we will explore some of the many
  6629.  options available to designers for presenting information on a PC screen.
  6630.  The method of screen access presented in this chapter is relatively easy to
  6631.  implement, produces excellent results, and costs little in terms of program
  6632.  size and complexity. Its use is not recommended for programs that will
  6633.  operate in a windowing environment, however, because it violates one of the
  6634.  primary rules of good behavior: "Thou shalt not write directly to the
  6635.  screen."
  6636.       We will purposely break this rule at first to show what level of
  6637.  performance we are striving for. In Chapter 12, we will explore some
  6638.  interesting display buffering techniques and show how we can be "good" boys
  6639.  and girls by using BIOS-based screen routines as an alternative to direct
  6640.  access. Then, in Chapter 13, we will seek to be downright virtuous by
  6641.  using the ANSI standard interface that is portable to nearly every PC that
  6642.  runs a version of MS-DOS (and PC-DOS).
  6643.  
  6644.  
  6645.  Determining the Display System Type
  6646.  
  6647.       One of the challenges that has always faced PC software designers is
  6648.  writing a program that determines what type of display adapter and monitor
  6649.  is in use before it does anything else. It is unfortunate that many
  6650.  programs ignore this crucial first step.
  6651.       Many of the offending programs were designed by programmers working on
  6652.  monochrome-only systems. When run on a color/graphics-equipped system,
  6653.  these programs at least alter the user's selected video attributes so that
  6654.  either during or following the program's running, the screen is unpleasant
  6655.  to look at or has a whole new shade of reality. Sometimes, but not too
  6656.  often, the screen gets completely messed up by being left in an
  6657.  inappropriate video mode.
  6658.       Programs designed for color-only systems can produce similarly
  6659.  unpleasant effects on monochrome systems. For example, light blue is a very
  6660.  pleasing color for text, but it causes everything on a monochrome system to
  6661.  be underlined and intense. Then there's the problem of a program that
  6662.  assumes the needed display adapter is in use and does not check for its
  6663.  presence. This can leave the user with a system that appears to be
  6664.  frozen.
  6665.       And it isn't just amateur programmers who are guilty of these
  6666.  transgressions. Although most commercial software offerings check the
  6667.  hardware, many supposedly "commercial quality" programs have the bad
  6668.  manners to ignore the user's color preferences, video mode settings, and so
  6669.  on.
  6670.       We can do something about this situation. Following are some routines
  6671.  and program examples that show how to detect, save, and restore the user's
  6672.  operating conditions. The routines use BIOS functions and simple memory
  6673.  tests to determine what equipment is installed. You can call the functions,
  6674.  as demonstrated in the DSPYTYPE program that follows and the ScreenTest
  6675.  (ST) program later in this chapter, and save the video state at the time
  6676.  your program begins execution. The original operating conditions can then
  6677.  be restored before the program returns control to DOS.
  6678.  
  6679.  
  6680.               ╔══════════════════════════════════════════╗
  6681.               ║                                          ║
  6682.               ║ Click to view the listing found on page ║
  6683.               ║ 296 in the printed version of the book.  ║
  6684.               ║                                          ║
  6685.               ╚══════════════════════════════════════════╝
  6686.  
  6687.  
  6688.       The DSPYTYPE program is a test driver for the functions memchk() and
  6689.  ega_info(). These functions supplement the BIOS equipchk() and getstate()
  6690.  functions we saw in Chapter 5. The memchk() function looks for memory at a
  6691.  specified segment and offset. It is a small-model function that calls on
  6692.  the library function movedata() to write and read the contents of memory
  6693.  locations. A large-model version of memchk() would use memcpy() in place of
  6694.  movedata(). The memcpy() function takes long pointers used in large-model
  6695.  programs; movedata() requires segmented addresses to access far data items
  6696.  in small-model programs.
  6697.  
  6698.  
  6699.               ╔══════════════════════════════════════════╗
  6700.               ║                                          ║
  6701.               ║ Click to view the listing found on page ║
  6702.               ║ 298 in the printed version of the book.  ║
  6703.               ║                                          ║
  6704.               ╚══════════════════════════════════════════╝
  6705.  
  6706.  
  6707.       The technique used to detect the presence of a monochrome display
  6708.  adapter (MDA) or a color/graphics adapter (CGA) involves a sequence of
  6709.  operations that save, write, read, compare, and restore the contents of a
  6710.  memory location that is known to be in the range of the given adapter. The
  6711.  save and restore operations ensure that the test is non-destructive. The
  6712.  write-read-compare operation determines whether there is any active memory
  6713.  at the test location. The value 55H is a bit pattern of alternating 0s and
  6714.  1s. Because unoccupied memory locations tend to look like 1s to programs
  6715.  that try to read them, the pattern of the test value used  by memchk() is
  6716.  not likely to be duplicated. If you want to feel more sure of the results,
  6717.  use sequential tests at the same address with two or more different
  6718.  values.
  6719.       But why bother looking at the memory--why not simply read the mode
  6720.  value returned by getstate()? We will probably want to do both in our
  6721.  programs. Use getstate() to find out what the operating video mode is, and
  6722.  look for memory at the other adapter locations. Some PC users (programmers
  6723.  in particular) have both monochrome and color display systems installed in
  6724.  a PC. A program that was designed to work exclusively with a CGA system
  6725.  might find the system in mode 7 (monochrome). The program can respond to
  6726.  this in two ways: It can print a message stating its need for a CGA and
  6727.  quit, or it can look for a CGA system and switch to it if one is found. I
  6728.  favor the first approach because the user may have the color system turned
  6729.  off. He or she can use the DOS MODE command to select the needed display
  6730.  mode (MODE MONO to select monochrome; MODE CO80, MODE CO40, and so on, to
  6731.  select a CGA mode).
  6732.       The memchk() function has other uses, too, such as looking around for
  6733.  blocks of memory that might be separated from the main memory of a system.
  6734.  It cannot be used to detect the presence of read-only memory (ROM), of
  6735.  course, because ROM cannot be written to and it will fail our tests. You
  6736.  can use memchk() to check memory locations at one-kilobyte increments to
  6737.  count up how much RAM is available to DOS and how much might be
  6738.  noncontiguous. Many add-in memory boards permit splitmemory addressing,
  6739.  allowing you to set up stand-alone print spoolers and other special memory
  6740.  allocations. With memchk(), we have a convenient way to find them. Add
  6741.  memchk() to the utility library to make it readily accessible to your
  6742.  programs.
  6743.       The enhanced graphics adapter (EGA) complicates the video-adapter
  6744.  detection problem a bit. The address space of the EGA nominally starts at
  6745.  segment A000. However, an EGA's addressing modes are very flexible, and the
  6746.  adapter usually masquerades as a CGA or MDA while operating at the DOS
  6747.  command level, setting itself up to start at either the B800 or B000 memory
  6748.  segments. The problem is in determining whether an EGA or some other
  6749.  adapter is installed at a particular location. The function ega_info(),
  6750.  based on information provided by IBM, looks for an EGA by calling the BIOS
  6751.  video interrupt 10H and invoking the Alternate Function Select function
  6752.  AH = 18 (12H), which is an EGA BIOS feature (the EGA BIOS is located at
  6753.  segment C000H). When AL equals 10H upon entry, this function returns EGA
  6754.  information, which includes the EGA mode (color or monochrome) and memory
  6755.  size (64 to 256 KB) in addition to feature and switch-setting information.
  6756.  An EGA display system is absent if ega_info() returns mode values other
  6757.  than 0 and 1 and memory size values outside the range of 0 to 3.
  6758.       If an EGA is found, checking the video mode returned by getstate()
  6759.  tells us whether it is emulating a CGA or MDA. If we need to know if
  6760.  another adapter is installed, we can use memchk() to do the test. If an EGA
  6761.  is emulating a CGA, you cannot have a real CGA installed, so we need check
  6762.  only for the adapter type not being emulated by the EGA.
  6763.  
  6764.  
  6765.               ╔══════════════════════════════════════════╗
  6766.               ║                                          ║
  6767.               ║ Click to view the listing found on page ║
  6768.               ║ 301 in the printed version of the book.  ║
  6769.               ║                                          ║
  6770.               ╚══════════════════════════════════════════╝
  6771.  
  6772.  
  6773.       DSPYTYPE is a simple program that calls ega_info() and memchk() to
  6774.  determine what display adapters are installed in a system and to report the
  6775.  results to the user. DSPYTYPE also reports the current video mode, screen
  6776.  width, and screen page number. In practice, we would use the functions to
  6777.  get the video mode and the display adapter configuration, and then set up
  6778.  the needed display conditions for our program or abort with an explanatory
  6779.  message about the lack of needed hardware.
  6780.       Next we will consider a method of direct screen access for reading and
  6781.  writing that serves as an alternative to using BIOS and DOS methods of
  6782.  screen access. The BIOS and DOS functions automatically deal with the wide
  6783.  variety of display configurations that are possible in a PC system, but
  6784.  they introduce a lot of processing overhead because an adapter test occurs
  6785.  with every character (or dot) that is written or read. The display-adapter
  6786.  detection methods just described are important to direct screen access
  6787.  because they let us do the adapter tests once at program start-up, cutting
  6788.  out a huge amount of processing during subsequent screen read and write
  6789.  operations.
  6790.  
  6791.  
  6792.  Direct Screen Access
  6793.  
  6794.       A fast but inherently nonportable way of updating the PC screen is to
  6795.  write directly to its associated memory. Using one or more off-screen
  6796.  buffers in the program's data space to create images before displaying them
  6797.  can produce excellent visual performances. A block-copy routine quickly
  6798.  copies the buffered-memory image to physical display memory, producing an
  6799.  "instant screen" effect. The IBM monochrome display adapter exhibits no
  6800.  problems with this strategy. Neither do the IBM enhanced graphics adapter
  6801.  and many third party monochrome and color/graphics adapters, all of which
  6802.  have been designed to permit simultaneous access to display memory by both
  6803.  the CPU and the display-refresh circuitry.
  6804.       The original CGA in either 40-column or 80-column text mode poses a
  6805.  problem for designers because, unlike the monochrome adapter, the CGA
  6806.  exhibits visible interference when a program tries to access display memory
  6807.  while the monitor screen is being updated (refreshed) from the same memory.
  6808.  The interference looks like "snow"--an irritating pulsing of short line
  6809.  segments covering all or portions of the screen. Several methods of
  6810.  avoiding the interference have been developed. One is to synchronize the
  6811.  display accesses during both reading and writing operations with the time
  6812.  periods within a display-refresh cycle that are considered "safe." The safe
  6813.  times are the horizontal and vertical retrace periods of each displayed
  6814.  frame. Another method involves blanking (turning off) the raster scan while
  6815.  the display memory is being written to. As we'll see, each approach has
  6816.  advantages and disadvantages.
  6817.  
  6818.  
  6819.       Display Adapter Basics
  6820.  
  6821.       We begin with a brief examination of the color/graphics adapter to see
  6822.  why the retrace periods are the only safe times for display memory
  6823.  accesses. This discussion is not applicable to the IBM enhanced graphics
  6824.  adapter because it uses faster memory devices and additional logic that
  6825.  prevent problems that occur when the CPU and video-refresh circuitry try to
  6826.  access the video buffer simultaneously.
  6827.       The memory on the standard CGA is placed within the address space of
  6828.  the central processor. The CGA memory starts at segment B800H and extends
  6829.  upward for 16 KB, enough memory for one high-resolution graphics screen
  6830.  (128,000 picture elements), or four screen pages in 80-column color text
  6831.  mode. The text mode is the focus of this discussion.
  6832.  
  6833.  
  6834.               ╔══════════════════════════════════════════╗
  6835.               ║                                          ║
  6836.               ║    Figure 11-1 is found on page 303      ║
  6837.               ║    in the printed version of the book.   ║
  6838.               ║                                          ║
  6839.               ╚══════════════════════════════════════════╝
  6840.  
  6841.       FIGURE 11-1 ▐ Typical horizontal sweep signal
  6842.  
  6843.  
  6844.       Figure 11-1 on the previous page represents the horizontal
  6845.  sweep signal, which is the signal within the display device that is
  6846.  responsible for the horizontal deflection of the electron beam that paints
  6847.  the screen. The figure depicts one horizontal scan period. The dependent
  6848.  (vertical) axis depicts the amount of beam deflection as a function of time
  6849.  shown along the independent (horizontal) axis. On a computer display
  6850.  device, the image on the screen is under-scanned so that the image is
  6851.  completely visible within the normal viewing area, resulting in a framed
  6852.  picture with a visible border. Television sets, on the other hand, use
  6853.  over-scanning to make the image "bleed," leaving no border. The beam is
  6854.  turned off completely (blanked) during retrace to avoid leaving unwanted
  6855.  residue on the screen.
  6856.       The IBM display is not interlaced, so there are 262.5 horizontal scans
  6857.  per frame (one full screen image), and frames occur at a rate of 60 per
  6858.  second. With 15,750 horizontal scans per second, each one takes about 63.4
  6859.  microseconds. Only a small portion of a single scan, typically 20 percent
  6860.  or less, is allocated to the horizontal retrace--one of the safe times for
  6861.  display memory accesses, as can be seen in the figure.
  6862.       The horizontal sweep signal moves the electron beam from side to side,
  6863.  and if it were the only sweep signal affecting the beam, there would be
  6864.  only a single straight line on the display surface. Another kind of sweep
  6865.  signal is needed to move the beam up and down the face of the tube. The
  6866.  vertical sweep has the same basic sawtooth shape as the horizontal sweep,
  6867.  but a slower rate of change. At minimum deflection, the beam is at the top
  6868.  of the screen; it moves toward the bottom with increasing deflection.
  6869.       There is a vertical retrace period at the end of each frame that
  6870.  occurs during the vertical-sync pulse period. During this time, the
  6871.  electron beam is blanked and moved from the lower right corner of the
  6872.  screen back to the upper left corner. The vertical-sync period (typically a
  6873.  little more than one millisecond for the standard American TV signals used
  6874.  by the CGA) is long enough to permit a block of about 250 data words (2-
  6875.  byte character and attribute pairs) to be transferred to or from display
  6876.  memory without interference. Most video controller designs disable the
  6877.  horizontal sweep during the vertical retrace, but some let it run
  6878.  continuously.
  6879.  
  6880.  
  6881.       Programming Considerations
  6882.  
  6883.       A few important choices affect the way programs that interact tightly
  6884.  with the display system are designed and written. There are enough choices
  6885.  at every step in the process of designing a video application so that no
  6886.  two designers are likely to do the job in exactly the same way. The
  6887.  following program implements one way of designing a video interface. It is
  6888.  not the only way. Other methods that are even more intimately tied to
  6889.  specific hardware can run as much as four times faster than the method
  6890.  described here, but they are much less portable to other hardware
  6891.  configurations.
  6892.       I decided to use a buffered screen interface. This means that the
  6893.  image to be sent to the display is assembled in the program's own data
  6894.  space. When complete, the image is copied in its entirety as quickly as
  6895.  possible to display memory.
  6896.       Conversely, an unbuffered approach is used by many programs and is
  6897.  adequate for most purposes. In the typical unbuffered scheme, characters
  6898.  are written into display memory via DOS and BIOS routines, but no memory
  6899.  image is retained by the application program. The DOS and BIOS routines can
  6900.  also be used in a buffered screen-management system but will slow things
  6901.  down quite a bit compared to direct methods. As we'll see in the next
  6902.  chapter, careful design can greatly improve the apparent speed of the DOS
  6903.  and BIOS routines under many operating conditions.
  6904.       The routines described in this chapter assume that programs calling
  6905.  them have already done an equipment inventory and have set up the display
  6906.  system in an 80-column text mode. The getstate() function obtains the
  6907.  current video mode, screen width, and visual display page values. Mode
  6908.  values of 2 or 3 indicate 80-column text modes (monochrome and color,
  6909.  respectively) on a CGA, and a value of 7 indicates a monochrome adapter.
  6910.  The DOS MODE command may be used to select an appropriate video mode before
  6911.  calling programs that require a particular mode.
  6912.  
  6913.  
  6914.       Alternative Solutions
  6915.  
  6916.       Available methods of synchronization to avoid visual interference on a
  6917.  CGA depend on the use of the status register at I/O address 3DA hex. This
  6918.  is a read-only register on the CGA that has two bits of interest to the
  6919.  block-copy routine described below. When high (1), bit 0 indicates that a
  6920.  horizontal retrace is in progress. When high, bit 3 indicates that a
  6921.  vertical retrace is in progress.
  6922.       Another register--a write-only register at I/O address 3D8 hex on the
  6923.  CGA--has a bit that may be reset (made 0) to disable video. Bit 3 must be
  6924.  set to a value of 1 to turn on the beam that paints the screen. Turning off
  6925.  the beam is an effective way to prevent visual interference from reaching
  6926.  the viewer's eye. However, the beam cannot be left off for more than about
  6927.  three character rows' worth of data before a flicker becomes apparent. The
  6928.  BIOS video scroll routines use this technique and are not pleasant to look
  6929.  at if the background color is anything but black. Even normal text on a
  6930.  black background appears to dim somewhat in the upper half on the display
  6931.  when the blanking method is used.
  6932.  
  6933.  
  6934.  A Synchronized Block-copy Routine
  6935.  
  6936.       The direct method of screen access uses a memory buffer that holds the
  6937.  same amount of data as one display page on the standard CGA. The block-copy
  6938.  routine, cpblk(), copies the contents of the memory buffer to display
  6939.  memory only during safe times. The memory buffer has a total of 4000 bytes;
  6940.  2000 bytes are for characters (25 rows by 80 columns), and the other 2000
  6941.  bytes hold the attributes associated with the characters. Display memory
  6942.  has 4096 bytes per page (four pages in 80-column mode), but the last 96
  6943.  bytes of each page are unused (except by some programs that hide
  6944.  information in them).
  6945.       The C-language source for the block-copy routine is cpblk.c. An image
  6946.  is copied from application memory to display memory as a series of ten
  6947.  blocks of 200 words each. Each word represents a character and its
  6948.  associated video attribute. (An assembly-language version of this routine
  6949.  is described in my "Instant Screens" article in the June 1986 issue of
  6950.  PC Tech Journal. Portions of this chapter are based on that article.)
  6951.  
  6952.  
  6953.               ╔══════════════════════════════════════════╗
  6954.               ║                                          ║
  6955.               ║ Click to view the listing found on page ║
  6956.               ║ 306 in the printed version of the book.  ║
  6957.               ║                                          ║
  6958.               ╚══════════════════════════════════════════╝
  6959.  
  6960.  
  6961.       The C version of cpblk() is about 60 percent slower than its assembly-
  6962.  language counterpart, but it is fast enough for most purposes, copying an
  6963.  entire screen in about two-tenths of a second. The assembly-language
  6964.  version uses both horizontal and vertical retrace periods to reduce the
  6965.  number of blocks needed to six instead of ten. The use of ten blocks is
  6966.  conservative and will work with nearly all IBM-compatible  display reduce
  6967.  the number of blocks needed to six instead of ten. The use of ten blocks is
  6968.  conservative and will work with nearly all IBM-compatible display hardware.
  6969.  On all of the IBM machines I tested, eight blocks were sufficient; however,
  6970.  some compatibles required smaller blocks because of differences in the
  6971.  timing of the retrace signal used to determine the safe time for copying
  6972.  data.
  6973.  
  6974.  
  6975.  Double Buffering
  6976.  
  6977.       To obtain the snappiest looking performance from a buffered screen-
  6978.  interface technique, programs can use an in-memory screen buffer that is
  6979.  updated out of view of the user and then is copied to display memory as
  6980.  fast as possible. A way of achieving nearly instant CGA updates is shown in
  6981.  Figure 11-2. The technique is called double buffering because two levels
  6982.  of buffers are maintained in the application program and elsewhere in main
  6983.  memory. A two-step process is used to form a composite image in a screen
  6984.  buffer before it is copied to physical display memory.
  6985.       Data source buffers may be of any size and are usually thought of as
  6986.  being rectangular in shape although they are simply sequences of bytes in
  6987.  memory. They are mapped to the off-screen buffer as needed--a technique
  6988.  that permits windows for help frames, menus, and the like to be easily
  6989.  overlaid onto another image. In the next chapter, we will write a set of
  6990.  library functions that handle the needed operations, such as writing
  6991.  characters, attributes, and strings, scrolling regions, and so on, and
  6992.  demonstrate the technique in a sample program.
  6993.  
  6994.  
  6995.               ╔══════════════════════════════════════════╗
  6996.               ║                                          ║
  6997.               ║    Figure 11-2 is found on page 308      ║
  6998.               ║    in the printed version of the book.   ║
  6999.               ║                                          ║
  7000.               ╚══════════════════════════════════════════╝
  7001.  
  7002.       FIGURE 11-2 ▐ Double buffering
  7003.  
  7004.  
  7005.       Once the off-screen buffer has all of its characters and attributes in
  7006.  the right places, the task of getting the data to the visual display is
  7007.  handled by the cpblk() routine described above. If the screen buffer is
  7008.  copied directly to the part of display memory being viewed, the user will
  7009.  see the screen being updated, albeit very quickly. If the contents of the
  7010.  before and after images vary only in small areas, it is difficult to notice
  7011.  that an update occurs.
  7012.       If, however, there are massive image changes, such as switching
  7013.  background colors, the user will detect the update. For some purposes, the
  7014.  visible updating of screen displays is desirable because it reassures the
  7015.  user that the program is doing something. In other situations, we will want
  7016.  screen images to snap instantly into place. We can have it either way.
  7017.  
  7018.  
  7019.  Demonstration Program
  7020.  
  7021.       The file st.c contains the C-language source for a test driver program
  7022.  called ST (for ScreenTest). The driver program uses a static screen buffer
  7023.  to hold text images that are copied en masse to display memory after the
  7024.  image is fully constructed.
  7025.       The getkey() function (see Chapter 5) calls DOS function 7 hex, which
  7026.  returns the next available character in the keyboard buffer. The function
  7027.  waits for a keypress if nothing is ready. The driver program displays a
  7028.  help message for any nonprintable keypress except Esc (the Quit command)
  7029.  and Ctrl-Break (Abort). As each character is keyed in, its value is used to
  7030.  fill the screen buffer. Differing color attributes derived from the
  7031.  character code are used to show the effects of massive color and intensity
  7032.  changes. The attribute for a given character is the code for the character
  7033.  shifted left 8 bit positions. The high bit is masked off to prevent screen
  7034.  blinking.
  7035.  
  7036.  
  7037.               ╔══════════════════════════════════════════╗
  7038.               ║                                          ║
  7039.               ║ Click to view the listing found on page ║
  7040.               ║ 309 in the printed version of the book.  ║
  7041.               ║                                          ║
  7042.               ╚══════════════════════════════════════════╝
  7043.  
  7044.  
  7045.       The driver program uses a trick called "page flipping" to produce the
  7046.  appearance of instant screen displays. It actually takes a few tenths of a
  7047.  second to copy a screen buffer in the application data space to the display
  7048.  adapter using the cpblk() routine. Although this is fast when compared to
  7049.  all other methods that avoid video interference, it is still far from
  7050.  instantaneous. The page flipping method is possible because the CGA has
  7051.  enough display memory to hold multiple screen pages simultaneously.
  7052.       An illustration of page flipping is presented in Figure 11-3.
  7053.  The method depends on having a means of telling the display system to
  7054.  view one page of display memory while the application program is writing to
  7055.  another. The ROM BIOS video interrupt includes a function (5) that sets the
  7056.  visual page.
  7057.  
  7058.  
  7059.               ╔══════════════════════════════════════════╗
  7060.               ║                                          ║
  7061.               ║    Figure 11-3 is found on page 313      ║
  7062.               ║    in the printed version of the book.   ║
  7063.               ║                                          ║
  7064.               ╚══════════════════════════════════════════╝
  7065.  
  7066.       FIGURE 11-3 ▐ Page flipping
  7067.  
  7068.  
  7069.                                COMMENT
  7070.            Ignore the Technical Reference statement that the
  7071.            function sets the "active" page. To be consistent with
  7072.            the way BASIC describes video pages, the active page
  7073.            should be the one being written and the visual page
  7074.            should be the one being viewed. Most frequently, the
  7075.            visual and active pages are one and the same display
  7076.            page.
  7077.  
  7078.       Notice that the fprintf() standard library function writes to standard
  7079.  error, which appears on the visual page. The cpblk() routine, however, is
  7080.  directed to write to the active page, which is effectively hidden from
  7081.  view. When the active page has been fully written, it is revealed to the
  7082.  user by flipping the pages. The function swap_int() exchanges the values of
  7083.  the apg and vpg variables. The source for swap_int() is contained in the
  7084.  file swap_int.c. The function is useful in other contexts, so we will add
  7085.  it to our utility library.
  7086.  
  7087.    /*
  7088.     *      swap_int -- exchange the values of the two integers
  7089.     */
  7090.  
  7091.    void
  7092.    swap_int(p1, p2)
  7093.    register int *p1;
  7094.    register int *p2;
  7095.    {
  7096.            int tmp;
  7097.  
  7098.            /* exchange the values */
  7099.            tmp = *p1;
  7100.            *p1 = *p2;
  7101.            *p2 = tmp;
  7102.    }
  7103.  
  7104.       After building the active page, ST then calls setpage(), another of
  7105.  our bios library routines from Chapter 5, to switch to the new visual
  7106.  page. The effect from the computer user's perspective is that of an
  7107.  instant update. There is a short delay while the active page is being
  7108.  updated before the page swap, but it's not noticeable to the user. The
  7109.  screen is repainted in one-sixtieth of a second, far faster than the human
  7110.  eye can follow, and the response to a keypress is usually completed before
  7111.  the key is released.
  7112.       The special efforts to synchronize with the vertical-retrace signal
  7113.  taken by the cpblk() routine are not needed when an MDA is being used.
  7114.  Therefore, the driver program, ST, checks for mode 7 and uses a standard
  7115.  block-copy routine that invokes the string-copy feature of the 8086/88
  7116.  processor. A string copy of 4 KB is done very quickly. There is no visible
  7117.  flicker and no apparent delay when this approach is used.
  7118.       An option on the invocation command line for ST permits a single
  7119.  argument (anything will do) to be given to turn off the special copy
  7120.  feature when operating on an EGA or on a compatible computer that does not
  7121.  experience display interference. Using the command ST x, for example, will
  7122.  tell the program to use a standard block-copy routine instead of the
  7123.  special one. If this option is selected on a system with a standard CGA,
  7124.  visible interference will be quite noticeable, especially if you hold down
  7125.  a key to repeatedly write the same data to the display.
  7126.       The makefile for ST, st.mk, contains the instructions needed by MAKE
  7127.  to build and maintain the ST programs.
  7128.  
  7129.    # makefile for ScreenTest (ST) program
  7130.  
  7131.    LLIB=c:\lib\local
  7132.  
  7133.    swap_int.obj:   swap_int.c
  7134.  
  7135.    cpblk.obj:      cpblk.c
  7136.  
  7137.    st.obj:         st.c
  7138.  
  7139.    st.exe:         st.obj swap_int.obj cpblk.obj $(LLIB)\bios.lib
  7140.                    $(LLIB)\dos.lib
  7141.            link $* swap_int cpblk, $*, nul, $(LLIB)\bios $(LLIB)\dos;
  7142.  
  7143.  
  7144.  Design Considerations
  7145.  
  7146.       Because it takes a few tenths of a second to copy data from a screen
  7147.  buffer to display memory, programs should not attempt to write one
  7148.  character at a time from the keyboard. This would result in a maximum
  7149.  update rate of a few characters per second. Even a very slow typist would
  7150.  get way ahead of such a program.
  7151.       A better way to handle this situation is to use a modified cpblk()
  7152.  function that updates only the changed character or the rectangular region
  7153.  in which changes have been made to the buffer. We could also use routines
  7154.  based on the BIOS and DOS interrupts to update the visual display page
  7155.  (they do so without causing interference) and use a separate routine to
  7156.  update the in-memory buffer so that it continues to track what is being
  7157.  displayed.
  7158.       The screen-update routines mentioned above can be fashioned from the
  7159.  cpblk() function. For example, a routine that copies a single line or a
  7160.  small range of lines, or one that copies a small rectangular region from a
  7161.  screen buffer to display memory, would be useful in doing selective screen
  7162.  updates with shorter delays. The next chapter describes screen-buffer
  7163.  routines that do these and other screen-buffer management tasks.
  7164.       Remember that the cpblk() routine described here is inherently less
  7165.  portable than the DOS and BIOS calls and thus should be used only when
  7166.  necessary for speed and effect. The symbolic constant that holds the
  7167.  display memory segment could be replaced by a variable. It would then be a
  7168.  simple matter to change the value for machines that put display memory in a
  7169.  nonstandard place, if a reliable method can be developed for determining
  7170.  the identity of the host hardware and its display memory location(s). There
  7171.  is no foolproof method of doing the hardware identity tests; the best we
  7172.  can do is look for copyright notices and company names, but finding out the
  7173.  exact machine type remains an elusive goal. In my programs, I make the
  7174.  assumption that displays are in the standard IBM-specified locations. This
  7175.  works on 90 percent of the machines currently in use. For the other 10
  7176.  percent I produce customized versions of the programs and charge a little
  7177.  extra to compensate for the added effort.
  7178.  
  7179.  
  7180.  
  7181.  Chapter 12  Buffered Screen-Interface Functions
  7182.  
  7183.  
  7184.  
  7185.       The purpose of this chapter is to extend the screen interface
  7186.  presented in Chapter 11 by building a set of routines that let us
  7187.  directly access a screen buffer as a virtual screen.
  7188.  
  7189.  
  7190.  A Buffered Screen-Interface Package
  7191.  
  7192.       We have already seen how a memory image of a text screen can be
  7193.  quickly copied to physical display memory, giving the appearance of instant
  7194.  or at least fast screen updates. Our concern now is how to form the memory
  7195.  image of the desired display. We want to form the images in a way that
  7196.  simplifies the effort needed to manage several distinct areas of the
  7197.  screen. For example, a screen may be divided into viewing areas devoted to
  7198.  user commands, program status, and text. A provision might also be made to
  7199.  display help frames in a region that partially or completely overlays
  7200.  another area of the screen.
  7201.       While developing the interface presented in this chapter, I wrote a
  7202.  test driver program (SB_TEST) that exercises each interface function to
  7203.  assure correct action. The test program creates several screen regions (or
  7204.  windows) that can be filled, cleared, scrolled, and so on, under control of
  7205.  the user at the keyboard. We'll develop the functions first, then apply the
  7206.  driver to show off some of the capabilities of the screen interface.
  7207.       An element in display memory consists of two bytes: one representing a
  7208.  character and the other an associated attribute. This design is
  7209.  considerably more flexible than the design used by many low-cost terminals
  7210.  that use a single byte for each screen location. The latter design requires
  7211.  that some screen positions be "wasted" (they appear blank) because they are
  7212.  used to select attributes for all positions that follow until the next
  7213.  attribute-setting position. (The attribute positions are referred to by
  7214.  some as "magic cookies.") The PC design requires twice as much storage but
  7215.  permits attributes to be assigned on a character-by-character basis.
  7216.       We will form a virtual-screen buffer as a two-dimensional array of
  7217.  "cells," with each cell being represented as a union. This allows our
  7218.  programs to access each cell as individual character and attribute bytes or
  7219.  as a single character/attribute pair. The definitions of a cell, the
  7220.  virtual-screen buffer, and other components of the buffered screen
  7221.  interface are contained in sbuf.h.
  7222.  
  7223.  
  7224.               ╔══════════════════════════════════════════╗
  7225.               ║                                          ║
  7226.               ║ Click to view the listing found on page ║
  7227.               ║ 318 in the printed version of the book.  ║
  7228.               ║                                          ║
  7229.               ╚══════════════════════════════════════════╝
  7230.  
  7231.  
  7232.       Buffer Management Functions
  7233.  
  7234.       One of the other components referred to above is a definition of a
  7235.  REGION, a structure that effectively defines a window, which in this
  7236.  implementation is simply a rectangular region of the screen buffer. To keep
  7237.  things reasonably simple, this interface keeps all screen data (characters
  7238.  and attributes) in the screen-buffer array, which is created by sb_init().
  7239.  There are no "shadow" buffers for individual windows.
  7240.  
  7241.  
  7242.               ╔══════════════════════════════════════════╗
  7243.               ║                                          ║
  7244.               ║ Click to view the listing found on page ║
  7245.               ║ 320 in the printed version of the book.  ║
  7246.               ║                                          ║
  7247.               ╚══════════════════════════════════════════╝
  7248.  
  7249.  
  7250.       The screen buffer is allocated as global data in sb_init.c. It has
  7251.  type union CELL. The declaration
  7252.  
  7253.       union CELL Scrnbuf[SB_ROWS][SB_COLS];
  7254.  
  7255.  uses the constants defined in sbuf.h to allocate 4000 bytes of storage for
  7256.  the screen buffer. The sb_init.c file also allocates the Sbuf structure
  7257.  that holds screen-buffer control information. Sbuf is of type struct
  7258.  BUFFER. It contains "cursor" row and column position variables, a pointer
  7259.  to the screen-buffer array, 2 bytes (unsigned short) of status flags, and
  7260.  arrays of column positions that mark the beginning and end of changes to
  7261.  each screen-buffer row. The range limits allow the routine that copies the
  7262.  screen buffer to physical-display memory to restrict the number of
  7263.  characters that must actually be written, thus minimizing the time needed
  7264.  to do display updates.
  7265.       The sb_show() function (in sb_show.c) is capable of using several
  7266.  methods to copy the screen-buffer contents to display memory. The buffer-
  7267.  copying method that is actually selected depends upon the user-specified
  7268.  access type (UPDATEMODE equal to DIRECT or BIOS access) and the type of
  7269.  display system that is installed in the host machine (IBM monochrome
  7270.  display adapter, or MDA, versus all other types).
  7271.  
  7272.  
  7273.               ╔══════════════════════════════════════════╗
  7274.               ║                                          ║
  7275.               ║ Click to view the listing found on page ║
  7276.               ║ 321 in the printed version of the book.  ║
  7277.               ║                                          ║
  7278.               ╚══════════════════════════════════════════╝
  7279.  
  7280.  
  7281.       The sb_show() function in DIRECT update mode checks to see how many
  7282.  screen-buffer rows differ from those currently displayed. If there are two
  7283.  or fewer, they are copied individually. Any greater number of changed
  7284.  screen rows causes a block-copy operation (based on the cpblk() function
  7285.  that is described in Chapter 11) to be performed. If the DOS variable
  7286.  UPDATEMODE is not set, BIOS is assumed. Adding the line
  7287.  
  7288.       set updatemode=direct
  7289.  
  7290.  to one's AUTOEXEC.BAT file (or typing it on the DOS command line) causes
  7291.  screen updates to use the faster, but less portable, direct-access
  7292.  method.
  7293.  
  7294.  
  7295.       General Window Functions
  7296.  
  7297.       Now that we can create a screen buffer and copy it to display memory,
  7298.  we can proceed to prepare functions that place the data to be displayed
  7299.  into the buffer. The user sees nothing until the image is fully formed and
  7300.  copied. This allows programs to handle overlapping windows and special
  7301.  effects without distracting the user. By writing the least amount of data
  7302.  to the screen, this buffered-screen interface also speeds up the apparent
  7303.  performance of programs. The following functions do the work.
  7304.  
  7305.         sb_new(). As noted earlier in this chapter, screens are easier
  7306.          to manage if they are divided into separate regions where
  7307.          each region is devoted to a single purpose. The function
  7308.          sb_new() is used to describe a new window by allocating a window-
  7309.          control structure that contains row and column values for two
  7310.          opposite corners (upper left and lower right) of the window and a
  7311.          scrolling region within it, a "cursor" position, and a status flag
  7312.          word. Initially, the scrolling region is set to the same dimensions
  7313.          as the window itself. It can be set to other dimensions by calling
  7314.          sb_set_scrl().
  7315.               Space for the control structure is allocated by a call to
  7316.          malloc(). NULL is returned if there is not enough memory
  7317.          satisfy the request. A successful request results in a pointer to
  7318.          the window-control structure (struct REGION *) being returned. All
  7319.          positioning, character and string operations, scrolling, and
  7320.          clearing is done with respect to this window pointer.
  7321.  
  7322.  
  7323.               ╔══════════════════════════════════════════╗
  7324.               ║                                          ║
  7325.               ║ Click to view the listing found on page ║
  7326.               ║ 324 in the printed version of the book.  ║
  7327.               ║                                          ║
  7328.               ╚══════════════════════════════════════════╝
  7329.  
  7330.  
  7331.         sb_move(). This function is used to move the cursor to a specified
  7332.          location within a window. Unreasonable requests (outside window
  7333.          boundaries) elicit an error return code (SB_ERR). If the request is
  7334.          in bounds, both the window-relative and buffer-relative cursor row
  7335.          and column values are updated and SB_OK is returned.
  7336.  
  7337.  
  7338.               ╔══════════════════════════════════════════╗
  7339.               ║                                          ║
  7340.               ║ Click to view the listing found on page ║
  7341.               ║ 325 in the printed version of the book.  ║
  7342.               ║                                          ║
  7343.               ╚══════════════════════════════════════════╝
  7344.  
  7345.  
  7346.         sb_fill(). A window may be filled with a given character and
  7347.          attribute pair by calling sb_fill(). Every cell in the specified
  7348.          window is set equal to the character and attribute values passed as
  7349.          arguments. Two variations on the theme, sb_filla() and sb_fillc(),
  7350.          are used to fill a window with either an attribute or a character
  7351.          while leaving the other component of each cell undisturbed.
  7352.  
  7353.  
  7354.               ╔══════════════════════════════════════════╗
  7355.               ║                                          ║
  7356.               ║ Click to view the listing found on page ║
  7357.               ║ 326 in the printed version of the book.  ║
  7358.               ║                                          ║
  7359.               ╚══════════════════════════════════════════╝
  7360.  
  7361.  
  7362.         sb_putc(). This function puts a character into a window at the
  7363.          current cursor position and advances the cursor. Accommodation is
  7364.          made for wrapping at the end of a window row, and scrolling is
  7365.          forced when a character is written to the last position of the last
  7366.          row of a window. Scrolling can be disabled by clearing the
  7367.          SB_SCROLL bit in the window flag word (the default state). Standard
  7368.          format-effector control codes (newline, carriage return, linefeed,
  7369.          and tab) are treated normally.
  7370.  
  7371.         sb_puts(). A string may be added to a window using sb_puts(), which
  7372.          simply calls sb_putc() to do its work for each character in the
  7373.          string.
  7374.  
  7375.  
  7376.               ╔══════════════════════════════════════════╗
  7377.               ║                                          ║
  7378.               ║ Click to view the listing found on page ║
  7379.               ║ 328 in the printed version of the book.  ║
  7380.               ║                                          ║
  7381.               ╚══════════════════════════════════════════╝
  7382.  
  7383.  
  7384.         sb_rca(). Used to read the character and attribute values from the
  7385.          screen-buffer cell at the current window cursor location. The
  7386.          functions sb_ra() and sb_rc() read the individual attribute and
  7387.          character values, respectively. This is a simple task because all
  7388.          the data is available in the screen buffer. In the case of
  7389.          sb_rca(), an unsigned short quantity is returned. The sb_ra() and
  7390.          sb_rc() functions return an unsigned char value.
  7391.  
  7392.  
  7393.               ╔══════════════════════════════════════════╗
  7394.               ║                                          ║
  7395.               ║ Click to view the listing found on page ║
  7396.               ║ 331 in the printed version of the book.  ║
  7397.               ║                                          ║
  7398.               ╚══════════════════════════════════════════╝
  7399.  
  7400.  
  7401.         sb_scrl(). To scroll a window vertically, use sb_scrl(). This
  7402.          function scrolls a region within a window. The scrolling region is
  7403.          frequently made smaller than the full window size to allow for a
  7404.          boxed border and a title line and other such trimmings. A
  7405.          comparable effect can be obtained by defining a window within a
  7406.          window, but then the regions must be managed individually, adding
  7407.          to program overhead.
  7408.  
  7409.  
  7410.               ╔══════════════════════════════════════════╗
  7411.               ║                                          ║
  7412.               ║ Click to view the listing found on page ║
  7413.               ║ 332 in the printed version of the book.  ║
  7414.               ║                                          ║
  7415.               ╚══════════════════════════════════════════╝
  7416.  
  7417.  
  7418.         sb_wca(). The sb_wca() function writes both a character and an
  7419.          attribute to a window at the current cursor position. A repetition
  7420.          count tells the function the extent of the write operation. The
  7421.          cursor position is not changed. The sb_wa() and sb_wc() functions
  7422.          write an attribute and a character, respectively, to a window. Note
  7423.          that sb_wca() combines the two unsigned char values into a single
  7424.          word by shifting the attribute left eight positions and bitwise
  7425.          ORing the result with the character value. We could achieve the
  7426.          same result by using separate assignment statements for the
  7427.          attribute and character components of the screen-buffer cell. The
  7428.          "write" routines are useful for such tasks as displaying horizontal
  7429.          portions of character graphics and changing the highlighting of
  7430.          text strings like those used in menu bars and pop-up selection
  7431.          menus.
  7432.  
  7433.  
  7434.               ╔══════════════════════════════════════════╗
  7435.               ║                                          ║
  7436.               ║ Click to view the listing found on page ║
  7437.               ║ 335 in the printed version of the book.  ║
  7438.               ║                                          ║
  7439.               ╚══════════════════════════════════════════╝
  7440.  
  7441.  
  7442.       Box-drawing Functions
  7443.  
  7444.       A portion of the screen that demands special attention is often
  7445.  highlighted by drawing a box around it. Some screen designs require that
  7446.  particular areas of the screen be set off by box designs that signify their
  7447.  purposes. For example, a program that displays multiple windows might
  7448.  identify inactive windows by surrounding them with single-line boxes while
  7449.  highlighting the currently active window with a double-line box.
  7450.       The IBM extended-ASCII character set (codes 128 through 255), which is
  7451.  supported by nearly all PC-compatible computers, includes a group of line-
  7452.  drawing characters that are suitable for drawing boxes and other shapes
  7453.  with various combinations of single and double lines, full and partial
  7454.  blocks, and various regular dot patterns.
  7455.       The sb_box() function can be called to draw a box around the perimeter
  7456.  of a window. The box is drawn entirely within the bounds of the specified
  7457.  window; therefore, to avoid overwriting it, text functions should be
  7458.  instructed to write to an area at least one character within the box. Box
  7459.  corners and edges are formed from characters that match correctly at all
  7460.  junctions. The box types are defined in the box.h header file.
  7461.  
  7462.    /*
  7463.     *      box.h -- header for box-drawing functions
  7464.     */
  7465.  
  7466.    typedef struct box_st {
  7467.            short ul, ur, ll, lr;   /* corners */
  7468.            short tbar, bbar;       /* horizontal bars */
  7469.            short lbar, rbar;       /* vertical bars */
  7470.    } BOXTYPE;
  7471.  
  7472.    /* box types */
  7473.    #define BOXASCII        0
  7474.    #define BOX11           1
  7475.    #define BOX22           2
  7476.    #define BOX12           3
  7477.    #define BOX21           4
  7478.    #define BOXBLK          5
  7479.  
  7480.  
  7481.               ╔══════════════════════════════════════════╗
  7482.               ║                                          ║
  7483.               ║ Click to view the listing found on page ║
  7484.               ║ 338 in the printed version of the book.  ║
  7485.               ║                                          ║
  7486.               ╚══════════════════════════════════════════╝
  7487.  
  7488.  
  7489.  The Screen-Buffer Library
  7490.  
  7491.       The sbuf.lib is a library that contains the object modules for each of
  7492.  the screen-buffer functions just described. The library has wide
  7493.  applicability to screen-oriented programs. It will reside in the \lib\local
  7494.  subdirectory for easy access by our programs. The makefile, sbuf.mk, using
  7495.  the inference rules in tools.ini, keeps all objects and the library file
  7496.  current and puts files in the required places. If you use a different
  7497.  directory scheme, you should modify the makefile to accommodate the
  7498.  differences.
  7499.  
  7500.  
  7501.               ╔══════════════════════════════════════════╗
  7502.               ║                                          ║
  7503.               ║ Click to view the listing found on page ║
  7504.               ║ 340 in the printed version of the book.  ║
  7505.               ║                                          ║
  7506.               ╚══════════════════════════════════════════╝
  7507.  
  7508.  
  7509.    # inference rules for SB_TEST program
  7510.    # (small model with function prototyping enabled)
  7511.  
  7512.    [make]
  7513.    .c.obj:
  7514.            msc -DLINIT_ARGS $*;
  7515.  
  7516.  
  7517.       A Test Driver Program
  7518.  
  7519.       The SB_TEST program (sources are in files sb_test.c and sb_test.h)
  7520.  alluded to earlier is one of several programs I used to test the SBUF
  7521.  library functions as they were being developed. The makefile (sb_test.mk)
  7522.  presumes that the screen-buffer function library is up to date. To use the
  7523.  test driver program, simply type its name at the DOS prompt.
  7524.       SB_TEST creates several windows and places some text in each. On a
  7525.  color display, each window sports its own color scheme. On a monochrome
  7526.  display, the status window displays in reverse video and the help window
  7527.  contains bold text; the other windows display normal text.
  7528.  
  7529.    /*
  7530.     *      sb_test.h -- header for sb_test program
  7531.     */
  7532.  
  7533.    /* dimensions of the display windows */
  7534.    #define CMND_ROW        0
  7535.    #define CMND_COL        0
  7536.    #define CMND_HT         1
  7537.    #define CMND_WID        SB_COLS
  7538.    #define STAT_ROW        CMND_HT
  7539.    #define STAT_COL        0
  7540.    #define STAT_HT         1
  7541.    #define STAT_WID        SB_COLS
  7542.    #define TEXT_ROW        (CMND_HT + STAT_HT)
  7543.    #define TEXT_COL        0
  7544.    #define TEXT_HT         (SB_ROWS - TEXT_ROW)
  7545.    #define TEXT_WID        SB_COLS
  7546.    #define HELP_ROW        5
  7547.    #define HELP_COL        5
  7548.    #define HELP_HT         18
  7549.    #define HELP_WID        70
  7550.  
  7551.  
  7552.               ╔══════════════════════════════════════════╗
  7553.               ║                                          ║
  7554.               ║ Click to view the listing found on page ║
  7555.               ║ 342 in the printed version of the book.  ║
  7556.               ║                                          ║
  7557.               ╚══════════════════════════════════════════╝
  7558.  
  7559.  
  7560.    # makefile for SB_TEST driver program
  7561.  
  7562.    LLIB = c:\lib\local
  7563.    LINC = c:\include\local
  7564.  
  7565.    sb_test.obj: sb_test.c $(LINC)\sbuf.h sb_test.h
  7566.  
  7567.    sb_test.exe: sb_test.obj $(LLIB)\sbuf.lib $(LLIB)\bios.lib
  7568.                    $(LLIB)\dos.lib
  7569.            link $*, $*, nul, $(LLIB)\sbuf $(LLIB)\bios $(LLIB)\dos;
  7570.  
  7571.       After compiling and linking the program, try using scrolling and
  7572.  clearing commands in the help and text windows to see how the command
  7573.  actions are implemented. Of course, a real program would display real text
  7574.  rather than the repeated sequences of letters that are produced by the test
  7575.  driver.
  7576.       The commands are described in the following table:
  7577.  
  7578.  ═══════════════════════════════════════════════════════════════════════════
  7579.    COMMAND                     DESCRIPTION
  7580.  ───────────────────────────────────────────────────────────────────────────
  7581.    Up and down arrows       │  Scroll up and down by one row
  7582.    PgUp, PgDn               │  Scroll up and down by one page (a windowful
  7583.                             │  minus one row)
  7584.    Alt-c                    │  Clear the current window
  7585.    Alt-h                    │  Display a help frame
  7586.    Alt-s                    │  Fill the status window
  7587.    Alt-r                    │  Read in a text file
  7588.    Alt-t                    │  Fill the text area
  7589.    Esc                      │  Clean up the display and quit
  7590.  ───────────────────────────────────────────────────────────────────────────
  7591.  
  7592.       The file-reading operation in SB_TEST demonstrates how implicit
  7593.  scrolling works. The scroll is called by the sb_putc() function, called
  7594.  either directly or indirectly by sb_puts().
  7595.       When used in the BIOS update mode, the screen-buffer function library
  7596.  is reasonably portable. Direct mode is, of course, portable only to highly
  7597.  PC-compatible computers. In the next chapter, we will look at the most
  7598.  portable of the screen management tools: the ANSI device driver.
  7599.  
  7600.  
  7601.  
  7602.  Chapter 13  The ANSI Device Driver
  7603.  
  7604.  
  7605.  
  7606.       DOS versions 2.00 and later can be extended to use peripheral devices
  7607.  not planned for in the basic design of DOS. The use of installable device
  7608.  drivers is the basis of this extendability. ANSI.SYS, the best known of
  7609.  these device drivers, is supplied with DOS to handle special keyboard and
  7610.  screen features. This chapter focuses on ANSI.SYS and its uses.
  7611.  
  7612.  
  7613.  ANSI Basics
  7614.  
  7615.       The American National Standards Institute (ANSI), long before becoming
  7616.  involved in efforts to standardize the C programming language, proposed
  7617.  terminal and computer-equipment standards that have since become adopted by
  7618.  the computer industry. Adherence to the standards is voluntary, but
  7619.  terminal and computer manufacturers who choose to ignore them do so at
  7620.  their own peril. Nearly all terminal equipment built since the late 1970s
  7621.  conforms, more or less, to the ANSI standards.
  7622.       Conformance to the ANSI standards can be claimed as long as functions
  7623.  that are implemented use the specified codes to invoke them; it is not
  7624.  necessary to implement every function and capability described by the
  7625.  standards. In addition, the ANSI standards permit the implementation of
  7626.  private functions that are invoked by codes outside the range of those
  7627.  specified. Thus, a Digital Equipment Corporation VT100 terminal, which does
  7628.  not have insert- and delete-line capabilities, and which has many private
  7629.  capabilities, is considered to be an ANSI-conforming terminal.
  7630.       The standard that concerns us most directly with regard to our program
  7631.  designs is X3.64--1979. (The number following the dash represents the year
  7632.  in which the standard became effective or was last revised.) This standard
  7633.  defines a set of "additional controls" for use with the ASCII character set
  7634.  defined in X3.4--1977 (a revision of X3.4--1968) and the code extensions
  7635.  defined in X3.41--1974. The additional controls are invoked by using
  7636.  control-function sequences and strings, which are commonly referred to as
  7637.  escape sequences because the ASCII escape code (ESC, 27 decimal, 16 hex) is
  7638.  used as the initial character. The escape code is the signal that
  7639.  identifies the codes that follow as control information rather than
  7640.  ordinary graphic symbols.
  7641.       The uses of ANSI control sequences are primarily those of screen and
  7642.  keyboard handling, but other control tasks, such as switching to alternate
  7643.  character sets and responding to requests for terminal type identification
  7644.  ("Who are you?"), are covered. The standard accommodates change with built-
  7645.  in mechanisms for adding controls and with periodic review and revision as
  7646.  circumstances dictate.
  7647.       ANSI control sequences have the general form shown in Figure 13-1.
  7648.  The ASCII escape (shown as ESC--a single code, not three characters) is
  7649.  the introducer. It is followed by a string called the intermediate
  7650.  bit combinations, usually a mixture of numeric and alphabetic characters,
  7651.  and a final character that represents the command to be executed.
  7652.  
  7653.  
  7654.  EXAMPLE: cursor position (CUP)
  7655.  
  7656.                                                 ESC  [20;42H
  7657.                                                 ▒▒▒  ▒▒▒▒▒ ▒▒
  7658.                                                  │     │    │
  7659.                                                  │     │    │
  7660.              ┌───────────────────────────────────┘     │    │
  7661.              │                                         │    └─┐
  7662.              │                     ┌───────────────────┘      │
  7663.    FORMAT:   │                     │                          │
  7664.              │                     │                          │
  7665.        ESC (code 27)     (optional parameters)         (command name)
  7666.              │                     │                          │
  7667.       ┌──────┴──────┐      ┌───────┴───────┐           ┌──────┴──────┐
  7668.       │             │      │               │           │             │
  7669.       │ introducer  │      │ intermediate  │           │ final       │
  7670.       │ character   │      │ string        │           │ character   │
  7671.       │             │      │ combinations  │           │             │
  7672.       └─────────────┘      │               │           └─────────────┘
  7673.       The Escape code      └───────────────┘           The letter H is the
  7674.       introduces the       [ begins the intermediate   "cursor position"
  7675.       control sequence.    string, which includes      command.
  7676.                            the row and column
  7677.                            numbers separated by
  7678.                            a semicolon.
  7679.  
  7680.       FIGURE 13-1 ▐ ANSI control sequence formation
  7681.  
  7682.  
  7683.       The string in the middle of a control sequence provides variable data
  7684.  that modifies the behavior of the basic command. For example, the sequence
  7685.  for moving the cursor forward (CUF) is ESC[#C. The sharp sign (pound sign
  7686.  or octothorpe, if you prefer) is a replaceable numeric parameter. If a
  7687.  value is given, it specifies the number of columns to move the cursor. If
  7688.  the value is zero or not specified, the default value of one is used.
  7689.  Attempts to move past the end of the line are ignored.
  7690.       The example given in Figure 13-1 shows how to form the sequence
  7691.  that moves the cursor to an absolute position on the screen. Impossible
  7692.  requests are simply ignored.
  7693.  
  7694.  
  7695.  Display Memory
  7696.  
  7697.       Figure 13-2 shows how a character and its associated attribute
  7698.  are stored in a PC's display memory. In a 25-line by 80-column text
  7699.  mode, each display-screen image requires a total of 4000 bytes of
  7700.  memory, half devoted to characters and half to attributes. The even-
  7701.  numbered byte (starting at zero) of each display-memory word holds the
  7702.  code for the character. The odd-numbered byte holds the attribute.
  7703.  
  7704.  
  7705.     7     6     5     4     3     2     1     0
  7706.  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
  7707.  │     │     │     │     │     │     │     │     │ character byte (even)
  7708.  └──┬──┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
  7709.     │  ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
  7710.     │                       │
  7711.     │              ASCII character code
  7712.     │
  7713.     └──extended character flag
  7714.  
  7715.     7     6     5     4     3     2     1     0
  7716.  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
  7717.  │ B/I │  R  │  G  │  B  │  I  │  R  │  G  │  B  │ attribute byte (odd)
  7718.  └──┬──┴─────┴─────┴─────┴──┬──┴─────┴─────┴─────┘
  7719.     │   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒   │   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
  7720.     │           │           │           │
  7721.     │     background color  │    foreground color
  7722.     │                       │
  7723.     │                       └────────────────────foreground intensity
  7724.     └──blink or background intensity
  7725.  
  7726.       FIGURE 13-2 ▐ Character and attribute bytes
  7727.  
  7728.  
  7729.       The low seven bits (0--6) of a character byte uniquely specify one
  7730.  of the possible 128 character codes (0--127) defined in the ASCII character
  7731.  set.
  7732.       The most significant bit of the character byte, when set (1),
  7733.  indicates an extended-ASCII code, one that falls in the range 128 through
  7734.  255. On an IBM PC and most compatibles, these are foreign alphabetics,
  7735.  block graphics, and other special characters.
  7736.       The attribute byte is used to control monochrome and color
  7737.  attributes for each character on an individual basis. As Figure 13-2
  7738.  shows, bits 0 through 2 specify the foreground color, and bit 3 indicates
  7739.  the intensity level of the foreground. Together, these four bits determine
  7740.  the color value of the character, providing a range of 16 colors. On an
  7741.  IBM monochrome adapter, only three of the values produce unique results
  7742.  (0 = black, 1 = underlined, and 7 = white). All other values below 7
  7743.  produce normal white, while values between 8 and 15 produce "bright"
  7744.  versions of their respective values in the low range. Thus, the value 15
  7745.  produces a bright white foreground.
  7746.       The background color (or grey level) of each character cell is
  7747.  specified by bits 4 through 6. The background can have one of only eight
  7748.  colors.
  7749.       Bit 7 serves one of two purposes depending on whether blinking is
  7750.  enabled or disabled. When the blink feature is enabled, setting background
  7751.  values above 7 (bit 7 set) produces quite an obnoxious blink in the
  7752.  foreground of the associated character. Disabling the blink produces a far
  7753.  more pleasing result: a stable background that can be any of 16 different
  7754.  colors on suitable display equipment. Chapter 5 presents routines for
  7755.  controlling the blink feature.
  7756.  
  7757.  
  7758.  ANSI.SYS
  7759.  
  7760.       As mentioned previously, the ANSI.SYS file provided with DOS versions
  7761.  2.00 and later is an installable device driver that provides a subset of
  7762.  ANSI-standard screen and keyboard control functions. When loaded into the
  7763.  computer's memory, the ANSI driver monitors keyboard input and screen
  7764.  output data streams.
  7765.       For screen output, when one of the escape sequences handled by the
  7766.  ANSI driver is received, the driver performs the associated action, such as
  7767.  moving the cursor or setting a display attribute. During keyboard
  7768.  operations, individual keys and combinations of keys can be intercepted and
  7769.  converted into customized character strings. Most codes are simply passed
  7770.  on without anything being done to them.
  7771.       ANSI escape sequences can be called from within our C programs or even
  7772.  from DOS batch files. The SetColor program described later in this chapter
  7773.  is an example of a C program that uses the ANSI driver to do most of the
  7774.  work involved in controlling the attributes of a color text display.
  7775.  
  7776.  
  7777.       Installing ANSI.SYS
  7778.  
  7779.       Installation of ANSI.SYS is trivial. DOS uses the file CONFIG.SYS to
  7780.  configure the system when it is started. If the ANSI.SYS file is in the
  7781.  directory c:\dos, for example, adding the line
  7782.  
  7783.       device=c:\dos\ansi.sys
  7784.  
  7785.  to CONFIG.SYS will cause DOS to load the ANSI device driver each time the
  7786.  system is turned on ("cold boot") or restarted using Ctrl-Alt-Del ("warm
  7787.  boot").
  7788.       A program that depends on the ANSI device driver should test for its
  7789.  presence. If the ANSI driver is not loaded into memory, the program should
  7790.  abort and provide instructions to the user about installing it. A method of
  7791.  testing for the presence of the device driver is presented in the next
  7792.  section of this chapter.
  7793.  
  7794.  
  7795.       The ANSI Control Codes
  7796.  
  7797.       Figure 13-3 (on the next page) summarizes the ANSI standard
  7798.  control capabilities and indicates which are supported by the DOS
  7799.  ANSI device driver. Note that some of the supported capabilities (for
  7800.  example, erase in display, ED) are implemented in a simplified way, and
  7801.  that others (for example, set mode, SM, and reset mode, RM) are decidedly
  7802.  implementation-dependent, being tied intimately to the IBM PC hardware
  7803.  architecture.
  7804.       The following descriptions are intended to supplement the
  7805.  information presented in the documentation provided with DOS, particularly
  7806.  with respect to usage guidelines. The subsequent section of this chapter
  7807.  develops an interface package to the ANSI driver and provides practical
  7808.  examples of these codes in use.
  7809.  
  7810.    CONTROL SEQUENCES WITH NUMERIC PARAMETERS
  7811.  
  7812. ╓┌─────────────┌───────────┌─────────────────────────────────────────────────╖
  7813.    ANSI.SYS    Mnemonic    Description
  7814.    ANSI.SYS    Mnemonic    Description
  7815.  ───────────────────────────────────────────────────────────────────────────
  7816.    No          CBT         Cursor Backward Tabulation
  7817.    No          CHA         Cursor Horizontal Absolute
  7818.    No          CHT         Cursor Horizontal Tabulation
  7819.    No          CNL         Cursor Next Line
  7820.    No          CPL         Cursor Preceding Line
  7821.    Yes         CPR         Cursor Position Report
  7822.    Yes         CUB         Cursor Backward
  7823.    Yes         CUD         Cursor Down
  7824.    Yes         CUF         Cursor Forward
  7825.    Yes         CUP         Cursor Position
  7826.    Yes         CUU         Cursor Up
  7827.    No          CVT         Cursor Vertical Tabulation
  7828.    No          DA          Device Attributes
  7829.    No          DCH         Delete Character
  7830.    No          DL          Delete Line
  7831.    No          ECH         Erase Character
  7832.    No          FNT         Font Selection
  7833.    No          GSM         Graphic Size Modification
  7834.    No          GSS         Graphic Size Selection
  7835.    ANSI.SYS    Mnemonic    Description
  7836.   No          GSS         Graphic Size Selection
  7837.    No          HPA         Horizontal Position Absolute
  7838.    No          HPR         Horizontal Position Relative
  7839.    Yes         HVP         Horizontal and Vertical Position
  7840.    No          ICH         Insert Character
  7841.    No          IL          Insert Line
  7842.    No          NP          Next Page
  7843.    No          PP          Preceding Page
  7844.    No          REP         Repeat
  7845.    No          SD          Scroll Down
  7846.    No          SL          Scroll Left
  7847.    No          SPI         Spacing Increment
  7848.    No          SR          Scroll Right
  7849.    No          SU          Scroll Up
  7850.    No          TSS         Thin Space Specification
  7851.    No          VPA         Vertical Position Absolute
  7852.    No          VPR         Vertical Position Relative
  7853.  
  7854.  
  7855.    CONTROL SEQUENCES WITH SELECTIVE PARAMETERS
  7856.  
  7857. ╓┌─────────────┌───────────┌─────────────────────────────────────────────────╖
  7858.    ANSI.SYS    Mnemonic    Description
  7859.  ─────────────────────────────────────────────────────────────────────────
  7860.    No          CTC         Cursor Tabulation Control
  7861.    No          DAQ         Define Area Qualification
  7862.    Yes         DSR         Device Status Report
  7863.    No          EA          Erase in Area
  7864.    Yes         ED          Erase in Display
  7865.    No          EF          Erase in Field
  7866.    Yes         EL          Erase in Line
  7867.    No          JFY         Justify
  7868.    No          MC          Media Copy
  7869.    No          QUAD        QUAD
  7870.    Yes         RM          Reset Mode
  7871.    No          SEM         Select Editing Extent Mode
  7872.    Yes         SGR         Set Graphic Rendition
  7873.    Yes         SM          Set Mode
  7874.    No          TBC         Tabulation Clear
  7875.  
  7876.       FIGURE 13-3 ▐ ANSI control sequences
  7877.  
  7878.  
  7879.       Cursor Position (CUP)  ESC[#;#H
  7880.       Horizontal and Vertical Position (HVP)  ESC[#;#f
  7881.       These two control sequences do the same job. They move the cursor to
  7882.  the position designated by the parameters: the row (the first #) and the
  7883.  column. Illegal requests are ignored. The upper left corner of the screen
  7884.  is designated row 1, column 1. Missing or zero-valued parameters default to
  7885.  1; therefore, a CUP or HVP sequence with no stated parameters is a synonym
  7886.  for "homing" the cursor.
  7887.  
  7888.       Cursor Up (CUU)  ESC[#A
  7889.       Cursor Down (CUD)  ESC[#B
  7890.       Cursor Forward (CUF)  ESC[#C
  7891.       Cursor Backward (CUB)  ESC[#D
  7892.       Each of these control sequences moves the cursor by # positions (the
  7893.  default is 1) in the specified direction. Attempts to move beyond either
  7894.  end of a row or off the top or bottom of the screen are ignored. For
  7895.  example, if a request to move the cursor down exceeds the number of rows
  7896.  between the one containing the cursor and the bottom row of the screen, the
  7897.  cursor is deposited on the bottom row. No scrolling occurs.
  7898.  
  7899.       Device Status Report (DSR)  ESC[6n
  7900.       Save Cursor Position (SCP)  ESC[s
  7901.       Restore Cursor Position (RCP)  ESC[u
  7902.       These three control sequences are designed to assist in cursor
  7903.  control. The first two determine where the cursor is located, and the third
  7904.  returns the cursor to a saved location. The DSR request causes the ANSI
  7905.  driver to place a string, in the form ESC[#;#R plus a trailing carriage
  7906.  return, into the keyboard buffer. The position parameters are stored as
  7907.  two-character representations of the numbers, so the string contains a
  7908.  total of nine characters. The ansi_cpr() function described in the next
  7909.  section can be used to retrieve the row and column data.
  7910.       The SCP/RCP pair of control sequences can be used to save a cursor
  7911.  position so that it can be restored after an intervening operation moves
  7912.  the cursor from the saved position. Only one position can be saved for
  7913.  subsequent restoration. If multiple calls are made to SCP, a call to RCP
  7914.  will restore the cursor position obtained by the most recent call to
  7915.  SCP.
  7916.  
  7917.       Erase in Display (ED)  ESC[2J
  7918.       Erase in Line (EL)  ESC[K
  7919.       The ED control sequence erases the entire screen by displaying blanks
  7920.  in the prevailing video attribute (see SGR). The cursor is deposited at the
  7921.  upper left corner of the screen. EL erases the characters from the cursor
  7922.  position to the end of the current row. The "erased" positions are set to
  7923.  blanks in the prevailing attribute, and the cursor is left at its current
  7924.  position.
  7925.  
  7926.                                CAUTION
  7927.            Some versions of the ANSI device driver provided with
  7928.            MS-DOS (for example, MS-DOS 2.11 for the AT&T PC6300)
  7929.            erase by setting characters to blanks in the normal
  7930.            (white on black) attribute rather than the attribute
  7931.            set by SGR.
  7932.  
  7933.       Set Graphic Rendition (SGR)  ESC[#; ... ;#m   A graphic rendition is
  7934.  the visual style of displaying a graphic symbol (number, letter,
  7935.  punctuation mark, and so forth), which in the IBM PC context translates
  7936.  into the attribute associated with a character. The ANSI driver uses the
  7937.  SGR control sequence to set the intensity, blink status, and color
  7938.  attributes of characters on a color adapter. On a monochrome system, SGR
  7939.  can be used to set intensity, blink status, and underlining. Some IBM-
  7940.  compatible machines (COMPAQ and AT&T are two examples) permit grey-scale
  7941.  settings on monochrome monitors in response to color attribute requests.
  7942.       SGR can take a series of numeric parameters in a single call. The
  7943.  effect of a single call to SGR with multiple parameters is the same as that
  7944.  produced by a series of calls to SGR with one parameter per call, but the
  7945.  former will be faster because of reduced function-call overhead.
  7946.  
  7947.       Set Mode (SM)  ESC[=#h
  7948.       Reset Mode (RM)  ESC[=#l
  7949.       The mode control sequences that are supported by the ANSI driver
  7950.  set the IBM PC and compatible hardware-dependent video modes for the
  7951.  color/graphics adapter and a "wrap-at-end-of-line" mode. The driver does
  7952.  not support switching to a monochrome adapter from a color/graphics
  7953.  adapter. Both SM and RM can be used to set a video mode with the parameters
  7954.  0 through 6. Setting a different video mode clears the current mode. Using
  7955.  SM with a parameter of 7 enables wrap; RM given the same value disables
  7956.  wrap.
  7957.  
  7958.  
  7959.       Pros and Cons of ANSI.SYS
  7960.  
  7961.       The primary reasons for using the ANSI device driver are simplified
  7962.  programming and portability of programs to other versions of MS-DOS and
  7963.  PC-DOS and to the machines that run DOS. The amount of programming effort
  7964.  required to do many screen operations is reduced to simple calls to ANSI
  7965.  control sequences. Compare the short cursor-positioning macro shown in the
  7966.  next section to the BIOS-interrupt-based function presented in Chapter 5;
  7967.  both do the same job.
  7968.       Unfortunately, some limitations of the ANSI driver make it unsuitable
  7969.  for many applications. For some purposes--full-screen text editing is a
  7970.  good example--it is simply too slow. In addition, ANSI.SYS implements only
  7971.  a very limited subset of ANSI X3.64. It has no concept of fields, protected
  7972.  display areas, display regions (useful for windowing), and many other
  7973.  important capabilities.
  7974.  
  7975.  
  7976.  An ANSI Interface Package
  7977.  
  7978.       In spite of its limitations, the ANSI driver has its place. It
  7979.  comprises a useful but limited subset of ANSI capabilities that can do a
  7980.  surprising amount of work. The interface to the ANSI driver described here
  7981.  uses mostly macros in a header file to accomplish the tasks of moving the
  7982.  cursor, finding its position, setting video attributes, clearing the
  7983.  display, and setting video modes. A few supporting C functions built on the
  7984.  ANSI driver take care of some higher level tasks, such as testing to see
  7985.  whether the driver is installed.
  7986.       The macros and definitions of the video attributes and modes are
  7987.  contained in the header file ansi.h, which resides in the \include\local
  7988.  directory. You should #include the file in any C program or function that
  7989.  will need to access the keyboard or screen through the ANSI device
  7990.  driver.
  7991.  
  7992.  
  7993.               ╔══════════════════════════════════════════╗
  7994.               ║                                          ║
  7995.               ║ Click to view the listing found on page ║
  7996.               ║ 358 in the printed version of the book.  ║
  7997.               ║                                          ║
  7998.               ╚══════════════════════════════════════════╝
  7999.  
  8000.  
  8001.       ANSI color attributes follow a different sequence from the standard
  8002.  IBM attribute set. The ansi.h file contains definitions for the basic
  8003.  colors and "shift" values (offsets) for foreground and background attribute
  8004.  specifications. In addition, there are definitions for normal, bright,
  8005.  blink, reverse, and invisible attributes. The normal attribute effectively
  8006.  clears all other special attributes. Thus, to set the foreground to bright
  8007.  green, one uses the statements
  8008.  
  8009.       ANSI_SGR(ANSI_GREEN + ANSI_FOREGROUND);
  8010.       ANSI_SGR(ANSI_BRIGHT);
  8011.  
  8012.  which will cause all subsequent characters sent to the screen to be
  8013.  displayed in the requested attribute until a new specification is
  8014.  given.
  8015.       A typedef is used to create a POSITION data type that is used by some
  8016.  of the functions in the SetColor program to keep track of which color
  8017.  attribute position (foreground, background, or border) is being attended to
  8018.  in the processing of attribute specifications. Each position must be
  8019.  handled in its own special way, as we'll see shortly.
  8020.       In addition to the macros defined in ansi.h, our interface employs
  8021.  several functions. The ansi_cpr() function is used to request a device
  8022.  status report (ANSI_DSR) and then to read the cursor-position data stored
  8023.  in the keyboard buffer. The format of the response is a string of nine
  8024.  characters, starting with ESC and ending with a carriage return. This is
  8025.  called the cursor position report, hence the name ansi_cpr() for the
  8026.  function.
  8027.       The ansi_cpr() function calls getkey(), a DOS interrupt-based routine
  8028.  (INT 21H) that was defined in Chapter 5, to gather input from the keyboard
  8029.  buffer. Here is the code for ansi_cpr():
  8030.  
  8031.  
  8032.               ╔══════════════════════════════════════════╗
  8033.               ║                                          ║
  8034.               ║ Click to view the listing found on page ║
  8035.               ║ 361 in the printed version of the book.  ║
  8036.               ║                                          ║
  8037.               ╚══════════════════════════════════════════╝
  8038.  
  8039.  
  8040.       Because the characters are placed in the buffer in a reasonable order,
  8041.  successive keyboard read calls can be used to gather and process the
  8042.  numeric values easily. We must read every character in the string, even
  8043.  though we're really interested in only four of them, so that no residue is
  8044.  left behind to befuddle other parts of our program's input processing. The
  8045.  formal parameters row and col are pointers to storage outside the function.
  8046.       A character representation of the tens digit of a number (row or
  8047.  column) is obtained by a call to getkey(). It is converted to its numeric
  8048.  representation by subtracting 0 (decimal 48) and then multiplying by ten
  8049.  (its numeric weight). The value is then added to the converted value of the
  8050.  next character obtained by getkey(), which is the units digit, to produce
  8051.  the needed number in integer format.
  8052.       We now have the ability to determine the cursor's position and to save
  8053.  it or pass the information on to other parts of our programs. But, if the
  8054.  ANSI device driver is not already loaded into memory, calling ansi_cpr()
  8055.  will cause the computer to appear to lock up. We need to be sure the driver
  8056.  is installed before calling functions that depend on its presence. The
  8057.  function ansi_tst() calls ANSI_CUP, which sets the cursor to a known
  8058.  location, and then calls our bios library function readcur() to read the
  8059.  cursor location. If the row and column positions match, the driver is
  8060.  probably loaded. I say "probably" because the routine will falsely claim
  8061.  the driver is loaded if the cursor happens to be at the test location
  8062.  before the ANSI_CUP call. (However, this is not likely for the test values
  8063.  used.) To be certain, a series of two tests at differing locations could be
  8064.  used.
  8065.  
  8066.  
  8067.               ╔══════════════════════════════════════════╗
  8068.               ║                                          ║
  8069.               ║ Click to view the listing found on page ║
  8070.               ║ 362 in the printed version of the book.  ║
  8071.               ║                                          ║
  8072.               ╚══════════════════════════════════════════╝
  8073.  
  8074.  
  8075.       The code for ansi_tst() aborts with an error message and instructions
  8076.  to the user about installing the ANSI driver. An alternative design might
  8077.  simply return a true/false response and let the program deal with the lack
  8078.  of a driver in some other way, such as switching to a mode of operation
  8079.  that does not depend on the ANSI driver being loaded. The file ansi.mk is
  8080.  the makefile to compile the separate components of the ANSI interface
  8081.  package.
  8082.  
  8083.    # makefile for the ANSI library
  8084.  
  8085.    LINC=c:\include\local
  8086.    LLIB=c:\lib\local
  8087.  
  8088.    ansi_cpr.obj:   ansi_cpr.c $(LINC)\ansi.h
  8089.            msc $*;
  8090.            lib $(LLIB)\ansi -+$*;
  8091.  
  8092.    ansi_tst.obj:   ansi_tst.c $(LINC)\ansi.h $(LINC)\video.h
  8093.            msc $*;
  8094.            lib $(LLIB)\ansi -+$*;
  8095.  
  8096.  
  8097.  The SetColor Program
  8098.  
  8099.       Now we'll do something useful with the ANSI interface. SetColor,
  8100.  invoked as SC to save keystrokes (in the UNIX tradition), is designed to be
  8101.  used with a computer that has a color display system. With minor
  8102.  modifications it can be used in a limited way with monochrome systems,
  8103.  too.
  8104.       Figure 13-4 is the manual page for SetColor. The program
  8105.  sets foreground and background attributes via ANSI control sequences. The
  8106.  ANSI driver provides no way to control the border attribute, so we resort
  8107.  to a BIOS routine, palette(), developed in Chapter 5, to do that job. The
  8108.  use of palette() may make the program nonportable to some nominally IBM-
  8109.  compatible equipment.
  8110.  
  8111.  
  8112.               ╔══════════════════════════════════════════╗
  8113.               ║                                          ║
  8114.               ║ Click to view the figure found on page  ║
  8115.               ║ 364 in the printed version of the book.  ║
  8116.               ║                                          ║
  8117.               ╚══════════════════════════════════════════╝
  8118.  
  8119.       FIGURE 13-4 ▐ Manual page for SetColor
  8120.  
  8121.  
  8122.       The following pseudocode description of SetColor reveals that the
  8123.  program exhibits extreme schizophrenia. On the one hand, it operates in an
  8124.  interactive mode when invoked as SC without arguments. The user selects
  8125.  attributes by pressing function keys. Screen updates immediately show the
  8126.  user what the combined attribute selections look like. However, when called
  8127.  with attribute specifications as arguments, SetColor runs in batch mode.
  8128.  All attributes and intensity modifiers, if any, are taken from the command
  8129.  line. In this mode, SetColor can be used to control screen appearance from
  8130.  within DOS batch files.
  8131.  
  8132.    if ANSI driver not loaded
  8133.            print message
  8134.            exit
  8135.    if not in color text mode
  8136.            print message
  8137.            exit
  8138.    if command-line argument given
  8139.            run in batch mode
  8140.    else
  8141.            run in interactive (menu) mode
  8142.    clear the screen
  8143.    exit
  8144.  
  8145.       The SetColor program consists of several functions, each packaged in
  8146.  its own file. Some of the functions have general applicability to other
  8147.  tasks and are, therefore, placed in local libraries.
  8148.       Starting at the top, the source file sc.c contains the main()
  8149.  function. In main(), two tests are made that determine whether the SetColor
  8150.  program runs to completion or quits early. The first test, ansi_tst(),
  8151.  checks to see that the ANSI device driver is installed. If not, the program
  8152.  terminates. If the driver is installed, the second function, iscolor(),
  8153.  finds out what video mode is in use. Rather than do anything fancy at this
  8154.  point, we opt to simply terminate program execution with a help message if
  8155.  the mode is not a standard color-text mode. The user's selection of batch
  8156.  or interactive operation, based upon the presence or absence of command-
  8157.  line arguments, is also detected here.
  8158.  
  8159.  
  8160.               ╔══════════════════════════════════════════╗
  8161.               ║                                          ║
  8162.               ║ Click to view the listing found on page ║
  8163.               ║ 367 in the printed version of the book.  ║
  8164.               ║                                          ║
  8165.               ╚══════════════════════════════════════════╝
  8166.  
  8167.  
  8168.       If attribute arguments are given, the function parse() is called with
  8169.  the argument list obtained by main(). The job of parse() is to analyze each
  8170.  argument and form attribute or color specifications that can be processed
  8171.  by the low-level ANSI interface functions. If no arguments are given, the
  8172.  menumode() function is called. Let's first examine the batch processing
  8173.  functions.
  8174.  
  8175.  
  8176.       Batch Mode
  8177.  
  8178.       If parse() receives a single attribute specification, it checks to see
  8179.  whether it is one of the special attributes (normal, reverse, invisible, or
  8180.  blink). None of these takes an optional intensity modifier, and each can be
  8181.  handled easily by a single call to ANSI_SGR. If the lone argument is not
  8182.  one of these special attributes or if there are two or more arguments,
  8183.  parse() falls through to a loop that analyzes each argument in turn and
  8184.  creates attribute specifications in a form suitable for processing by the
  8185.  ANSI interface functions.
  8186.  
  8187.  
  8188.               ╔══════════════════════════════════════════╗
  8189.               ║                                          ║
  8190.               ║ Click to view the listing found on page ║
  8191.               ║ 368 in the printed version of the book.  ║
  8192.               ║                                          ║
  8193.               ╚══════════════════════════════════════════╝
  8194.  
  8195.  
  8196.       The basic problem we face is receiving color/attribute specifications
  8197.  as text strings and converting them to numbers. We will also need to
  8198.  account for the differences between the standard IBM attribute numbers and
  8199.  the ANSI attribute numbers described earlier. The IBM attribute numbers are
  8200.  defined in the header file ibmcolor.h, which resides in the \include\local
  8201.  directory.
  8202.  
  8203.  
  8204.               ╔══════════════════════════════════════════╗
  8205.               ║                                          ║
  8206.               ║ Click to view the listing found on page ║
  8207.               ║ 370 in the printed version of the book.  ║
  8208.               ║                                          ║
  8209.               ╚══════════════════════════════════════════╝
  8210.  
  8211.  
  8212.       The colornum() function converts text strings into these IBM color/
  8213.  attribute numbers. Given the string green as an argument, for example,
  8214.  colornum() returns the defined value IBM_GREEN, which is the number 2. Two
  8215.  synonyms for bright (bold and light) are allowed, and yellow is treated as
  8216.  a synonym for bright brown. The following is the source code for colornum():
  8217.  
  8218.  
  8219.               ╔══════════════════════════════════════════╗
  8220.               ║                                          ║
  8221.               ║ Click to view the listing found on page ║
  8222.               ║ 371 in the printed version of the book.  ║
  8223.               ║                                          ║
  8224.               ╚══════════════════════════════════════════╝
  8225.  
  8226.  
  8227.       The function uses a simple sequential search through a table of
  8228.  string/number pairs that compares the first three letters of the string
  8229.  argument with the table entries. The strlwr() standard library function
  8230.  converts the incoming argument to lowercase before any comparisons are
  8231.  made. Its return type is cast to void because the pointer to a character is
  8232.  not used. If no matching string is found, colornum() complains by returning
  8233.  -1 instead of a valid color number.
  8234.       A color number thus produced may be passed as an argument to
  8235.  setattr(), which takes a position indicator and an attribute value as
  8236.  input. It outputs an ANSI control sequence by applying the ANSI_SGR macro
  8237.  to a properly converted attribute number. The process sounds more confusing
  8238.  than it really is. First, the code for setattr():
  8239.  
  8240.  
  8241.               ╔══════════════════════════════════════════╗
  8242.               ║                                          ║
  8243.               ║ Click to view the listing found on page ║
  8244.               ║ 373 in the printed version of the book.  ║
  8245.               ║                                          ║
  8246.               ╚══════════════════════════════════════════╝
  8247.  
  8248.  
  8249.       The pos argument has type POSITION (defined in the ansi.h header
  8250.  file), and it tells setattr() which rules to apply to the attr argument.
  8251.  The attribute argument contains two kinds of information lumped together:
  8252.  The low three bits (0--2) represent the base color or video attribute, and
  8253.  bit 3 indicates the intensity.
  8254.       An intensity modifier is treated differently when it is applied to a
  8255.  foreground attribute than when it is applied to a background attribute. In
  8256.  the foreground case, bit 3 of the intensity modifier sets the foreground
  8257.  intensity bit in the attribute byte (bit 3). In the background case, it
  8258.  sets the blink bit (bit 7). If blinking is enabled (the default), setting
  8259.  the blink bit causes the foreground to blink and does not change the
  8260.  background intensity, resulting in a useful range of eight background
  8261.  colors. If blinking is disabled, setting the blink bit causes the
  8262.  background intensity to be bright. The full range of 16 background colors
  8263.  can be obtained with blinking disabled. The setattr() function calls
  8264.  ANSI_SGR once if attr is for normal intensity or twice for high
  8265.  intensity.
  8266.       A table of foreground and background attribute values in the array
  8267.  ibm2ansi[] contains the conversions from IBM attributes--represented by the
  8268.  index into the array--to the ANSI values needed by ANSI_SGR. Thus, the
  8269.  expression ibm2ansi[IBM_BLUE], where IBM_BLUE is defined in
  8270.  ibmcolor.h as the number 1, yields the value represented by ANSI_BLUE,
  8271.  which is defined in ansi.h as the number 4.
  8272.       The border, on display systems that support it, can be set to any of
  8273.  16 colors by using palette(). There is no need to separate the intensity
  8274.  information from the rest of the attribute when setting the border because
  8275.  palette() is a BIOS-based function and uses the IBM attribute values
  8276.  directly.
  8277.       That explains batch-mode processing. The other processing path that
  8278.  can be taken from main() is interactive mode, which is handled by
  8279.  menumode(). The menumode() function calls upon palette(), setattr(),
  8280.  getkey(), and a new function, sc_cmds(), to do the bulk of the work.
  8281.  
  8282.  
  8283.       Interactive Mode
  8284.  
  8285.       In interactive mode, attributes are selected by pressing function
  8286.  keys. F1 decrements the foreground color and F2 increments it. The color
  8287.  values wrap around at the boundaries; therefore, a request to increment
  8288.  foreground = 15 (white) produces foreground = 0 (black). Similarly, the
  8289.  F3/F4 pair of function keys are used to select the background attribute,
  8290.  and the F5/F6 pair cycle through border attributes.
  8291.       The mainmenu() function returns control to DOS when the Return key is
  8292.  pressed, leaving the screen cleared to the most recently selected attribute
  8293.  combination.
  8294.  
  8295.  
  8296.               ╔══════════════════════════════════════════╗
  8297.               ║                                          ║
  8298.               ║ Click to view the listing found on page ║
  8299.               ║ 375 in the printed version of the book.  ║
  8300.               ║                                          ║
  8301.               ╚══════════════════════════════════════════╝
  8302.  
  8303.  
  8304.       Because the color/attribute specifications are presented in
  8305.  interactive mode as numbers rather than as text strings, setattr() can
  8306.  process them directly. The process of updating the screen after each set of
  8307.  calls to setattr() means that the list of commands and the current status
  8308.  information must be rewritten following each keypress that affects the
  8309.  foreground or background color. This is done by the function sc_cmds(),
  8310.  which contains a static array that converts IBM color numbers into text
  8311.  strings suitable for humans. All cursor positioning is done via the
  8312.  ANSI_CUP macro. Text strings are written to the display by the standard
  8313.  library function fputs(), which is directed to send its output to
  8314.  stdout.
  8315.  
  8316.  
  8317.               ╔══════════════════════════════════════════╗
  8318.               ║                                          ║
  8319.               ║ Click to view the listing found on page ║
  8320.               ║ 377 in the printed version of the book.  ║
  8321.               ║                                          ║
  8322.               ╚══════════════════════════════════════════╝
  8323.  
  8324.  
  8325.       The file sc.mk is the makefile for SetColor. It collects several of
  8326.  the interface functions into a special-purpose library, color.lib. This is
  8327.  done to minimize the number of items that must be specified on the
  8328.  dependency and link lines that create SC.EXE and keep it up-to-date. A
  8329.  linker-response file could be used to achieve the same result. To rebuild
  8330.  the SetColor program after any changes are made to its source files or
  8331.  libraries, type
  8332.  
  8333.       make sc.mk
  8334.  
  8335.  
  8336.               ╔══════════════════════════════════════════╗
  8337.               ║                                          ║
  8338.               ║ Click to view the listing found on page ║
  8339.               ║ 378 in the printed version of the book.  ║
  8340.               ║                                          ║
  8341.               ╚══════════════════════════════════════════╝
  8342.  
  8343.  
  8344.       Using SetColor
  8345.  
  8346.       Aside from being a good demonstration of the ANSI device-driver
  8347.  interface, SetColor is a useful program. I use it most frequently in batch
  8348.  files to restore the screen to match my peculiar tastes after some other
  8349.  program has made a mess of things. Most programs, especially those that do
  8350.  full-screen operations, have built-in default settings for video
  8351.  attributes. These programs have no idea what attributes were in use before
  8352.  they were run; therefore, when they exit, they either leave the attributes
  8353.  as they are when the program terminates or set them to white on black.
  8354.       It would be better to have the colors set to the user's preferences,
  8355.  if possible. This can be achieved in a couple of ways. The first way uses a
  8356.  DOS batch file to intercept the call to a program. The batch file calls the
  8357.  real program and then calls SetColor to make things right again. For
  8358.  example, assume the program in BADPROG.EXE does bad things to the screen
  8359.  when it exits. A batch file called BADPROG.BAT with the lines
  8360.  
  8361.       badprog.exe
  8362.       sc white blue blue
  8363.  
  8364.  will clear the screen and set the foreground to white on a field of blue
  8365.  when the program returns to DOS.
  8366.       This can be generalized further by the use of DOS environment
  8367.  variables. If environment variables having the names FGND, BKGND, and
  8368.  BORDER are defined, the batch file can access the values and control the
  8369.  SetColor program automatically. DOS variable values are obtained in batch
  8370.  files by surrounding each variable name with percent signs. The revised
  8371.  batch file looks like this:
  8372.  
  8373.       badprog.exe
  8374.       sc %fgnd% %bkgnd% %border%
  8375.  
  8376.       Of course, SetColor may be called from the DOS command line with
  8377.  literal attribute specifications. I sometimes change the attributes just to
  8378.  minimize eyestrain during long programming and writing sessions. It's
  8379.  remarkable what a good effect a simple change in screen colors can have
  8380.  (unless it's to something like magenta on red!).
  8381.  
  8382.  
  8383.       A User-Attribute Function
  8384.  
  8385.       Here's a way for programmers to be kind to those who will be using
  8386.  their programs. The function userattr() that follows can be used in
  8387.  programs to restore the user's attribute preferences just before the return
  8388.  to DOS. It gets values directly from the DOS environment, just as the batch
  8389.  file in the previous section does. If no attribute variables are defined,
  8390.  userattr() uses some reasonable defaults as parameters. The userattr()
  8391.  function calls on colornum() and setattr() and uses the standard library
  8392.  functions getenv() and strtok() to look for user preferences and parse them
  8393.  into recognizable values.
  8394.  
  8395.  
  8396.               ╔══════════════════════════════════════════╗
  8397.               ║                                          ║
  8398.               ║ Click to view the listing found on page ║
  8399.               ║ 380 in the printed version of the book.  ║
  8400.               ║                                          ║
  8401.               ╚══════════════════════════════════════════╝
  8402.  
  8403.  
  8404.       The processing of the strings by strtok() is necessary because the
  8405.  attribute specifications may contain an intensity modifier. The strtok()
  8406.  function returns a pointer to a null-terminated string token taken from its
  8407.  first string argument; if no tokens are found, it returns NULL. Acceptable
  8408.  token separators are spaces and tabs, in any combination. Subsequent calls
  8409.  to strtok() use NULL as the first argument to request further searching in
  8410.  the string given in argument one of the first call. If the first call to
  8411.  strtok() retrieves an intensity modifier, a second call is made to strtok()
  8412.  on the same string to get the second token, which should be a valid
  8413.  attribute specifier. Error-checking code prevents bad attribute names from
  8414.  causing problems.
  8415.       The next time someone tells you the ANSI.SYS device driver is a
  8416.  worthless piece of software, you can show them how useful it really
  8417.  is!
  8418.       As a fitting end to this development session, echo ESC[2J (look it
  8419.  up).
  8420.  
  8421.  
  8422.  
  8423.  Chapter 14  Viewing Files
  8424.  
  8425.  
  8426.  
  8427.       The next program to be added to our kit of tools is a file viewer.
  8428.  ViewFile, called VF by its close friends and associates, is such a
  8429.  tool.
  8430.       The design for VF has two objectives: to obtain a convenient window
  8431.  into a file that permits us to look at the file without harming it and to
  8432.  experiment with some programming techniques that we can apply to other
  8433.  tasks. We will apply the linked-list techniques to the management of an in-
  8434.  memory text buffer. In addition, we will use the standard library function
  8435.  strncmp() to implement a simple but fast search capability.
  8436.       Some trade-offs made in VF's design are noteworthy. First, we
  8437.  sacrifice loading speed to obtain very quick response to user commands. By
  8438.  using an internal buffer, instead of buffering to disk, we provide speedy
  8439.  response to positioning requests but limit files to a size determined by
  8440.  the available memory of the host machine. Fortunately, most PCs these days
  8441.  have lots of memory. Second, VF uses some of the BIOS routines described in
  8442.  Chapter 5 to handle display interactions. It does not use the buffered
  8443.  screen interface described in Chapters 11 and 12. Even so, the display
  8444.  updates are tolerably quick, taking about a second or less for most
  8445.  operations on a standard PC for a typical file.
  8446.  
  8447.  
  8448.  ViewFile (VF) Features
  8449.  
  8450.       The features of ViewFile are summarized in the manual page below
  8451.  (Figure 14-1).
  8452.  
  8453.  
  8454.               ╔══════════════════════════════════════════╗
  8455.               ║                                          ║
  8456.               ║ Click to view the figure found on page  ║
  8457.               ║ 384 in the printed version of the book.  ║
  8458.               ║                                          ║
  8459.               ╚══════════════════════════════════════════╝
  8460.  
  8461.       FIGURE 14-1 ▐ Manual page for ViewFile
  8462.  
  8463.  
  8464.       In addition to offering vertical scrolling by screen pages, VF
  8465.  provides convenient horizontal scrolling as a way of dealing with long
  8466.  lines. VF accepts lines up to MAXLINE characters (defined as 256 in our
  8467.  std.h header file) including the null-byte terminator. You can make this
  8468.  value larger if you wish. The horizontal scrolling feature lets you move
  8469.  right and left, 20 columns at a time. The horizontal offset is maintained
  8470.  when vertical movements are made.
  8471.       If a list of files is presented to VF, the program will view the files
  8472.  sequentially. Pressing the Escape key tells VF to read the next file in the
  8473.  list, if any. To quit without viewing any remaining files in the list, type
  8474.  Q instead. DOS wildcards may be used to specify ambiguous file names.
  8475.  Therefore, the command
  8476.  
  8477.       vf \src\*.h
  8478.  
  8479.  forms a list consisting of all header files in the \src directory on the
  8480.  current disk drive. VF then reads in the first matching file and displays
  8481.  the first screen page of its contents.
  8482.       The forward and reverse search feature looks through each line in the
  8483.  buffer for a literal string. VF searches around the end of the buffer,
  8484.  stopping at the first line that contains a match, or returning to the
  8485.  current line if no match is found. Searches in either direction may be
  8486.  repeated by pressing a carriage return in response to the "Search for:"
  8487.  prompt.
  8488.       VF has a built-in help "frame." When a user types a question mark,
  8489.  Alt-h, or H, VF overlays the help frame on the text-display area. The next
  8490.  keypress restores the covered text. Although it is only a brief memory-
  8491.  jogger, the help frame contains all the information needed to use VF
  8492.  successfully.
  8493.       VF should be compiled using the compact memory model so that it can
  8494.  handle large files. Compact model programs are distinguished by small code
  8495.  space (less than 64 KB) and large data space (whatever available memory
  8496.  permits). Files that exceed the capacity of VF produce the message "File
  8497.  too big to load." To use the compact memory model, you will have to create
  8498.  compact-model versions of the bios, dos, and util libraries by recompiling
  8499.  all functions with the -AC option. It is probably best to follow the
  8500.  pattern established by Microsoft of having separate library files for each
  8501.  version of a given library. Thus, for example, we might have the files
  8502.  bios.lib, cbios.lib, lbios.lib, and so on. The linker gets clues from the
  8503.  object files, so it will use the correct versions of the standard
  8504.  libraries. The makefile, vf.mk, contains the instructions required by MAKE.
  8505.  The library of object modules used to construct VF is maintained by vf.mk,
  8506.  which enables us to bypass the command-line length limitation imposed by
  8507.  DOS.
  8508.  
  8509.  
  8510.               ╔══════════════════════════════════════════╗
  8511.               ║                                          ║
  8512.               ║ Click to view the listing found on page ║
  8513.               ║ 386 in the printed version of the book.  ║
  8514.               ║                                          ║
  8515.               ╚══════════════════════════════════════════╝
  8516.  
  8517.  
  8518.       The initialization file tools.ini in the VF directory instructs MAKE
  8519.  to verify the number and types of function arguments and to use the compact
  8520.  memory model. Renaming or deleting this file causes the default small-model
  8521.  tools.ini file to be read for inference rules.
  8522.  
  8523.    # inference rules for VF program make
  8524.    # enable argument checking and compact memory model
  8525.  
  8526.    [make]
  8527.    .c.obj:
  8528.            msc -DLINT_ARGS -AC $*;
  8529.  
  8530.  
  8531.  Implementation Details
  8532.  
  8533.       VF is a modest-sized program. Some of its general-purpose functions
  8534.  are available to other programs.
  8535.       VF uses an in-memory text buffer. The decision to use an in-memory
  8536.  buffer has a profound impact on how a few of the functions are designed,
  8537.  but virtually no impact on the rest. We should try to keep the number of
  8538.  functions that actually know how the text is stored to a minimum; this
  8539.  permits us to apply them to a wider range of problems. The functions in the
  8540.  message module message.c, for example, don't know anything about the text
  8541.  buffer. Therefore, these message functions can be used unchanged in any
  8542.  program that uses the BIOS video routines.
  8543.       The functions in vf.c are also ignorant of the buffer mechanism. The
  8544.  main() function does the customary work of preserving the user's
  8545.  preferences for display type, video attributes, and cursor type. It also
  8546.  aborts the program if the operating environment is a DOS version earlier
  8547.  than 2.00, because VF is designed to accept pathnames in file
  8548.  specifications.
  8549.       The main() function also clears the screen and sets up the basic
  8550.  display appearance for the VF session. All program and status information,
  8551.  user input, and feedback messages are confined to the top two lines of the
  8552.  screen. The remaining 23 lines are used to display text and optional file-
  8553.  relative line numbers. When help is requested, the help frame is
  8554.  temporarily overlaid on a rectangular portion of the text display area.
  8555.       Because the cursor is turned off by VF (except during user input
  8556.  operations), it is necessary to restore its shape when the program ends.
  8557.  The clean() function in vf.c is called when the program terminates normally
  8558.  or when some condition (out of memory, for example) causes an abnormal
  8559.  termination. The user's original video attribute selection is restored by
  8560.  clean(), and the cursor is then sent to the home position.
  8561.  
  8562.  
  8563.               ╔══════════════════════════════════════════╗
  8564.               ║                                          ║
  8565.               ║ Click to view the listing found on page ║
  8566.               ║ 388 in the printed version of the book.  ║
  8567.               ║                                          ║
  8568.               ╚══════════════════════════════════════════╝
  8569.  
  8570.  
  8571.       A set of manifest constants that controls placement of visual elements
  8572.  of the VF display, search direction, and the data structure that defines
  8573.  the text buffer are contained in vf.h. The buffer data structure and its
  8574.  application will be described in detail later. The constants determine
  8575.  where the program name and messages to the user appear, which screen line
  8576.  is the "header" row, and how many screen rows of text display and scrolling
  8577.  overlap are to be used. To simplify VF a bit, we do not add automatic
  8578.  configuration to the program. The information and samples in Chapters 7
  8579.  and 9 give detailed information about configuring programs.
  8580.  
  8581.  
  8582.               ╔══════════════════════════════════════════╗
  8583.               ║                                          ║
  8584.               ║ Click to view the listing found on page ║
  8585.               ║ 391 in the printed version of the book.  ║
  8586.               ║                                          ║
  8587.               ╚══════════════════════════════════════════╝
  8588.  
  8589.  
  8590.       For each file named on the command line (either literally or
  8591.  ambiguously), main() calls vf_cmd(), the ViewFile command module. The
  8592.  vf_cmd.c file, which contains the source for vf_cmd() and for the auxiliary
  8593.  function prtshift(), is a large file but it does only a few jobs--all
  8594.  related to controlling and accessing the text buffer. Chief among these
  8595.  jobs is setting up the text buffer for the file that is about to be read.
  8596.  Another major job is handled by the big switch statement within the while
  8597.  loop in vf_cmd(), which accepts keypresses as commands. Simple positioning
  8598.  commands are handled directly by vf_cmd(). Commands that require user input
  8599.  are handled by separate functions.
  8600.  
  8601.  
  8602.               ╔══════════════════════════════════════════╗
  8603.               ║                                          ║
  8604.               ║ Click to view the listing found on page ║
  8605.               ║ 392 in the printed version of the book.  ║
  8606.               ║                                          ║
  8607.               ╚══════════════════════════════════════════╝
  8608.  
  8609.  
  8610.       Within vf_cmd() there are several calls on putstr(). This is a BIOS-
  8611.  based function that displays a string in the prevailing video attribute on
  8612.  a specified screen page. It advances the cursor by the number of characters
  8613.  written. (We could also use the BIOS writetty() function to help us with
  8614.  this task, but writetty() disturbs the video attribute, setting it to the
  8615.  default, which may not be what we want.)
  8616.  
  8617.  
  8618.               ╔══════════════════════════════════════════╗
  8619.               ║                                          ║
  8620.               ║ Click to view the listing found on page ║
  8621.               ║ 399 in the printed version of the book.  ║
  8622.               ║                                          ║
  8623.               ╚══════════════════════════════════════════╝
  8624.  
  8625.  
  8626.       As noted earlier, the vf.h header file contains the definition of the
  8627.  buffer node structure. In addition to having forward and backward pointers
  8628.  to other nodes, each node contains a pointer to a character string, a file-
  8629.  relative line number, and a "flag" word. The flag word is not used in this
  8630.  implementation of VF, but it could be used to mark lines for highlighting.
  8631.       The text buffer management functions are in vf.list.c:
  8632.  
  8633.  
  8634.               ╔══════════════════════════════════════════╗
  8635.               ║                                          ║
  8636.               ║ Click to view the listing found on page ║
  8637.               ║ 399 in the printed version of the book.  ║
  8638.               ║                                          ║
  8639.               ╚══════════════════════════════════════════╝
  8640.  
  8641.  
  8642.       The text buffer used by VF consists of three parts: a group of
  8643.  "control nodes" that form a doubly linked list, pointers to the buffer
  8644.  "head" and the "current" position, and independent character strings for
  8645.  each line in the file being viewed. The node pointed to by head is a dummy
  8646.  node that is used to ease the work necessary to set up and maintain the
  8647.  list. Figure 14-2 depicts the text buffer. Note that the control nodes
  8648.  are in a circular list, which makes searching across the buffer
  8649.  boundaries easy. It takes the same amount of time to move backward in
  8650.  the buffer as it does to move forward, because of the use of a doubly
  8651.  linked list. Most other text buffer designs would exact a rather stiff time
  8652.  penalty for scrolling backward in a file.
  8653.       Figure 14-3 shows another aspect of the text buffer-allocation
  8654.  scheme used by VF. An initial pool of 256 "free" nodes is allocated by a
  8655.  call to vf_alloc(); the free nodes are controlled by the symbolic
  8656.  constant N_NODES. The list-header node is established by vf_mklst().
  8657.  Both vf_mklst() and vf_alloc() call the library function malloc()
  8658.  to obtain needed storage.
  8659.  
  8660.  
  8661.               control nodes
  8662.                     │
  8663.               ▒▒▒▒▒▒▒▒▒▒▒▒▒▒                 text string buffers
  8664.  ┌─head ┌────────┐                                   │
  8665.  │      │ ┌───┐  │               ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
  8666.  │┌──┐  │ │  ┌─┬┼─┬─────┬──┐
  8667.  └│■─┼──┼─┼─│■ │■ │     │■ │
  8668.   └──┘  │ │  └┼─┴─┴─────┴──┘
  8669.   ┌──┐  │ │  ┌─┬┼─┬─────┬──┐   ┌───────────────────────────────────────┐
  8670.  ┌┤■─┼──┼─┼─│■ │■ │     │■─┼──│                                       │
  8671.  │└──┘  │ │  └┼─┴─┴─────┴──┘   └───────────────────────────────────────┘
  8672.  │      │ │  ┌─┬┼─┬─────┬──┐   ┌──────────────────────────────┐
  8673.  └─cur- │ │  │■ │■ │     │■─┼──│                              │
  8674.    rent │ │  └┼─┴─┴─────┴──┘   └──────────────────────────────┘
  8675.         │ │  ┌─┬┼─┬─────┬──┐   ┌──────────────────────────────────────────┐
  8676.         │ │  │■ │■ │     │■─┼──│                                          │
  8677.         │ │  └┼─┴─┴─────┴──┘   └──────────────────────────────────────────┘
  8678.         | |   |  |
  8679.         │ │  ┌─┬┼─┬─────┬──┐   ┌────────────────────────────┐
  8680.         │ │  │■ │■ │     │■─┼──│                            │
  8681.         │ │  └┼─┴─┴─────┴──┘   └────────────────────────────┘
  8682.         │ └───┘  │
  8683.         └────────┘
  8684.  
  8685.       FIGURE 14-2 ▐ Text buffer management
  8686.  
  8687.  
  8688.               ┌────────┐
  8689.               │ ┌───┐  │
  8690.          ┌──┐ │ │  ┌─┬┼─┬────────┐
  8691.      head│■─┼─┼─┼─│■ │■ │        │ Inserting a node into the list:
  8692.          └──┘ │ │  └┼─┴─┴────────┘
  8693.               │ │  ┌─┬┼─┬────────┐ if (insertion will exhaust the
  8694.               │ │  │■ │■ │        │     freelist)
  8695.               │ │  └┼─┴─┴────────┘ extract first node from the freelist
  8696.               │ │  ┌─┬┼─┬────────┐ insert the node after the "current" node
  8697.               │ │  │■ │■ │        │ return a pointer to the next free node
  8698.               │ │  └┼─┴─┴────────┘
  8699.               │ │  ┌─┬┼─┬────────┐
  8700.               │ │  │■ │■ │        │
  8701.               │ │  └┼─┴─┴────────┘
  8702.          ┌──┐ │ │  ┌─┬┼─┬────────┐
  8703.   current│■─┼─┼─┼─│■ │■ │        │
  8704.          └──┘ │ │  └┼─┴─┴────────┘
  8705.               │ └───┘  │
  8706.               └────────┘ ───────────────┐
  8707.                                          │
  8708.          ┌──┐      ┌──┬──┬────────┐      │
  8709.  freelist│■─┼─────│■ │■ │        │ ────┘
  8710.          └──┘      └┼─┴──┴────────┘
  8711.                    ┌─┬──┬────────┐
  8712.                    │■ │■ │        │
  8713.                    └┼─┴──┴────────┘
  8714.                    ┌─┬──┬────────┐
  8715.                    │■ │■ │        │
  8716.                    └┼─┴──┴────────┘
  8717.                    ┌─┬──┬────────┐
  8718.                    │■ │■ │        │
  8719.                    └──┴──┴────────┘
  8720.                     |
  8721.                     |
  8722.                    ┌─┬──┬────────┐
  8723.                    │■ │■ │        │
  8724.                    └──┴──┴────────┘
  8725.  
  8726.       FIGURE 14-3 ▐ Buffer-allocation scheme
  8727.  
  8728.  
  8729.       The header-node pointers are both initially set to point to the header
  8730.  node itself. This constitutes the empty list. The freelist pointer points
  8731.  to the chained free nodes. Only the "next" pointers are used for chaining,
  8732.  and the last free node contains a NULL next pointer. Each line of text is
  8733.  effectively added to the buffer by the function vf_ins(), which inserts a
  8734.  free node into the list after the position of the current node. When the
  8735.  supply of free nodes is about to be exhausted, another block of nodes is
  8736.  allocated and added to the chain. This process continues as long as there
  8737.  are more lines to read from the file and there is enough memory to satisfy
  8738.  the allocation requests.
  8739.       Looking at vf_cmd.c, we see that after a new node has been inserted
  8740.  into the list by vf_ins(), there is still more work to be done. The
  8741.  Microsoft C standard library function strdup() is used to duplicate the
  8742.  text of the most recently read line from the file. This function also calls
  8743.  malloc() to obtain an array of characters that is large enough to
  8744.  hold a copy of the received string, including the terminating null
  8745.  byte.
  8746.  
  8747.                                COMMENT
  8748.            Since strdup() is not a UNIX System V library function,
  8749.            many C compilers may not have it or an equivalent
  8750.            function in their run-time support libraries. You can
  8751.            easily mimic the function using malloc(), strlen(), and
  8752.            strcpy():
  8753.  
  8754.            #include <stdio.h>
  8755.            #include <malloc.h>
  8756.            #include <string.h>
  8757.  
  8758.            char *
  8759.            strdup(str)
  8760.            char *str;
  8761.            {
  8762.                    char *buf;
  8763.  
  8764.                    buf = malloc(strlen(str) + 1);
  8765.                    return (buf == NULL ? buf : strcpy(buf, str));
  8766.            }
  8767.  
  8768.       We are careful to check for error returns that let us know if memory
  8769.  is exhausted. VF simply cleans up and quits with an error message if it
  8770.  runs out of memory.
  8771.       The function vf_del() takes a node out of the list and puts it back on
  8772.  the freelist. We call vf_del(), and free(), a standard library function,
  8773.  when the command loop is terminated by a "next file" (Escape) command. This
  8774.  frees the memory used by the current file, leaving the largest possible
  8775.  amount for the next file in the list. If we do not free the memory, we
  8776.  might eventually receive a "File too big to load" error message.
  8777.       After any command that affects the current buffer position, vf_dspy()
  8778.  is called to update the text display area of the screen. The version of
  8779.  vf_dspy() presented here uses the bios library functions that we
  8780.  constructed in Chapter 5 to position the cursor, control display
  8781.  attributes, and display text and optional line numbers. I elected to use
  8782.  global variables only for the video subsystem based on the BIOS interface.
  8783.  The Boolean variable numbers defaults to FALSE but may be set to TRUE
  8784.  for the duration of a VF session by using the command-line option -n. The
  8785.  numbers variable controls the displaying of line numbers. The initial
  8786.  setting of numbers is passed as an argument from main(), relayed through
  8787.  vf_cmd(), to vf_dspy(). The user may toggle line-numbering on and off while
  8788.  viewing a given file, but the state of line-numbering goes back to the
  8789.  initial setting when the next file from the list is brought into the
  8790.  viewing buffer.
  8791.  
  8792.  
  8793.               ╔══════════════════════════════════════════╗
  8794.               ║                                          ║
  8795.               ║ Click to view the listing found on page ║
  8796.               ║ 405 in the printed version of the book.  ║
  8797.               ║                                          ║
  8798.               ╚══════════════════════════════════════════╝
  8799.  
  8800.  
  8801.       The vf_dspy() function updates the text-display area by calling
  8802.  putfld(), which outputs a string of characters to a field starting at the
  8803.  current cursor position. The putfld() function uses a compression technique
  8804.  that is made possible by BIOS. All sequences of repeated characters are
  8805.  output by a single call to writec(). This technique reduces the time needed
  8806.  to update a screen that contains, for example, the large amounts of white
  8807.  space found in C and Pascal source files. Assembly-language source
  8808.  listings, on the other hand, usually do not benefit from this compression
  8809.  technique because they contain long lines and fewer repeated character
  8810.  sequences.
  8811.  
  8812.  
  8813.               ╔══════════════════════════════════════════╗
  8814.               ║                                          ║
  8815.               ║ Click to view the listing found on page ║
  8816.               ║ 407 in the printed version of the book.  ║
  8817.               ║                                          ║
  8818.               ╚══════════════════════════════════════════╝
  8819.  
  8820.  
  8821.       This function becomes part of the bios library described in
  8822.  Chapter 5. It can be used by any program that uses BIOS to access the PC
  8823.  screen.
  8824.       We have to watch out for the boundary conditions imposed by physical
  8825.  realities. When there are not enough lines left in the buffer to fill the
  8826.  screen, vf_dspy() must be careful not to leave residue from previous
  8827.  displays. After displaying the last line of the text buffer, vf_dspy()
  8828.  clears each remaining screen line and deposits a tilde in the leftmost
  8829.  column to show that the line is an unused screen line, as opposed to an
  8830.  empty buffer line.
  8831.       Two utility functions that are specific to VF reside in vf_util.c. The
  8832.  first is gotoln(), which implements the absolute go-to-line feature. This
  8833.  function prompts the user for a line number. If the number is within the
  8834.  range of lines in the buffer, the current position is updated and the line
  8835.  sought by the user is brought to the top of the text display area.
  8836.       The second function is called showhelp(). To assist the user with a
  8837.  brief memory-jogger of command names and actions, showhelp() displays the
  8838.  essence of the manual page at the top of the text display area. When the
  8839.  user has finished reading the help frame, a keypress restores the screen to
  8840.  its former condition by rewriting all of the text lines.
  8841.  
  8842.  
  8843.               ╔══════════════════════════════════════════╗
  8844.               ║                                          ║
  8845.               ║ Click to view the listing found on page ║
  8846.               ║ 408 in the printed version of the book.  ║
  8847.               ║                                          ║
  8848.               ╚══════════════════════════════════════════╝
  8849.  
  8850.  
  8851.       The task of gathering the user's input for the gotoln() function falls
  8852.  to getstr(), a function that is constructed from several of the dos and
  8853.  bios library functions presented in Chapter 5. The getstr() function takes
  8854.  buffer and width parameters as arguments. It echoes keystrokes as it
  8855.  receives them and interprets a few keys as commands rather than as input.
  8856.  The user cancels input by pressing the Escape key, and signals the end of
  8857.  input with a carriage return. Errors may be corrected by pressing Ctrl-H
  8858.  (backspace). All input is in the form of character strings. If a program
  8859.  requires numeric input, such as a line number, it must convert the string
  8860.  to numeric form before using it.
  8861.  
  8862.  
  8863.               ╔══════════════════════════════════════════╗
  8864.               ║                                          ║
  8865.               ║ Click to view the listing found on page ║
  8866.               ║ 411 in the printed version of the book.  ║
  8867.               ║                                          ║
  8868.               ╚══════════════════════════════════════════╝
  8869.  
  8870.  
  8871.       Notice that getstr() is actually a simplified version of the
  8872.  getreply() function that we used in Chapter 6. We don't need a sliding
  8873.  window or other fancy gimmicks, so we took out all the superfluous
  8874.  windowing and editing features, made the prompting external, and eliminated
  8875.  the response stack. What's left is a simple but effective function that
  8876.  collects a string from the keyboard. Two boundary conditions are addressed:
  8877.  First, we don't allow the user to backspace beyond the left column of the
  8878.  response field, and second, we return the response immediately if the user
  8879.  types past the right column of the response field. You may prefer to
  8880.  modify the latter condition to require an explicit carriage return before
  8881.  returning the response.
  8882.       Additional assistance is provided to the user via the message line on
  8883.  the right half of the top screen line. The simple message-line manager
  8884.  consists of three functions contained in message.c. The function initmsg()
  8885.  establishes the message display area by filling in the values called for by
  8886.  the MESSAGE data structure defined in the message.h header file. The
  8887.  message line can be placed anywhere on the screen by changing the values
  8888.  presented to initmsg(). The showmsg() function displays a message, and
  8889.  clrmsg() restores the message area to its pristine state. A flag in the
  8890.  message structure is set when a message is displayed and is reset when the
  8891.  message area is cleared. If a call is made to clrmsg() and the flag is not
  8892.  set, no action is taken.
  8893.  
  8894.    /*
  8895.     *      message.h -- header for message-line manager
  8896.     */
  8897.  
  8898.    typedef struct msg_st {
  8899.            int m_row;
  8900.            int m_col;
  8901.            int m_wid;
  8902.            int m_pg;
  8903.            int m_flag;
  8904.            unsigned char m_attr;
  8905.    } MESSAGE;
  8906.  
  8907.  
  8908.  
  8909.               ╔══════════════════════════════════════════╗
  8910.               ║                                          ║
  8911.               ║ Click to view the listing found on page ║
  8912.               ║ 413 in the printed version of the book.  ║
  8913.               ║                                          ║
  8914.               ╚══════════════════════════════════════════╝
  8915.  
  8916.  
  8917.       The message-line manager functions are based on bios library routines.
  8918.  They can be generalized to use either standard library routines or the
  8919.  screen-buffer routines described in Chapters 11 and 12, which permit
  8920.  their use in programs that do not use the BIOS interface.
  8921.       A simple but useful search feature is implemented by the functions in
  8922.  vf_srch.c. The getsstr() function prompts for a search string and reads it
  8923.  using getstr(). The string must be literal in this implementation. The
  8924.  string is presented to search(), which tries to find a line in the
  8925.  specified search direction that contains a match for the search string.
  8926.  Searching starts on the line following the current line (or the preceding
  8927.  line for a reverse search) and continues until a match is found or until
  8928.  search() returns to the current line. The search is circular because of the
  8929.  way the buffer is implemented.
  8930.  
  8931.  
  8932.               ╔══════════════════════════════════════════╗
  8933.               ║                                          ║
  8934.               ║ Click to view the listing found on page ║
  8935.               ║ 415 in the printed version of the book.  ║
  8936.               ║                                          ║
  8937.               ╚══════════════════════════════════════════╝
  8938.  
  8939.  
  8940.   /*   nlerase -- replace the first newline in a string
  8941.    *   with a null character  */
  8942.  
  8943.   char *nlerase(s)
  8944.   char *s;
  8945.   {
  8946.           register char *cp;
  8947.  
  8948.        cp = s;
  8949.        while (*cp != '\n' && *cp != '\0')
  8950.                ++cp;
  8951.        *cp = '\0';
  8952.  
  8953.        return (s);
  8954.  }
  8955.  
  8956.  
  8957.               ╔══════════════════════════════════════════╗
  8958.               ║                                          ║
  8959.               ║ Click to view the listing found on page ║
  8960.               ║ 417 in the printed version of the book.  ║
  8961.               ║                                          ║
  8962.               ╚══════════════════════════════════════════╝
  8963.  
  8964.  
  8965.       Figure 14-4 shows VF in operation.
  8966.  
  8967.  
  8968.  ViewFile/1.0  H=Help Q=Quit Esc=Next
  8969.  File: vf.c (126 lines)
  8970.  /*
  8971.   *      vf -- view a file using a full-screen window onto
  8972.   *      ┌──────────────────────────────────────────────────────────┐
  8973.   */     │ PgUp (U)        Scroll up the file one screen page       │
  8974.          │ PgDn (D)        Scroll down the file one screen page     │
  8975.  #include│ Up arrow (-)    Scroll up in the file one line           │
  8976.  #include│ Down arrow (+)  Scroll down in the file one line         │
  8977.  #include│ Right arrow (>) Scroll right by 20 columns               │
  8978.  #include│ Left arrow (<)  Scroll left by 20 columns                │
  8979.  #include│ Home (B)        Go to beginning of file buffer           │
  8980.  #include│ End (E)         Go to end of file buffer                 │
  8981.          │ Alt-g (G)       Go to a specified line in the buffer     │
  8982.  extern i│ Alt-h (H or ?)  Display this help frame                  │
  8983.  int Star│ Alt-n (N)       Toggle line-numbering feature            │
  8984.  unsigned│ \ (R)           Reverse search for a literal text string │
  8985.  unsigned│ / (S)           Search forward for a literal text string │
  8986.  unsigned│ Esc             Next file from list (quits if none)      │
  8987.          │ Alt-q (Q)       Quit                                     │
  8988.  main(arg│ -------------------------------------------------------- │
  8989.  int argc│             << Press a key to continue >>                │
  8990.  char **a└──────────────────────────────────────────────────────────┘
  8991.  {
  8992.          int ch;
  8993.  
  8994.       FIGURE 14-4 ▐ The ViewFile program with the help frame displayed
  8995.  
  8996.  
  8997.  Considerations and Alternatives
  8998.  
  8999.       If you need to view very large files, the use of external storage is
  9000.  the best alternative. A theoretical maximum file size of 32 megabytes can
  9001.  be achieved under current versions of DOS, but some of that space will
  9002.  probably be needed to manage the data storage.
  9003.       With external storage, a file would be loaded from disk faster than by
  9004.  the current VF because only a small piece of the file need be in memory at
  9005.  one time. However, it's likely that there would be a significant increase
  9006.  in the time needed to access a particular line in the file because the disk
  9007.  must be accessed and read for each new portion of the file that is to be
  9008.  displayed.
  9009.       An area of the VF design that could use some enhancement is the search
  9010.  feature. Although literal-string searches satisfy most positioning
  9011.  requirements, a regular-expression capability can be extremely handy.
  9012.  Regular expressions are, in essence, text formulas that can be used to form
  9013.  patterns rather than fixed strings.
  9014.       Another option to consider is an incremental search. As the user types
  9015.  characters, the program accumulates them in the search pattern and moves
  9016.  immediately to a string in the text, if there is one, that matches the
  9017.  accumulated search string. The advantage of the incremental search is that
  9018.  the user need only type enough characters to get the match. You would need
  9019.  to provide an "escape" command to terminate the search when the desired
  9020.  string is found.
  9021.       The implementation of VF presented in this chapter wraps around the
  9022.  buffer boundaries. In most cases this action is appropriate, but not
  9023.  always. It may be useful to have a "no wrap" option that would terminate
  9024.  the search at the end (or beginning) of the buffer to avoid unwanted
  9025.  repositioning.
  9026.  
  9027.  
  9028.  
  9029.  SECTION V  Appendixes
  9030.  
  9031.  
  9032.  
  9033.  Appendix A  Microsoft C Compiler, Version 4.00
  9034.  
  9035.  
  9036.  
  9037.       The current version of the Microsoft C Compiler at the time of this
  9038.  writing is version 4.00. The compiler is noted for producing very fast and
  9039.  compact executable program files. It trades compile time for a high degree
  9040.  of optimization and produces object code that is arguably in the same
  9041.  league as that produced by expert assembly-language programmers. "Hand
  9042.  tuning" to improve performance is virtually unnecessary.
  9043.  
  9044.  
  9045.  Technical Highlights
  9046.  
  9047.       The following material presents a general overview of the Microsoft C
  9048.  Compiler, version 4.00. The compiler system encompasses a three-pass
  9049.  compiler, extensive runtime library support, and a set of development
  9050.  tools. See Chapter 1 for additional information about the compiler, the
  9051.  impact of the proposed ANSI standard for C, and information about DOS and C
  9052.  compiler programs that assist programmers in the development of
  9053.  programs.
  9054.       Two compiler drivers are available with the Microsoft C Compiler. The
  9055.  MSC driver executes the preprocessor and compiler passes. The CL driver is
  9056.  similar to the CC driver on XENIX and UNIX, which compiles and links
  9057.  programs automatically. Either driver can take advantage of a variety of
  9058.  compile-time options.
  9059.  
  9060.  
  9061.       Program Size
  9062.  
  9063.       The Microsoft C Compiler now provides five distinct memory models--
  9064.  small, medium, compact, large, and huge--plus extensions to produce mixed
  9065.  models.
  9066.  
  9067.         The small model generates tight, efficient code for programs with
  9068.          up to 64 KB of code and up to 64 KB of data.
  9069.  
  9070.         The medium model supports programs with up to 1 MB of code but only
  9071.          64 KB of data.
  9072.  
  9073.         The compact model handles programs with up to 64 KB of code and up
  9074.          to 1 MB of data.
  9075.  
  9076.         The large model extends program space to 1 MB of code with 1 MB of
  9077.          data.
  9078.  
  9079.         The huge model is similar to the large model, but allows individual
  9080.          arrays to be larger than 64 KB.
  9081.  
  9082.       Mixed memory models are made possible through the use of near, far,
  9083.  and huge pointers. These pointers change the addressing conventions for one
  9084.  or more elements without changing them for the program as a whole.
  9085.  
  9086.  
  9087.       Overlays
  9088.  
  9089.       The Microsoft C Compiler single-level overlay linker allows memory
  9090.  space to be shared in turn by several program modules. Overlays may be
  9091.  called from the "root" program or from another overlay and are
  9092.  automatically loaded when needed.
  9093.  
  9094.  
  9095.       Multiple Math Libraries
  9096.  
  9097.       The Microsoft C Compiler offers several options for floating-point
  9098.  operations. If an 8087/80287 math coprocessor is available, the smallest
  9099.  and fastest floating-point library can be used. And even though a math
  9100.  coprocessor may not be present, identical math results can still be
  9101.  obtained from an emulator.
  9102.       This versatility gives programs the same high degree of precision
  9103.  whether or not a math coprocessor is available. For maximum speed, the
  9104.  emulator routines automatically use the math coprocessor if it is
  9105.  installed.
  9106.       When full 80-bit precision and consistency with the 8087/80287 math
  9107.  coprocessor are not required, the alternate math package can be selected.
  9108.  Supporting a compatible interface for single- and double-precision IEEE
  9109.  numbers, this math library is optimized for speed.
  9110.  
  9111.  
  9112.       Network File Sharing
  9113.  
  9114.       Under MS-DOS version 3.1 or higher with Microsoft Networks version
  9115.  1.0, or under IBM PC Network, the Microsoft C Compiler supports multi-user
  9116.  network access with both file and record locking.
  9117.  
  9118.  
  9119.       Direct Interlanguage Calls
  9120.  
  9121.       Routines written in the Microsoft C Compiler (version 3.00 or higher),
  9122.  Microsoft FORTRAN (version 3.30 or higher), Microsoft Pascal (version 3.30
  9123.  or higher), or Microsoft Macro Assembler can be linked together. So you can
  9124.  share the investment you've made in developing libraries in a different
  9125.  language.
  9126.       This interlanguage calling lets you use the best features of each
  9127.  language, or upgrade to a new language while retaining libraries written in
  9128.  the old one. Selected routines can be recoded as needed.
  9129.  
  9130.  
  9131.       Library Manager
  9132.  
  9133.       Entire program-development libraries can be built in any mix of C,
  9134.  Pascal, FORTRAN, and assembly language by using the library manager that's
  9135.  included with the Microsoft C Compiler.
  9136.       Once a library is constructed, the library subroutines can be called
  9137.  directly from your C program. Subroutine calls are resolved when the
  9138.  finished program is linked.
  9139.  
  9140.  
  9141.       Utility Programs
  9142.  
  9143.       The Microsoft C Compiler includes several utility programs; two are
  9144.  used to increase the efficiency of .EXE files:
  9145.  
  9146.         EXEPACK removes null characters from .EXE files and optimizes the
  9147.          relocation table. This results in smaller files on disk and faster
  9148.          loading at execution time.
  9149.  
  9150.         EXEMOD allows fine-tuning of file header information usually set by
  9151.          default, such as stack size.
  9152.  
  9153.  
  9154.  Language Details
  9155.  
  9156.       This is a summary of data types, keywords, compiler options,
  9157.  preprocessor directives, and other important aspects of the Microsoft C
  9158.  Compiler. Implementation-dependent items are grouped separately. Items that
  9159.  are new in version 4.00 are flagged, as are library routines that are
  9160.  implemented as macros.
  9161.  
  9162.  ═══════════════════════════════════════════════════════════════════════════
  9163.  KEYWORDS
  9164.  ───────────────────────────────────────────────────────────────────────────
  9165.  auto           else           long            switch
  9166.  break          enum           register        typedef
  9167.  case           extern         return          union
  9168.  char           float          short           unsigned
  9169.  continue       for            signed          void
  9170.  default        goto           sizeof          while
  9171.  do             if             static
  9172.  double         int            struct
  9173.  
  9174.  The keywords const and volatile are reserved for future use.
  9175.  
  9176.  
  9177.  ═══════════════════════════════════════════════════════════════════════════
  9178.  IMPLEMENTATION-DEPENDENT KEYWORDS
  9179.  ___________________________________________________________________________
  9180.  cdecl          fortran        near            pascal
  9181.  far            huge
  9182.  
  9183.  
  9184.  ═══════════════════════════════════════════════════════════════════════════
  9185.  FUNDAMENTAL TYPES
  9186.  ───────────────────────────────────────────────────────────────────────────
  9187.  char           int            signed          void
  9188.  double         long           unsigned
  9189.  float          short
  9190.  
  9191.  Enumeration types are also included.
  9192.  
  9193.  
  9194.  ═══════════════════════════════════════════════════════════════════════════
  9195.  TYPE SPECIFIERS
  9196.  ───────────────────────────────────────────────────────────────────────────
  9197.  char           int            struct          unsigned
  9198.  double         long           typedef name    void
  9199.  enum           short          union
  9200.  float          signed
  9201.  
  9202.  
  9203.  ═══════════════════════════════════════════════════════════════════════════
  9204.  DATA FORMATS
  9205.  ───────────────────────────────────────────────────────────────────────────
  9206.  The standard C data types are implemented as follows:
  9207.  
  9208.  Type                Length in Bytes     Range
  9209.  ───────────────────────────────────────────────────────────────────────────
  9210.  char                1                   -128 to 127
  9211.  double              8                   10**±306
  9212.  float               4                   10**±38
  9213.  int                 2                   -32768 to 32767
  9214.  long                4                   -2**31 to 2**31-1
  9215.  short               2                   -32768 to 32767
  9216.  unsigned char       1                   0 to 255
  9217.  unsigned int        2                   0 to 65535
  9218.  unsigned long       4                   0 to 2**32-1
  9219.  unsigned short      2                   0 to 65535
  9220.  
  9221.  
  9222.  ═══════════════════════════════════════════════════════════════════════════
  9223.  POINTER SIZES
  9224.  ───────────────────────────────────────────────────────────────────────────
  9225.                      Bytes          Address Arithmetic
  9226.  ───────────────────────────────────────────────────────────────────────────
  9227.  Near Pointer        2              16 bits (offset)
  9228.  Far Pointer         4              16 bits (offset)
  9229.  Huge Pointer        4              32 bits (segment offset)
  9230.  
  9231.  
  9232.  ═══════════════════════════════════════════════════════════════════════════
  9233.  PREPROCESSOR DIRECTIVES
  9234.  ───────────────────────────────────────────────────────────────────────────
  9235.  #define        #endif         #ifndef        #pragma
  9236.  #elif          #if            #include       #undef
  9237.  #else          #ifdef         #line
  9238.  
  9239.  
  9240.  ═══════════════════════════════════════════════════════════════════════════
  9241.  STORAGE CLASSES
  9242.  ───────────────────────────────────────────────────────────────────────────
  9243.  auto           extern         register       static
  9244.  
  9245.  
  9246.  ═══════════════════════════════════════════════════════════════════════════
  9247.  EXTENDED TYPE MODIFIERS
  9248.  ───────────────────────────────────────────────────────────────────────────
  9249.  cdecl          fortran        near           pascal
  9250.  far            huge
  9251.  
  9252.  
  9253.  ═══════════════════════════════════════════════════════════════════════════
  9254.  COMPILER STATEMENTS
  9255.  ───────────────────────────────────────────────────────────────────────────
  9256.  break          default        for            return
  9257.  case           do             goto           switch
  9258.  continue       else           if             while
  9259.  
  9260.  
  9261.  ═══════════════════════════════════════════════════════════════════════════
  9262.  COMPILER OPTIONS
  9263.  ───────────────────────────────────────────────────────────────────────────
  9264.  The Microsoft C Compiler supports a large number of compile-time options.
  9265.  Here is a partial list.
  9266.  
  9267.  /A string          Sets program memory model
  9268.  /Dname [=text]     Defines preprocessor macro
  9269.  /Fx                Selects listing options for source, object, assembly,
  9270.                     and testing
  9271.  /FPstring          Selects floating-point options
  9272.  /Gx                Specifies options for code generation; generates
  9273.                     8086/8088, 80186/80188, or 80286 instructions;
  9274.                     disables stack checking
  9275.  /I path            Adds to #include search path
  9276.  /Ox                Controls optimization for speed and size
  9277.  /Wn                Sets warning message level
  9278.  /Zx                Selects language options for disabling extensions,
  9279.                     emitting debugging information, and structure packing
  9280.  
  9281.  
  9282.  ═══════════════════════════════════════════════════════════════════════════
  9283.  LIBRARY FUNCTIONS
  9284.  ───────────────────────────────────────────────────────────────────────────
  9285.  The Microsoft C Compiler contains an extensive set of UNIX System V
  9286.  compatible library routines. This set includes many of the functions
  9287.  included in the emerging ANSI standard. Functions new in this version
  9288.  are designated by an exclamation point (!) before the function name.
  9289.  Functions implemented as Macros are designated by a double exclamation
  9290.  point ().
  9291.  
  9292.  Buffer Manipulation
  9293.  ───────────────────────────────────────────────────────────────────────────
  9294.   memccpy        memcmp       !memicmp        movedata
  9295.   memchr         memcpy        memset
  9296.  
  9297.  Character Classification and Conversion
  9298.  ───────────────────────────────────────────────────────────────────────────
  9299.  isalnum       isgraph      isupper       toupper
  9300.  isalpha       islower      isxdigit      _toupper
  9301.  isascii       isprint      toascii
  9302.  iscntrl       ispunct      tolower
  9303.  isdigit       isspace      _tolower
  9304.  
  9305.  Data Conversion
  9306.  ───────────────────────────────────────────────────────────────────────────
  9307.   atof           ecvt          itoa          !strtol
  9308.   atoi           fcvt          ltoa          ultoa
  9309.   atol           gcvt          !strtod
  9310.  
  9311.  Directory Control
  9312.  ───────────────────────────────────────────────────────────────────────────
  9313.   chdir         getcwd         mkdir          rmdir
  9314.  
  9315.  File Handling
  9316.  ───────────────────────────────────────────────────────────────────────────
  9317.  
  9318.   access        fstat          remove         unmask
  9319.   chmod         isatty         rename         unlink
  9320.   chsize        locking        setmode
  9321.   filelength    mktemp         stat
  9322.  
  9323.  I/O Stream Routines
  9324.  ───────────────────────────────────────────────────────────────────────────
  9325.   clearerr      fopen         getchar       !setvbuf
  9326.   fclose        fprintf        gets           sprintf
  9327.   fcloseall     fputc          getw           sscanf
  9328.   fdopen        fputchar       printf        !tempnam
  9329.  feof          fputs         putc          !tmpfile
  9330.  ferror        fread         putchar       !tmpnam
  9331.   fflush        freopen        puts           ungetc
  9332.   fgetc         fscanf         putw          !vfprintf
  9333.   fgetchar      fseek          rewind        !vprintf
  9334.   fgets         ftell         !rmtmp         !vsprintf
  9335.  fileno        fwrite         scanf
  9336.   flushall     getc           setbuf
  9337.  
  9338.  Low-Level Routines
  9339.  ───────────────────────────────────────────────────────────────────────────
  9340.   close          dup2           open           tell
  9341.   creat          eof            read           write
  9342.   dup            lseek          sopen
  9343.  
  9344.  Console and Port I/O Routines
  9345.  ───────────────────────────────────────────────────────────────────────────
  9346.   cgets          cscanf         inp            putch
  9347.   cprintf        getch          kbhit          ungetch
  9348.   cputs          getche         outp
  9349.  
  9350.  Math (Floating Point)
  9351.  ───────────────────────────────────────────────────────────────────────────
  9352.   acos          !_control87     frexp          sin
  9353.   asin           cos            hypot          sinh
  9354.   atan           cosh           ldexp          sqrt
  9355.   atan2          exp            log           !_status87
  9356.   bessel         fabs           log10          tan
  9357.   cabs           floor          matherr        tanh
  9358.   ceil           fmod           modf
  9359.  !_clear87      !_fpreset       pow
  9360.  
  9361.  Memory Allocation
  9362.  ───────────────────────────────────────────────────────────────────────────
  9363.  !alloca        !_fmsize        malloc        !_nmsize
  9364.   calloc         free          !_memavl        realloc
  9365.  !_expand       !_freect       !_msize         sbrk
  9366.  !_ffree        !halloc        !_nfree        !stackavail
  9367.  !_fmalloc      !hfree         !_nmalloc
  9368.  
  9369.  MS-DOS Interface
  9370.  ───────────────────────────────────────────────────────────────────────────
  9371.   bdos           int86          intdos         segread
  9372.   dosexterr      int86x         intdosx
  9373.  
  9374.  Process Control
  9375.  ───────────────────────────────────────────────────────────────────────────
  9376.   abort          execve        !onexit         spawnv
  9377.   execl          execvp         signal         spawnve
  9378.   execle        !execvpe        spawnl         spawnvp
  9379.   execlp         exit           spawnle       !spawnvpe
  9380.  !execlpe        _exit          spawnlp        system
  9381.   execv          getpid        !spawnlpe
  9382.  
  9383.  Searching and Sorting
  9384.  ───────────────────────────────────────────────────────────────────────────
  9385.   bsearch      !lfind          !lsearch        qsort
  9386.  
  9387.  String Manipulation
  9388.  ───────────────────────────────────────────────────────────────────────────
  9389.   strcat        strdup          strncmp        strrev
  9390.   strchr       !strerror        strncpy        strset
  9391.   strcmp       !stricmp        !strnicmp       strspn
  9392.   strcmpi       strlen          strnset       !strstr
  9393.   strcpy        strlwr          strpbrk        strtok
  9394.   strcspn       strncat         strrchr        strupr
  9395.  
  9396.  Time
  9397.  ───────────────────────────────────────────────────────────────────────────
  9398.   asctime       ftime           localtime      tzset
  9399.   ctime         gmtime          time           utime
  9400.  !difftime
  9401.  
  9402.  Miscellaneous
  9403.  ───────────────────────────────────────────────────────────────────────────
  9404.  abs           getenv          perror         setjmp
  9405.  assert        labs            putenv         srand
  9406.  FP_OFF        longjmp         rand           swab
  9407.  FP_SEG
  9408.  ───────────────────────────────────────────────────────────────────────────
  9409.  
  9410.  
  9411.  
  9412.  Appendix B  Other C Compilers
  9413.  
  9414.  
  9415.  
  9416.       There are many C compilers for DOS in today's PC marketplace. If you
  9417.  are in the process of choosing one, I recommend that you read all you can
  9418.  before making a purchase. Price is not the only consideration. Look for
  9419.  features that will make your job easier: a complete standard library of
  9420.  functions and macros; DOS and BIOS interface functions; program development
  9421.  tools; and product support.
  9422.       If you are going to produce programs commercially, look for a compiler
  9423.  that produces compact, fast-running programs. Ultimately, it is the user of
  9424.  your programs who will benefit from your choice of a quality product. Using
  9425.  a compiler that compiles faster than others but produces sloppy object code
  9426.  is probably not in your best long-term interest if you intend to make a
  9427.  living by writing programs. It may save you a little development time, but
  9428.  it will penalize your program's users every time they run the
  9429.  program.
  9430.       Interactive tools such as C interpreters are now available and provide
  9431.  a comfortable testing and experimenting environment that helps a programmer
  9432.  to quickly examine a variety of problem-solving approaches. You may want to
  9433.  use a C interpreter for early development work. The current crop of C
  9434.  interpreters are, however, less appropriate for large programming projects,
  9435.  particularly projects that involve more than one programmer. Analyze your
  9436.  needs carefully before selecting a C compiler or interpreter.
  9437.       Here are some magazine review articles that may help you decide which
  9438.  C compiler is right for you:
  9439.       "The State of C," William Hunt, PC Tech Journal, January 1986.
  9440.       "Software Reviews (Department)--21 C Compilers," S. Leibson, J.
  9441.  Reed, and J. Kyle, Computer Language, February 1985 (page 73).
  9442.       If you already have a C compiler and plan to stick with it, you have a
  9443.  different concern. The task at hand is to use the available compiler
  9444.  features and support tools to produce functioning programs. I have used
  9445.  several C compilers in addition to the Microsoft C Compiler to compile the
  9446.  programs presented in this book. They are the C86 C Compiler (Computer
  9447.  Innovations), The C Programming System (Mark Williams Company), and the
  9448.  MS-DOS C Compiler (Lattice). Each has its own way of doing things, although
  9449.  nearly all C compiler vendors are starting to converge on the proposed ANSI
  9450.  standard for C.
  9451.       Although it is possible to write source code with conditional
  9452.  compilation directives to accommodate various compilers, I chose not to do
  9453.  so in the source in Proficient C because that tends to obscure the meaning
  9454.  of the code and makes the source files nearly unreadable. The following
  9455.  material briefly describes each compiler system, the major observable
  9456.  differences from Microsoft C, and what has to be done to accommodate those
  9457.  differences. Others may exist. The ones mentioned are those relevant to the
  9458.  programs and routines developed in Proficient C.
  9459.       The compilers mentioned here deliver full compatibility with the de
  9460.  facto specification of C presented in the book The C Programming Language,
  9461.  by Brian Kernighan and Dennis Ritchie, commonly referred to as "K&R" by the
  9462.  C programming community. Each compiler varies from the others in the level
  9463.  of support for the proposed ANSI standard for C.
  9464.  
  9465.  
  9466.  Computer Innovations--Optimizing C86, Version 2.3
  9467.  
  9468.       One of the first C compiler entries into the PC marketplace was
  9469.  Computer Innovations' C86, which was later replaced by Optimizing C86. The
  9470.  compiler features good compatibility with the standard UNIX library (pre-
  9471.  System V), support for many DOS features (all versions), object libraries
  9472.  in MS-DOS object format, and is delivered with all library source (C and
  9473.  assembler) in compressed form on disk.
  9474.       The compiler is due for an update at the time this is being written to
  9475.  bring it up to System V and DOS (version 3.00 and higher) compatibility.
  9476.  Optimizing C86 differs fundamentally from Microsoft C in that it:
  9477.  
  9478.         Has no void function return specifier--use a typedef of an int to
  9479.          get around this.
  9480.  
  9481.         Has no enum keyword--either avoid the use of enums or synthesize a
  9482.          palatable substitute using a typedef of an int.
  9483.  
  9484.         Uses the sysint() function instead of intdos() and uses sysint21()
  9485.          instead of int86(). The calling sequence of parameters is
  9486.          different.
  9487.  
  9488.         Provides movblock() instead of movedata() for segmented address
  9489.          memory moves. The parameter ordering is different.
  9490.  
  9491.         Has no means of determining the DOS version provided by the
  9492.          compiler--use the ver() function described in Chapter 5.
  9493.  
  9494.         Has no structure assignment and no passing of entire structures as
  9495.          parameters or function return values.
  9496.  
  9497.         Has no perror()--use fprintf() and a message of your own making.
  9498.  
  9499.       Optimizing C86 supports small and large memory models in a way that is
  9500.  consistent with the equivalent Microsoft models. Programs may also be
  9501.  compiled in an 8080 .COM memory model, which is called "compact." It is not
  9502.  the same as the Microsoft compact memory model. There are no equivalents to
  9503.  the Microsoft medium and huge models. No data object can exceed 64 KB in
  9504.  size.
  9505.       Compiling programs under C86 involves the use of batch files. There
  9506.  are currently no compiler driver programs and no MAKE-like facility to
  9507.  control processing. C86 can be given path information for the default
  9508.  location of header files.
  9509.  
  9510.  
  9511.  Mark Williams--The C Programming System, Version 3.0.7
  9512.  
  9513.       The Mark Williams C Programming System (MWC from now on) is a complete
  9514.  C programming system adapted from the Mark Williams Coherent operating
  9515.  system, a UNIX Version 7 work-alike system. MWC incorporates some of C's
  9516.  newer features such as enum and void and up-to-date library functions.
  9517.       The compiler can be told to produce object files in either the Mark
  9518.  Williams or Microsoft object file formats. Libraries for both formats are
  9519.  provided with the compiler. MWC produces both small and large model (32-bit
  9520.  pointers) programs. It offers nothing comparable to the compact, medium,
  9521.  and huge Microsoft models.
  9522.       A public domain full-screen editor, MicroEMACS, is included in the
  9523.  package along with a good selection of UNIX-style support tools (make, cc,
  9524.  ld, pr, wc, and others).
  9525.       A trimmed-down version of MWC called Let's C is also available from
  9526.  Mark Williams. It compiles small-model programs only and is supported by
  9527.  fewer utilities, but it is a full K&R C compiler that is a good entry
  9528.  vehicle for programmers who are new to C.
  9529.       The primary differences between MWC and Microsoft C are that
  9530.  MWC:
  9531.  
  9532.         Has no structure assignment and no passing of entire structures as
  9533.          parameters or function return values.
  9534.  
  9535.         Uses intcall() instead of intdos(). It has no equivalent to the
  9536.          Microsoft int86(), but intcall() with an interrupt number of 21H
  9537.          will do the job (a bit slower, however).
  9538.  
  9539.         Provides port access via inb() and outb() (equivalent to
  9540.          Microsoft's inp() and outp() routines).
  9541.  
  9542.         Has no perror()--use fprintf() and a message of your own making.
  9543.          The global variable errno produces valid values in connection with
  9544.          math library functions only. It is not generally useful with other
  9545.          standard I/O library routines.
  9546.  
  9547.  
  9548.  Lattice--MS-DOS C Compiler, Version 3.00H
  9549.  
  9550.       The Lattice C Compiler, particularly the library support package, has
  9551.  undergone significant changes. The version 3.00 Lattice C compiler is  very
  9552.  compatible with Microsoft C, versions 3.00 and 4.00; it differs mostly in
  9553.  minor details:
  9554.  
  9555.         The standard printer device is called stdprt instead of stdprn.
  9556.  
  9557.         The Microsoft _osmajor and _osminor are handled by the external
  9558.          character variable _DOS, which may be accessed as a single
  9559.          character or a two-character array to obtain the DOS major and
  9560.          minor version numbers. You can use the ver() function described in
  9561.          the Proficient C DOS library to get the operating system version
  9562.          numbers.
  9563.  
  9564.       The MS-DOS interface functions emulate virtually all of the PC
  9565.  operating system functions provided by Microsoft C, so calls to kbhit(),
  9566.  int86(), intdos(), and so on require no modifications to compile under
  9567.  Lattice C.
  9568.       The compiler is accompanied by a library that classifies each function
  9569.  by its "environment" (i.e, origins and compatibility). The following
  9570.  classes are defined by Lattice:
  9571.  
  9572.  ═══════════════════════════════════════════════════════════════════════════
  9573.  CLASS          DESCRIPTION
  9574.  ───────────────────────────────────────────────────────────────────────────
  9575.  ANSI           Conforming to the proposed ANSI standard
  9576.  UNIX           Conforming to AT&T's UNIX System V standard
  9577.  XENIX          Compatible with Microsoft XENIX
  9578.  LATTICE        Available on all systems for which Lattice provides a
  9579.                 C compiler
  9580.  iAPX86         Available only on iAPX32 machines
  9581.  MS-DOS         Designed for use under generic MS-DOS and compatible
  9582.                 with PC-DOS (a superset of MS-DOS)
  9583.  PC-DOS         For use under PC-DOS but not guaranteed to work under
  9584.                 MS-DOS
  9585.  ───────────────────────────────────────────────────────────────────────────
  9586.  
  9587.       Lattice offers four memory models: small, program, data, and large.
  9588.  The models are comparable to the Microsoft small, medium, compact, and
  9589.  large models, respectively. Objects are limited to 64 KB in size. There are
  9590.  no equivalents to Microsoft's huge keyword and huge model.
  9591.       Compiling programs with the latest Lattice C compiler is easier than
  9592.  under previous versions. A compiler driver program, LC, does most of the
  9593.  work of compiling by calling the passes in the correct order and bailing
  9594.  out in the event of compile-time errors. The linking step is still manual
  9595.  and is best handled by batch files tailored to the particular linking task
  9596.  and the selected memory model.
  9597.  
  9598.  
  9599.  Maintaining Programs
  9600.  
  9601.       For owners of C compilers that do not provide a MAKE command, several
  9602.  alternatives are available. The first is to use DOS batch files to control
  9603.  compilation and linking steps. The DOS ERRORLEVEL (program exit code) can
  9604.  be used to abort processing when errors occur (if the compiler programs use
  9605.  the error-return facility and you are running DOS version 2.00 or higher).
  9606.  The problem with batch files is that the burden of knowing which program
  9607.  modules have to be remade is left to you.
  9608.       Another alternative is to use a third-party MAKE utility. An excellent
  9609.  MAKE program called PolyMake is available from Polytron. It is as UNIX-like
  9610.  as any DOS MAKE I have seen and can be made to work with any C compiler via
  9611.  rules placed in a control file. Because PolyMake is closer to the UNIX
  9612.  model that I am familiar with, I prefer to use it rather than the MAKEs
  9613.  provided with most C compilers (with the exception of the Mark Williams
  9614.  MAKE program, which is excellent). Several other MAKE utilities for DOS are
  9615.  available at reasonable cost. Note: The makefiles in Proficient C are
  9616.  written for the Microsoft version of MAKE. They will probably need to be
  9617.  rewritten to run successfully with other MAKE programs.
  9618.  
  9619.  
  9620.  
  9621.  Appendix C  Characters and Attributes
  9622.  
  9623.  
  9624.  
  9625.       A character code is a numeric value that is used to represent a
  9626.  character in computer memory and to control peripheral devices such as
  9627.  terminals, the console display screen, and printers. We are interested in
  9628.  two primary character-coding schemes: the ASCII character set, because it
  9629.  is the standard to which most commercial terminal and computer equipment
  9630.  designs adhere, and the IBM extended ASCII character set used by the IBM PC
  9631.  and work-alike computers.
  9632.       In dealing with terminals and PC console display devices, numeric
  9633.  codes are also used to control the appearance of a displayed character
  9634.  (video attribute), to prevent specified screen regions from being updated
  9635.  (protect), and even to make certain areas of the screen invisible to the
  9636.  user (nondisplay). IBM PCs do not directly support protected fields,
  9637.  although our programs can simulate the effect. On a PC, a nondisplay field
  9638.  is one that carries a special video attribute which sets the foreground
  9639.  color to the same value as the background color.
  9640.       This appendix is a detailed summary of the characters and attributes
  9641.  available to programmers of the IBM PC family of computers.
  9642.  
  9643.  
  9644.  ASCII Character Codes
  9645.  
  9646.       The ASCII (American National Standard Code for Information
  9647.  Interchange) character set is defined as a table of 7-bit codes that
  9648.  represent a collection of control characters and printable characters. In a
  9649.  7-bit environment all data bits are significant. In the 8-bit environment
  9650.  typical of the IBM PC and similar equipment, the lower seven bits (0
  9651.  through 6) are used to represent ASCII characters. The high bit (bit 7) is
  9652.  used for IBM code extensions, which are discussed later in this appendix.
  9653.       Figure C-1 shows the relationship between various character
  9654.  codes and the data bytes that hold them.
  9655.  
  9656.  
  9657.  Bit #      7      6      5      4      3      2      1      0
  9658.          ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
  9659.          │▒▒▒▒▒▒│      │      │      │      │      │      │      │ Character
  9660.          │▒▒▒▒▒▒│      │      │      │      │      │      │      │ Byte
  9661.          └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
  9662.  Weight    128     64     32     16     8      4      2      1
  9663.          ▓▓▓▓▓▓▓  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
  9664.             │                           │
  9665.        Code Exten-                 7-Bit ASCII Codes
  9666.         sion Bit
  9667.  
  9668.          ╔═══════════════════════════════════════════════════════╗
  9669.          ║   Character Code Ranges:                              ║
  9670.          ║                                                       ║
  9671.          ║          0-31    Control Characters       ▓           ║
  9672.          ║         32-126   Graphic Characters       ▓  ASCII   ║
  9673.          ║            127   Delete Character         ▓           ║
  9674.          ║        128-255   IBM Special Graphic Characters       ║
  9675.          ╚═══════════════════════════════════════════════════════╝
  9676.  
  9677.       FIGURE C-1 ▐ Character codes and bytes
  9678.  
  9679.  
  9680.       ASCII Control Character Table
  9681.  
  9682.       ASCII codes 0 through 31 and 127 decimal (shown in Figure C-2,
  9683.  next page) are called control codes because they are used to start,
  9684.  stop, or modify some action. Several of the control characters have
  9685.  meanings that vary with the context of their use, but most can be
  9686.  categorized as being one or more of the following types:
  9687.  
  9688.         Format Effector (FE)--controls the printed or displayed layout of
  9689.          graphic information
  9690.  
  9691.         Communications Control (CC)--controls the operation of
  9692.          communication devices and networks
  9693.  
  9694.         Information Separator (IS)_controls the logical separation of
  9695.          information
  9696.  
  9697.       It may seem a bit odd to have one control code (DEL = 127) isolated
  9698.  from all the others. This is due to its earliest use with paper-tape
  9699.  punches for which the DEL code means "erase or obliterate" an unwanted
  9700.  character, which on a medium like paper tape could be done only by punching
  9701.  out all seven of the holes (hence code 127 or 7F hex).
  9702.       In its design of the PC, IBM has attached special meanings to most of
  9703.  the ASCII control codes so that they have displayable graphic content when
  9704.  placed directly into display memory. Thus, the format effector CR (code 13
  9705.  decimal), in addition to its "carriage return" meaning when embedded in
  9706.  data streams, can also be used to place a musical-note symbol on the
  9707.  display screen. Each of the control codes except NUL (0) have an associated
  9708.  displayable symbol on the IBM PC.
  9709.  
  9710. ╓┌─────┌─────────────────────────┌──────────┌─────────┌──────────┌───────────╖
  9711.  NAME  DESCRIPTION               DEC        HEX       IBM        KEY
  9712.        (TYPE)                    CODE       CODE      GRAPHIC
  9713.  ───────────────────────────────────────────────────────────────────────────
  9714.  NUL   Null                       0         00                   nothing
  9715.  SOH   Start of heading           1         01                  ^A
  9716.  STX   Start of text              2         02                  ^B
  9717.  ETX   End of text                3         03                  ^C
  9718.  EOT   End of transmission        4         04                  ^D
  9719.  ENQ   Enquiry                    5         05                  ^E
  9720.  ACK   Acknowledge                6         06                  ^F
  9721.  BEL   Bell                       7         07                  ^G
  9722.  BS    Backspace                  8         08                  ^H
  9723.  HT    Horizontal tab             9         09                   ^I
  9724.  LF1  Linefeed                  10         0A                   ^J
  9725.  NAME  DESCRIPTION               DEC        HEX       IBM        KEY
  9726.        (TYPE)                    CODE       CODE      GRAPHIC
  9727. LF1  Linefeed                  10         0A                   ^J
  9728.  VT    Vertical tab              11         0B                   ^K
  9729.  FF    Formfeed                  12         0C                   ^L
  9730.  CR    Carriage return           13         0D                   ^M
  9731.  SO    Shift out                 14         0E                  ^N
  9732.  SI    Shift in                  15         0F                  ^O
  9733.  DLE   Data link escape          16         10                  ^P
  9734.  DC1   Device control 1          17         11                  ^Q
  9735.  DC2   Device control 2          18         12                  ^R
  9736.  DC3   Device control 3          19         13                  ^S
  9737.  DC4   Device control 4          20         14                  ^T
  9738.  NAK   Negative acknowledge      21         15                  ^U
  9739.  SYN   Synchronous idle          22         16                  ^V
  9740.  ETB   End transmission block    23         17                  ^W
  9741.  CAN   Cancel                    24         18                  ^X
  9742.  EM    End of medium             25         19                  ^Y
  9743.  SUB   Substitute                26         1A                  ^Z
  9744.  ESC   Escape                    27         1B                  ^[
  9745.  FS    File separator            28         1C                  ^\
  9746.  NAME  DESCRIPTION               DEC        HEX       IBM        KEY
  9747.        (TYPE)                    CODE       CODE      GRAPHIC
  9748. FS    File separator            28         1C                  ^\
  9749.  GS    Group separator           29         1D                  ^]
  9750.  RS    Record separator          30         1E                  ^^
  9751.  US    Unit separator            31         1F                   ^_
  9752.  DEL   Delete                   127         7F                  Del
  9753.  
  9754.       FIGURE C-2 ▐ ASCII control character table
  9755.  
  9756.  
  9757.       Printable Character Table
  9758.  
  9759.       The ASCII codes in the range of 32 to 126 decimal are designated
  9760.  "graphic" characters to show that they have a visible representation on a
  9761.  display device. Only the meaning of code 32, space (SP), which prints or
  9762.  displays as a blank location in a graphic sequence, is defined by the ASCII
  9763.  standard. All of the remaining graphic characters have meanings that are,
  9764.  in effect, enforced by consensus. We simply accept the fact that code 65
  9765.  decimal represents the letter 'A', for example.
  9766.       Nothing in the standard demands that the graphic symbols be shown as
  9767.  Gothic or Roman or any other type style. That decision is left to the
  9768.  terminal/computer maker and usually is determined by whatever type style
  9769.  the producer of the video controller chip supplies. The following table
  9770.  (Figure C-3) shows the accepted definitions of the graphic characters.
  9771.  
  9772. ╓┌──────────┌────────┌───────────────────────────────────────────────────────╖
  9773.  ASCII      DEC      HEX
  9774.  ──────────────────────
  9775.  <space>    32       20
  9776.  !          33       21
  9777.  "          34       22
  9778.  #          35       23
  9779.  $          36       24
  9780.  %          37       25
  9781.  &          38       26
  9782.  '          39       27
  9783.  (          40       28
  9784.  )          41       29
  9785.  *          42       2A
  9786.  +          43       2B
  9787.  ,          44       2C
  9788.  ASCII      DEC      HEX
  9789. ,          44       2C
  9790.  -          45       2D
  9791.  .          46       2E
  9792.  /          47       2F
  9793.  0          48       30
  9794.  1          49       31
  9795.  2          50       32
  9796.  3          51       33
  9797.  4          52       34
  9798.  5          53       35
  9799.  6          54       36
  9800.  7          55       37
  9801.  8          56       38
  9802.  9          57       39
  9803.  :          58       3A
  9804.  ;          59       3B
  9805.  <          60       3C
  9806.  =          61       3D
  9807.  >          62       3E
  9808.  ?          63       3F
  9809.  ASCII      DEC      HEX
  9810. ?          63       3F
  9811.  @          64       40
  9812.  A          65       41
  9813.  B          66       42
  9814.  C          67       43
  9815.  D          68       44
  9816.  E          69       45
  9817.  F          70       46
  9818.  G          71       47
  9819.  H          72       48
  9820.  I          73       49
  9821.  J          74       4A
  9822.  K          75       4B
  9823.  L          76       4C
  9824.  M          77       4D
  9825.  N          78       4E
  9826.  O          79       4F
  9827.  P          80       50
  9828.  Q          81       51
  9829.  R          82       52
  9830.  ASCII      DEC      HEX
  9831. R          82       52
  9832.  S          83       53
  9833.  T          84       54
  9834.  U          85       55
  9835.  V          86       56
  9836.  W          87       57
  9837.  X          88       58
  9838.  Y          89       59
  9839.  Z          90       5A
  9840.  [          91       5B
  9841.  \          92       5C
  9842.  ]          93       5D
  9843.  ^          94       5E
  9844.  _          95       5F
  9845.  `          96       60
  9846.  a          97       61
  9847.  b          98       62
  9848.  c          99       63
  9849.  d         100       64
  9850.  e         101       65
  9851.  ASCII      DEC      HEX
  9852. e         101       65
  9853.  f         102       66
  9854.  g         103       67
  9855.  h         104       68
  9856.  i         105       69
  9857.  j         106       6A
  9858.  k         107       6B
  9859.  l         108       6C
  9860.  m         109       6D
  9861.  n         110       6E
  9862.  o         111       6F
  9863.  p         112       70
  9864.  q         113       71
  9865.  r         114       72
  9866.  s         115       73
  9867.  t         116       74
  9868.  u         117       75
  9869.  v         118       76
  9870.  w         119       77
  9871.  x         120       78
  9872.  ASCII      DEC      HEX
  9873. x         120       78
  9874.  y         121       79
  9875.  z         122       7A
  9876.  {         123       7B
  9877.  |         124       7C
  9878.  }         125       7D
  9879.  ~         126       7E
  9880.  
  9881.       FIGURE C-3 ▐ The ASCII standard character set
  9882.  
  9883.  
  9884.  IBM Extended ASCII Codes
  9885.  
  9886.       Because the ASCII character set is based on a 7-bit code, the high bit
  9887.  of each byte used by the IBM PC is available for other uses. When bit 7
  9888.  (the high bit) is a logical 1, the character codes range from 128 to 255
  9889.  decimal (Figure C-4). IBM uses these extra 128 codes for special characters
  9890.  (international symbols, line-and-block drawing characters, special symbols
  9891.  for mathematics, and so forth).
  9892.  
  9893. ╓┌──────────┌─────────┌──────────────────────────────────────────────────────╖
  9894.  IBM
  9895.  GRAPHIC    DEC      HEX
  9896.  ───────────────────────
  9897.  Ç          128       80
  9898.  ü          129       81
  9899.  é          130       82
  9900.  â          131       83
  9901.  ä          132       84
  9902.  à          133       85
  9903.  å          134       86
  9904.  ç          135       87
  9905.  ê          136       88
  9906.  ë          137       89
  9907.  è          138       8A
  9908.  ï          139       8B
  9909.  î          140       8C
  9910.  ì          141       8D
  9911.  Ä          142       8E
  9912.  Å          143       8F
  9913.  É          144       90
  9914.  IBM
  9915. É          144       90
  9916.  æ          145       91
  9917.  Æ          146       92
  9918.  ô          147       93
  9919.  ö          148       94
  9920.  ò          149       95
  9921.  û          150       96
  9922.  ù          151       97
  9923.  ÿ          151       98
  9924.  Ö          152       99
  9925.  Ü          154       9A
  9926.  ¢          155       9B
  9927.  £          156       9C
  9928.  ¥          157       9D
  9929.  ₧          158       9E
  9930.  ƒ          159       9F
  9931.  á          160       A0
  9932.  í          161       A1
  9933.  ó          162       A2
  9934.  ú          163       A3
  9935.  IBM
  9936. ú          163       A3
  9937.  ñ          164       A4
  9938.  Ñ          165       A5
  9939.  ª          166       A6
  9940.  º          167       A7
  9941.  ¿          168       A8
  9942.  ⌐          169       A9
  9943.  ¬          170       AA
  9944.  ½          171       AB
  9945.  ¼          172       AC
  9946.  ¡          173       AD
  9947.  «          174       AE
  9948.  »          175       AF
  9949.  ░          176       B0
  9950.  ▒          177       B1
  9951.  ▓          178       B2
  9952.  │          179       B3
  9953.  ┤          180       B4
  9954.  ╡          181       B5
  9955.  ╢          182       B6
  9956.  IBM
  9957. ╢          182       B6
  9958.  ╖          183       B7
  9959.  ╕          184       B8
  9960.  ╣          185       B9
  9961.  ║          186       BA
  9962.  ╗          187       BB
  9963.  ╝          188       BC
  9964.  ╜          189       BD
  9965.  ╛          190       BE
  9966.  ┐          191       BF
  9967.  └          192       C0
  9968.  ┴          193       C1
  9969.  ┬          194       C2
  9970.  ├          195       C3
  9971.  ─          196       C4
  9972.  ┼          197       C5
  9973.  ╞          198       C6
  9974.  ╟          199       C7
  9975.  ╚          200       C8
  9976.  ╔          201       C9
  9977.  IBM
  9978. ╔          201       C9
  9979.  ╩          202       CA
  9980.  ╦          203       CB
  9981.  ╠          204       CC
  9982.  ═          205       CD
  9983.  ╬          206       CE
  9984.  ╧          207       CF
  9985.  ╨          208       D0
  9986.  ╤          209       D1
  9987.  ╥          210       D2
  9988.  ╙          211       D3
  9989.  ╘          212       D4
  9990.  ╒          213       D5
  9991.  ╓          214       D6
  9992.  ╫          215       D7
  9993.  ╪          216       D8
  9994.  ┘          217       D9
  9995.  ┌          218       DA
  9996.  █          219       DB
  9997.  ▄          220       DC
  9998.  IBM
  9999. ▄          220       DC
  10000.  ▌          221       DD
  10001.  ▐          222       DE
  10002.  ▀          223       DF
  10003.  α          224       E0
  10004.  ß          225       E1
  10005.  Γ          226       E2
  10006.  π          227       E3
  10007.  Σ          228       E4
  10008.  σ          229       E5
  10009.  µ          230       E6
  10010.  τ          231       E7
  10011.  Φ          232       E8
  10012.  Θ          233       E9
  10013.  Ω          234       EA
  10014.  δ          235       EB
  10015.  ∞          236       EC
  10016.  φ          237       ED
  10017.  ε          238       EE
  10018.  ∩          239       EF
  10019.  IBM
  10020. ∩          239       EF
  10021.  ≡          240       F0
  10022.  ±          241       F1
  10023.  ≥          242       F2
  10024.  ≤          243       F3
  10025.  ⌠          244       F4
  10026.  ⌡          245       F5
  10027.  ÷          246       F6
  10028.  ≈          247       F7
  10029.  °          248       F8
  10030.  ∙          249       F9
  10031.  ·          250       FA
  10032.  √          251       FB
  10033.  ⁿ          252       FC
  10034.  ²          253       FD
  10035.  ■          254       FE
  10036.             255       FF
  10037.  
  10038.         FIGURE C-4 ▐ The IBM extended ASCII character set
  10039.  
  10040.  
  10041.       Line-Drawing Characters_Quick Reference
  10042.  
  10043.       Among the IBM extended ASCII characters are some line-drawing
  10044.  characters that allow us to draw boxes and other shapes that can be formed
  10045.  from various corners and straight-line segments. The codes are a bit
  10046.  scattered, so to make them accessible, Figure C-5 visually groups the
  10047.  codes for single- and double-line drawing characters. The origin of
  10048.  these design aids can be traced to Rich Schinnell in an item published
  10049.  in PC World (November 1983).
  10050.  
  10051.  
  10052.    205    186                         196    186
  10053.  ═══════   ║                        ───────   ║
  10054.            ║                                  ║
  10055.  
  10056.    201    203    187                  214    210    183
  10057.  ╔═════  ══╦══  ═════╗              ╓─────  ──╥──  ─────╖
  10058.  ║         ║         ║              ║         ║         ║
  10059.  
  10060.    204    206    185                  199    215    182
  10061.  ║         ║         ║              ║         ║         ║
  10062.  ╠════  ═══╬═══  ════╣              ╟────  ───╫───  ────╢
  10063.  ║         ║         ║              ║         ║         ║
  10064.  
  10065.    200    202    188                  211    208    189
  10066.  ║         ║         ║              ║         ║         ║
  10067.  ╚═════  ══╩══  ═════╝              ╙─────  ──╨──  ─────╜
  10068.  
  10069.  
  10070.    196    179                         205    179
  10071.  ───────   │                        ═══════   │
  10072.            │                                  │
  10073.  
  10074.    218    194    191                  213    209    184
  10075.  ┌─────  ──┬──  ─────┐              ╒═════  ══╤══  ═════╕
  10076.  │         │         │              │         │         │
  10077.  
  10078.    195    197    180                  198    216    181
  10079.  │         │         │              │         │         │
  10080.  ├────  ───┼───  ────┤              ╞════  ═══╪═══  ════╡
  10081.  │         │         │              │         │         │
  10082.  
  10083.  192      193    217                  212    207    190
  10084.  │         │         │              │         │         │
  10085.  └─────  ──┴──  ─────┘              ╘═════  ══╧══  ═════╛
  10086.  
  10087.       FIGURE C-5 ▐ Line-drawing character sets
  10088.  
  10089.  
  10090.       Block Characters--Quick Reference
  10091.  
  10092.       The treasure trove of special characters in the IBM extended ASCII
  10093.  character set also contains eight block characters that can be used to good
  10094.  effect in screen displays. They are detailed in Figure C-6.
  10095.  
  10096.  
  10097.               ╔══════════════════════════════════════════╗
  10098.               ║                                          ║
  10099.               ║    Figure C-6 is found on page 445       ║
  10100.               ║    in the printed version of the book.   ║
  10101.               ║                                          ║
  10102.               ╚══════════════════════════════════════════╝
  10103.  
  10104.       FIGURE C-6 ▐ Block character sets
  10105.  
  10106.  
  10107.  Video Attributes
  10108.  
  10109.       Both the monochrome and color display systems used with PCs give us a
  10110.  considerable degree of control over the appearance of characters on the
  10111.  screen. Each character byte in memory is accompanied by an attribute byte
  10112.  that specifies the visual characteristics of each displayed
  10113.  character.
  10114.       Figure C-7 shows how the attribute byte is
  10115.  interpreted.
  10116.  
  10117.  
  10118.  Bit #      7      6      5      4      3      2      1      0
  10119.          ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
  10120.          │  BL  │  R   │  G   │  B   │  I   │  R   │  G   │  B   │ Attribute
  10121.          └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘ Byte
  10122.          ░ 128     64     32     16     8      4      2      1
  10123.  Weight ░
  10124.          ░  8       4      2      1     8      4      2      1
  10125.           ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
  10126.                        │                           │
  10127.                   Background                   Foreground
  10128.  
  10129.          ╔═══════════════════════════════════════════════════════╗
  10130.          ║   Bit designations:                                   ║
  10131.          ║                                                       ║
  10132.          ║      BL  Foreground blink or background intensity     ║
  10133.          ║       I  Foreground intensity                         ║
  10134.          ║       R  Red                                          ║
  10135.          ║       G  Green                                        ║
  10136.          ║       B  Blue                                         ║
  10137.          ╚═══════════════════════════════════════════════════════╝
  10138.  
  10139.       FIGURE C-7 ▐ Video attribute byte interpretation
  10140.  
  10141.  
  10142.       The following table summarizes the text mode attributes for both
  10143.  monochrome and color/graphics systems. The codes between 0 and 7 set the
  10144.  foreground (low 4 bits) or background (high 4 bits) attribute. Thus,
  10145.  storing the code 0x02 in an attribute byte produces a green foreground and
  10146.  a black background.
  10147.       The high bit in each 4-bit (nibble) attribute component determines the
  10148.  intensity. In the foreground component, setting bit 3 causes the foreground
  10149.  to be high intensity. Setting bit 7 of an attribute byte controls either
  10150.  foreground blinking or background intensity. On a CGA, port 3D8, bit 5
  10151.  enables foreground blinking when set and disables blinking when cleared.
  10152.  The power-on default is blink (bit 5=1). The equivalent MDA port is 3B8.
  10153.       We can combine attribute values using bit shifting and ORing to
  10154.  produce various compound attributes. The combined code
  10155.  
  10156.       (0x01 << 4) | 0x07 | 0x08
  10157.  
  10158.  yields a bright white character on a blue background.
  10159.  
  10160.  ═══════════════════════════════════════════════════════════════════════════
  10161.  PRIMARY ATTRIBUTES
  10162.  ───────────────────────────────────────────────────────────────────────────
  10163.  Dec     Hex      Binary      Attribute  Description
  10164.  Code    Code     Code        CGA        MDA FG             MDA BG
  10165.  ───────────────────────────────────────────────────────────────────────────
  10166.  0       00       0000        Black      Black              Black
  10167.  1       01       0001        Blue       White Underline    White
  10168.  2       02       0010        Green      White              White
  10169.  3       03       0011        Cyan       White              White
  10170.  4       04       0100        Red        White              White
  10171.  5       05       0101        Magenta    White              White
  10172.  6       06       0110        Brown      White              White
  10173.  7       07       0111        White      White              White
  10174.  ───────────────────────────────────────────────────────────────────────────
  10175.  
  10176.  
  10177.  ═══════════════════════════════════════════════════════════════════════════
  10178.  INTENSITY MODIFIERS
  10179.  ───────────────────────────────────────────────────────────────────────────
  10180.  
  10181.  Dec     Hex      Binary       Description
  10182.  Code    Code     Code
  10183.  ───────────────────────────────────────────────────────────────────────────
  10184.  8       08       1000         High Intensity
  10185.  128     80       10000000     Blinking Foreground (or High Intensity
  10186.                                Background). [This is just (0x08 << 4) and
  10187.                                is referred to as the "blink" bit or
  10188.                                background intensity bit depending upon
  10189.                                whether blinking is enabled (default) or
  10190.                                disabled.]
  10191.  ───────────────────────────────────────────────────────────────────────────
  10192.  
  10193.  
  10194.  
  10195.  Appendix D  Local Library Summary
  10196.  
  10197.  
  10198.  
  10199.       Proficient C is largely about stockpiling routines to augment those
  10200.  provided in the standard library. In the course of 14 chapters, we have
  10201.  developed numerous functions and macros for a wide range of purposes. To
  10202.  make the routines accessible to programmers, a brief manual page for each
  10203.  routine is presented here.
  10204.       The routines are grouped by library category and organized
  10205.  alphabetically within categories. The libraries we developed are the
  10206.  ansi.sys interface (ansi.lib), the operating system interfaces (bios.lib
  10207.  and dos.lib), the buffered screen interface (sbuf.lib), and the utility
  10208.  routines (util.lib).
  10209.  
  10210.  ═══════════════════════════════════════════════════════════════════════════
  10211.  ANSI LIBRARY
  10212.  ───────────────────────────────────────────────────────────────────────────
  10213.  The ANSI device driver is accessed primarily through the macros defined in
  10214.  the ansi.h header file that resides in \include\local. Two additional
  10215.  routines are implemented as functions in \lib\local\ansi.lib. The ANSI
  10216.  device driver and this interface are described in Chapter 13.
  10217.  
  10218.  ───────────────────────────────────────────────────────────────────────────
  10219.  Function
  10220.       ansi_cpr--cursor position report
  10221.  
  10222.  Synopsis
  10223.  
  10224.            #include <local\ansi.h>
  10225.  
  10226.            void ansi_cpr(row, col)
  10227.            int *row;       /* pointer to row value */
  10228.            int *col;       /* pointer to column value */
  10229.  
  10230.  Description
  10231.       Request a cursor position report from the ANSI device driver. The
  10232.       driver must have been previously installed. The cursor position values
  10233.       are saved in the row and col variables whose addresses are passed as
  10234.       parameters.
  10235.  
  10236.  Notes
  10237.       This function will fail (hang the system waiting for keyboard input)
  10238.       if the ANSI device driver is not properly installed.
  10239.  
  10240.  ───────────────────────────────────────────────────────────────────────────
  10241.  Macro
  10242.       ANSI_CUP--position the cursor
  10243.       ANSI_HVP--horizontal and vertical position
  10244.  
  10245.  Synopsis
  10246.  
  10247.            #include <local\ansi.h>
  10248.  
  10249.            ANSI_CUP(r, c)
  10250.            ANSI_HVP(r, c)
  10251.            short r, c;     /* row and column */
  10252.  
  10253.  Description
  10254.       These two macros achieve the same result. They attempt to move the
  10255.       cursor to the specified screen position. Illegal requests are ignored.
  10256.  
  10257.  ───────────────────────────────────────────────────────────────────────────
  10258.  Macro
  10259.       ANSI_CUB--move cursor back (left)
  10260.       ANSI_CUD--move cursor down
  10261.       ANSI_CUF--move cursor forward (right)
  10262.       ANSI_CUU--move cursor up
  10263.  
  10264.  Synopsis
  10265.  
  10266.            #include <local\ansi.h>
  10267.  
  10268.            ANSI_CUB(n)
  10269.            ANSI_CUD(n)
  10270.            ANSI_CUF(n)
  10271.            ANSI_CUU(n)
  10272.            short n;        /* number of repetitions (columns or rows) */
  10273.  
  10274.  Description
  10275.       These four macros attempt to move the cursor in the specified
  10276.       direction. If n is larger than the number of rows or columns remaining
  10277.       in the direction of travel, the cursor is moved to the extreme
  10278.       position.
  10279.  
  10280.  ───────────────────────────────────────────────────────────────────────────
  10281.  Macro
  10282.       ANSI_DSR--request a device status report
  10283.       ANSI_RCP--restore screen position
  10284.       ANSI_SCP--save screen position
  10285.  
  10286.  Synopsis
  10287.  
  10288.            #include <local\ansi.h>
  10289.  
  10290.            ANSI_DSR
  10291.            ANSI_RCP
  10292.            ANSI_SCP
  10293.  
  10294.  Description
  10295.       A call to ANSI_DSR causes the ANSI driver to deposit a string
  10296.       containing the cursor row and column data into the keyboard buffer
  10297.       where it can be read (see the ansi_cpr() function). ANSI_SCP and
  10298.       ANSI_RCP are cooperating macros that communicate through private
  10299.       storage to save and restore the cursor position.
  10300.  
  10301.  ───────────────────────────────────────────────────────────────────────────
  10302.  Macro
  10303.       ANSI_ED--erase in display (clear screen)
  10304.       ANSI_EL--erase in line (clear to end of line)
  10305.  
  10306.  Synopsis
  10307.  
  10308.            #include <local\ansi.h>
  10309.  
  10310.            ANSI_ED
  10311.            ANSI_EL
  10312.  
  10313.  Description
  10314.       ANSI_ED sets all character locations in the display to blanks,
  10315.       effectively clearing the screen. Similarly, ANSI_EL sets all
  10316.       character positions from the cursor to the end of the line to blanks.
  10317.  
  10318.  Notes
  10319.       Most ANSI drivers use the prevailing video attribute for cleared
  10320.       character positions, but at least the MS-DOS version of the ANSI
  10321.       driver provided for the AT&T 6300 uses the "normal" (white on black)
  10322.       attribute.
  10323.  
  10324.  ───────────────────────────────────────────────────────────────────────────
  10325.  Macro
  10326.       ANSI_RM--reset mode
  10327.       ANSI_SM--set mode
  10328.  
  10329.  Synopsis
  10330.  
  10331.            #include <local\ansi.h>
  10332.  
  10333.            ANSI_RM(m)
  10334.            ANSI_SM(m)
  10335.            short m;
  10336.  
  10337.  Description
  10338.       These two macros are used to set and reset (clear) video modes. In
  10339.       addition to the usual video modes that can be set from the DOS command
  10340.       line using the MODE command, ANSI_SM and ANSI_RM permit programs to
  10341.       control "wrapping" at the end of display lines.
  10342.  
  10343.  ───────────────────────────────────────────────────────────────────────────
  10344.  Macro
  10345.       ANSI_SGR--set graphic rendition (video attribute)
  10346.  
  10347.  Synopsis
  10348.  
  10349.            #include <local\ansi.h>
  10350.  
  10351.            ANSI_SGR(a)
  10352.            unsigned char a;        /* video attribute */
  10353.  
  10354.  Description
  10355.       ANSI_SGR can be used to set any of the video attributes allowed for
  10356.       text modes.
  10357.  
  10358.  ───────────────────────────────────────────────────────────────────────────
  10359.  Function
  10360.       ansi_tst--check for the presence of ANSI.SYS
  10361.  
  10362.  Synopsis
  10363.  
  10364.            #include <local\ansi.h>
  10365.  
  10366.            void ansi_tst()
  10367.  
  10368.  Description
  10369.       Uses cursor positioning and reading to determine whether the ANSI
  10370.       device driver is installed. If not, the function displays an error
  10371.       message and returns control to DOS. If the ANSI driver is installed,
  10372.       control is returned to the calling program.
  10373.  
  10374.  Notes
  10375.       This function uses the BIOS readca() function to find out where the
  10376.       cursor is located because ansi_cpr() cannot be called unless the ANSI
  10377.       driver is known to be installed. Thus, ansi_tst() may fail on PCs that
  10378.       are not completely BIOS-compatible with the IBM PC family.
  10379.  
  10380.  ───────────────────────────────────────────────────────────────────────────
  10381.  Function
  10382.       colornum--convert a color string to a number
  10383.  
  10384.  Synopsis
  10385.  
  10386.            #include <local\ibmcolor.h>
  10387.  
  10388.            int colornum(name)
  10389.            char *name;             /* color string */
  10390.  
  10391.  Description
  10392.       Using a built-in lookup table, colornum() converts a color name in
  10393.       string form, "red" for example, to its IBM numeric equivalent (4 in
  10394.       this case).
  10395.  
  10396.  Return
  10397.       A number that specifies the IBM color value for the specified color
  10398.       name.
  10399.  
  10400.  ───────────────────────────────────────────────────────────────────────────
  10401.  Function
  10402.       setattr--set video attribute
  10403.  
  10404.  Synopsis
  10405.  
  10406.            #include <local\ansi.h>
  10407.            #include <local\ibmcolor.h>
  10408.  
  10409.            void setattr(pos, attr)
  10410.            POSITION pos;
  10411.            int attr;
  10412.  
  10413.  Description
  10414.       Using information provided by the caller about the attribute position
  10415.       (pos), setattr() sets the foreground, background, or border attribute
  10416.       to the specified value.
  10417.  
  10418.  ───────────────────────────────────────────────────────────────────────────
  10419.  Function
  10420.       userattr--set a user-specified attribute
  10421.  
  10422.  Synopsis
  10423.  
  10424.            #include <local\ansi.h>
  10425.  
  10426.            int userattr(foreground, background, border)
  10427.            char *foreground, *background, *border;
  10428.  
  10429.  Description
  10430.       A program can call userattr() just before exiting to set the video
  10431.       attributes to those specified by the user in the DOS environment
  10432.       (using FGND, BKGND, and BORDER DOS variables). The calling function
  10433.       must specify attribute values to be used in the event that none are
  10434.       specified in the environment.
  10435.  
  10436.  Return
  10437.       An indication of SUCCESS or FAILURE.
  10438.  
  10439.  ═══════════════════════════════════════════════════════════════════════════
  10440.  BIOS LIBRARY
  10441.  ───────────────────────────────────────────────────────────────────────────
  10442.  
  10443.  The BIOS library is composed of selected routines supported by the PC's ROM
  10444.  BIOS. Primary coverage is given to BIOS video and equipment-determination
  10445.  functions. In addition, keyboard status and some composite video functions
  10446.  are included in the library. Most other needed services can be obtained via
  10447.  routines in the standard libraries provided with nearly all C compilers for
  10448.  DOS.
  10449.  
  10450.  Most of the BIOS routines are designed to return the value of the carry
  10451.  flag, which is a nonzero value if an error occurs duing the interrupt
  10452.  operation. A few use the return value to pass back the information
  10453.  requested by the calling function.
  10454.  
  10455.  ───────────────────────────────────────────────────────────────────────────
  10456.  Function
  10457.       clrscrn--clear the entire screen to spaces
  10458.       clrw--clear a "window" region to spaces
  10459.  
  10460.  Synopsis
  10461.  
  10462.            int clrscrn(a)
  10463.            int clrw(t, l, b, r, a)
  10464.            unsigned char a;        /* video attribute */
  10465.            int t, l;               /* upper left corner */
  10466.            int b, r;               /* lower right corner */
  10467.  
  10468.  Description
  10469.       The two functions, clrscrn() and clrw(), set all characters to spaces
  10470.       in the entire display memory or a specified region, respectively. The
  10471.       current "visual" page is cleared.
  10472.  
  10473.  Return
  10474.       The value of the carry flag.
  10475.  
  10476.  ───────────────────────────────────────────────────────────────────────────
  10477.  Function
  10478.       delay--provide a machine-independent time delay
  10479.  
  10480.  Synopsis
  10481.  
  10482.            void delay(d)
  10483.            float d;                /* delay duration */
  10484.  
  10485.  Description
  10486.       The delay() function loops to waste the specified number of seconds
  10487.       and fractional seconds. The delay has a practical resolution of about
  10488.       .06 seconds.
  10489.  
  10490.  ───────────────────────────────────────────────────────────────────────────
  10491.  Function
  10492.       drawbox--draw a single-line text graphics box shape
  10493.  
  10494.  Synopsis
  10495.  
  10496.            int drawbox(top, lft, btm, rgt, pg)
  10497.            int top, lft;           /* upper left corner */
  10498.            int btm, rgt;           /* lower right corner */
  10499.            int pg;                 /* screen page number */
  10500.  
  10501.  Description
  10502.       The drawbox() function draws a fine-rule box (using the IBM single-
  10503.       line drawing characters) around the perimeter of the region specified
  10504.       by the corner parameters. The pg parameter specifies the screen page
  10505.       (active) to draw on. On a standard monochrome display adapter, this
  10506.       value must be 0.
  10507.  
  10508.  Notes
  10509.       This function is usually not appropriate for use in graphics modes
  10510.       because the special drawing characters are not defined unless you
  10511.       create your own table of "extended" character definitions.
  10512.  
  10513.  ───────────────────────────────────────────────────────────────────────────
  10514.  Function
  10515.       ega_info--gather EGA-related information
  10516.  
  10517.  Synopsis
  10518.  
  10519.            #include <local\video.h>
  10520.  
  10521.            int ega_info(memsize, mode, features, switches)
  10522.            int *memsize;                   /* memory size index (0-3) */
  10523.            int *mode;                      /* color (0)/mono (1) mode */
  10524.            unsigned int *features;         /* feature bit settings */
  10525.            unsigned int *switches;         /* EGA switch settings */
  10526.  
  10527.  Description
  10528.       This function uses an EGA BIOS routine to gather and save EGA-related
  10529.       information including whether an EGA adapter is installed in the
  10530.       system. Memory size is reported by an index (from 0 = 64 KB up to
  10531.       3 = 256 KB in 64-KB increments) and mode is indicated only as color
  10532.       (0) or monochrome. Use the getstate() function to find out what the
  10533.       video mode number is.
  10534.  
  10535.  Return
  10536.       A logical 1 if the reported memory size is valid or 0 otherwise. This
  10537.       is considered by IBM to be an adequate indicator of the presence or
  10538.       absence of an EGA adapter.
  10539.  
  10540.  ───────────────────────────────────────────────────────────────────────────
  10541.  Function
  10542.       equipchk--get equipment list
  10543.  
  10544.  Synopsis
  10545.  
  10546.            #include <local\equip.h>
  10547.  
  10548.            int equipchk()
  10549.  
  10550.  Description
  10551.       The equipchk() function queries the system data areas and fills in the
  10552.       global Eq structure to indicate what equipment is installed. The data
  10553.       structure holds the number of logical disk drives, parallel printer
  10554.       ports, and serial ports. It also indicates whether the system has a
  10555.       game port and shows how much of the installed memory is on the system
  10556.       board. In addition, the current video mode is indicated by the BIOS
  10557.       video mode number (see getstate() for details).
  10558.  
  10559.  Return
  10560.       The value of the carry flag.
  10561.  
  10562.  ───────────────────────────────────────────────────────────────────────────
  10563.  Function
  10564.       getctype--get the current cursor type
  10565.  
  10566.  Synopsis
  10567.  
  10568.            #include <local\bioslib.h>
  10569.  
  10570.            int getctype(start_scan, end_scan, pg)
  10571.            int *start_scan;        /* starting cursor scan line */
  10572.            int *end_scan;          /* ending cursor scan line */
  10573.            int pg;                 /* "visual" page */
  10574.  
  10575.  Description
  10576.       The getctype() function retrieves the values of the starting and
  10577.       ending cursor scan rows.
  10578.  
  10579.  Return
  10580.       The value of the carry flag.
  10581.  
  10582.  ───────────────────────────────────────────────────────────────────────────
  10583.  
  10584.  Function
  10585.       getstate--get the current video state
  10586.  
  10587.  Synopsis
  10588.  
  10589.            #include <local\video.h>
  10590.  
  10591.            int getstate()
  10592.  
  10593.  Description
  10594.       This function queries the system data areas in memory and updates the
  10595.       global video state variables.
  10596.  
  10597.  Return
  10598.       The value of the carry flag.
  10599.  
  10600.  ───────────────────────────────────────────────────────────────────────────
  10601.  Function
  10602.       getticks--get the current timer "tick" count
  10603.  
  10604.  Synopsis
  10605.  
  10606.            long getticks()
  10607.  
  10608.  Description
  10609.       This function queries the BIOS data area via the time-of-day service
  10610.       to get a value that is the number of timer ticks since midnight. If
  10611.       the rollover flag is set, a full day's worth of ticks is added to the
  10612.       count.
  10613.  
  10614.  Return
  10615.       The current timer "tick" count.
  10616.  
  10617.  ───────────────────────────────────────────────────────────────────────────
  10618.  Function
  10619.       kbd_stat--get keyboard status information
  10620.  
  10621.  Synopsis
  10622.  
  10623.            #include <local\keybdlib.h>
  10624.  
  10625.            unsigned char kbd_stat()
  10626.  
  10627.  Description
  10628.       The kbd_stat() function obtains keyboard status information and makes
  10629.       it available to the calling function. Each bit in the status byte is
  10630.       significant and is defined in the keybdlib.h file. This function lets
  10631.       the program determine the current state of the shift, control (Ctrl),
  10632.       alternate shift (Alt), and lock (Num, Caps, Scroll) keys.
  10633.  
  10634.  Return
  10635.       The value of the AL register, which contains the keyboard status
  10636.       information (bit-significant).
  10637.  
  10638.  ───────────────────────────────────────────────────────────────────────────
  10639.  Function
  10640.       memsize--get memory size in kilobytes
  10641.  
  10642.  Synopsis
  10643.  
  10644.            int memsize()
  10645.  
  10646.  Description
  10647.       The memsize() function reads the system data area to determine the
  10648.       total amount of memory installed in the system.
  10649.  
  10650.  Return
  10651.       The total memory size (system board plus I/O channel) in kilobytes.
  10652.  
  10653.  ───────────────────────────────────────────────────────────────────────────
  10654.  Function
  10655.       palette--select graphics palette or text border color
  10656.  
  10657.  Synopsis
  10658.  
  10659.            int palette(id, color)
  10660.            unsigned int id;        /* palette identifier */
  10661.            unsigned int color;     /* color number */
  10662.  
  10663.  Description
  10664.       The palette() function can be used in either graphics or text
  10665.       (alphanumeric) modes. It selects the palette of foreground drawing
  10666.       colors in graphics modes. In text modes, it selects the border color.
  10667.  
  10668.  Return
  10669.       The value of the carry flag.
  10670.  
  10671.  ───────────────────────────────────────────────────────────────────────────
  10672.  Function
  10673.       putcur--move the cursor to a specified row and column
  10674.  
  10675.  Synopsis
  10676.  
  10677.            int putcur(r, c, pg)
  10678.            int r, c;               /* row and column */
  10679.            int pg;                 /* "active" screen page */
  10680.  
  10681.  Description
  10682.       This function places the cursor at the specified row and column of the
  10683.       active page given by pg. Illegal requests are silently ignored by the
  10684.       BIOS routine.
  10685.  
  10686.  Return
  10687.       The value of the carry flag.
  10688.  
  10689.  ───────────────────────────────────────────────────────────────────────────
  10690.  Function
  10691.       putfld--display a string in a field
  10692.  
  10693.  Synopsis
  10694.  
  10695.           int putfld(s, w, pg)
  10696.           register char *s;       /* string to write */
  10697.           int w;                  /* field width */
  10698.           int pg;                 /* screen page for writes */
  10699.  
  10700.  Description
  10701.       The putfld() function calls other BIOS functions to display a string
  10702.       within a field starting on page pg at the current cursor position and
  10703.       extending for w columns. Runs of a single given character, such as a
  10704.       series of spaces, are compressed to a single call on writec() to
  10705.       reduce function-call overhead. The field is padded with spaces if the
  10706.       string does not completely fill it. The string is truncated if it is
  10707.       too long to fit in the field. The cursor location is updated to the
  10708.       last position written.
  10709.  
  10710.  Return
  10711.       A 0 if all goes well or a nonzero value to flag an error.
  10712.  
  10713.  ───────────────────────────────────────────────────────────────────────────
  10714.  Function
  10715.       putstr--display a string in the prevailing attribute
  10716.  
  10717.  Synopsis
  10718.  
  10719.            int putstr(s, pg)
  10720.            register char *s;       /* string to display */
  10721.            int pg;                 /* "active" screen page */
  10722.  
  10723.  Description
  10724.       The putstr() function places the text of the string into display
  10725.       memory and advances the cursor position.
  10726.  
  10727.  Return
  10728.       Number of characters written.
  10729.  
  10730.  Notes
  10731.       Results are undefined if the string is not confined to the current
  10732.       screen row.
  10733.  
  10734.  ───────────────────────────────────────────────────────────────────────────
  10735.  Function
  10736.       put_ch--display a character
  10737.  
  10738.  Synopsis
  10739.  
  10740.            int put_ch(ch, pg)
  10741.            register char ch;       /* character to write */
  10742.            int pg;                 /* "active" screen page */
  10743.  
  10744.  Description
  10745.       The put_ch() function writes a single character to the display and
  10746.       advances the cursor position.
  10747.  
  10748.  Return
  10749.       Always 1 to indicate the number of characters written.
  10750.  
  10751.  ───────────────────────────────────────────────────────────────────────────
  10752.  Function
  10753.       readca--read the character and attribute in a cell
  10754.  
  10755.  Synopsis
  10756.  
  10757.            int readca(ch, attr, pg)
  10758.            unsigned char *ch;              /* character */
  10759.            unsigned char *attr;            /* video attribute */
  10760.            int pg;                         /* "active" screen page */
  10761.  
  10762.  Description
  10763.       The readca() function gets the values of the character and video
  10764.       attribute at the current cursor position on the specified active
  10765.       screen page and fills in the ch and attr variables pointed to by the
  10766.       parameters.
  10767.  
  10768.  Return
  10769.       The value of the carry flag.
  10770.  
  10771.  ───────────────────────────────────────────────────────────────────────────
  10772.  Function
  10773.       readcur--get the current cursor row and column
  10774.  
  10775.  Synopsis
  10776.  
  10777.            int readcur(row, col, pg)
  10778.            int *row, *col;         /* pointers to cursor values */
  10779.            int pg;                 /* "active" screen page */
  10780.  
  10781.  Description
  10782.       The readcur() function reports the location of the cursor on the
  10783.       specified screen page by storing the row and column values in the row
  10784.       and col variables pointed to by the parameters.
  10785.  
  10786.  Return
  10787.       The value of the carry flag.
  10788.  
  10789.  ───────────────────────────────────────────────────────────────────────────
  10790.  Function
  10791.       readdot--read the value of a single pixel
  10792.  
  10793.  Synopsis
  10794.  
  10795.            int readdot(row, col, dcolor)
  10796.            int row, col;           /* cursor position */
  10797.            int *dcolor;            /* pointer to dot color */
  10798.  
  10799.  Description
  10800.       The readdot() function places the color number for the pixel at the
  10801.       row and column position into the variable pointed to by the dcolor
  10802.       parameter. The row and col parameters can be used to address up to
  10803.       224,000 individual screen pixels in the high resolution mode of the
  10804.       Enhanced Graphics Adapter.
  10805.  
  10806.  Return
  10807.       The value of the carry flag.
  10808.  
  10809.  ───────────────────────────────────────────────────────────────────────────
  10810.  Function
  10811.       scroll--scroll a specified screen region vertically
  10812.  
  10813.  Synopsis
  10814.  
  10815.            int scroll(t, l, b, r, n, a)
  10816.            int t, l;               /* upper left corner */
  10817.            int b, r;               /* lower right corner */
  10818.            int n;                  /* number of rows to scroll */
  10819.            unsigned char a;        /* video attribute */
  10820.  
  10821.  Description
  10822.       The scroll() routine clears (initializes) the specified region if n is
  10823.       0. It scrolls the display image up n lines if n is a positive nonzero
  10824.       value and down if n is negative. Vacated lines are filled with blanks
  10825.       (spaces) in the specified attribute.
  10826.  
  10827.  Return
  10828.       The value of the carry flag.
  10829.  
  10830.  ───────────────────────────────────────────────────────────────────────────
  10831.  Function
  10832.       setctype--set the cursor type
  10833.  
  10834.  Synopsis
  10835.  
  10836.            int setctype(start, end)
  10837.            int start;              /* starting cursor scan line */
  10838.            int end;                /* ending cursor scan line */
  10839.  
  10840.  Description
  10841.       The setctype() function sets the starting and ending scan lines that
  10842.       form the cursor shape. If the starting scan line is greater than the
  10843.       ending scan line, the cursor is split (formed of two parts) on
  10844.       perfectly IBM-compatible machines. Setting the cursor starting scan
  10845.       line to a large value turns the cursor off on many machines, but this
  10846.       is not a reliable method on all hardware.
  10847.  
  10848.  Return
  10849.       The value of the carry flag.
  10850.  
  10851.  ───────────────────────────────────────────────────────────────────────────
  10852.  Function
  10853.       setpage--select the "visual" video page
  10854.  
  10855.  Synopsis
  10856.  
  10857.            int setpage(pg)
  10858.            int pg;                 /* "visual" screen page */
  10859.  
  10860.  Description
  10861.       This function sets the "visual" screen page to pg. It checks the value
  10862.       of pg against the valid page ranges for the current video mode.
  10863.  
  10864.  Return
  10865.       The value of the carry flag or -1 if an illegal page request is made.
  10866.  
  10867.  ───────────────────────────────────────────────────────────────────────────
  10868.  Function
  10869.       setvmode--set the video mode
  10870.  
  10871.  Synopsis
  10872.  
  10873.            int setvmode(vmode)
  10874.            int vmode;              /* video mode number */
  10875.  
  10876.  Description
  10877.       The setvmode() function attempts to set the specified video mode.
  10878.       Illegal mode requests are not detected and are usually silently
  10879.       ignored by the BIOS routine.
  10880.  
  10881.  Return
  10882.       The value of the carry flag.
  10883.  
  10884.  Notes
  10885.       This function cannot be used to switch from one display adapter to
  10886.       another in systems equipped with more than one adapter. Instruct users
  10887.       to run the DOS MODE command to set the mode before entering the
  10888.       program.
  10889.  
  10890.  ───────────────────────────────────────────────────────────────────────────
  10891.  Function
  10892.       writea--write an attribute
  10893.       writec--write a character
  10894.       writeca--write a character in a specified attribute
  10895.  
  10896.  Synopsis
  10897.  
  10898.            int writea(a, count, pg)
  10899.            int writec(ch, count, pg)
  10900.            int writeca(ch, attr, count, pg)
  10901.            unsigned char ch;       /* character */
  10902.            unsigned char attr;     /* video attribute */
  10903.            int count;              /* number of repetitions */
  10904.            int pg;                 /* "active" screen page */
  10905.  
  10906.  Description
  10907.       These functions write a (possibly) repeated attribute, character, or a
  10908.       combination of the two to the specified screen page. The count
  10909.       specifies the number of characters to write and must fit entirely
  10910.       within the current screen row.
  10911.  
  10912.  Return
  10913.       The value of the carry flag.
  10914.  
  10915.  ───────────────────────────────────────────────────────────────────────────
  10916.  Function
  10917.       writedot--write a single pixel
  10918.  
  10919.  Synopsis
  10920.  
  10921.            int writedot(r, c, color)
  10922.            int r, c;               /* coordinates of the pixel */
  10923.            int color;              /* dot color */
  10924.  
  10925.  Description
  10926.       This function is for use in graphics modes only. It sets the color of
  10927.       the specified pixel to color. Setting the color to the same color as
  10928.       the background effectively erases the pixel.
  10929.  
  10930.  Return
  10931.       The value of the carry flag.
  10932.  
  10933.  ───────────────────────────────────────────────────────────────────────────
  10934.  Function
  10935.       writemsg--write a two-part message
  10936.  
  10937.  Synopsis
  10938.  
  10939.            int writemsg(r, c, w, s1, s2, pg)
  10940.            int r, c;               /* starting location */
  10941.            int w;                  /* field width */
  10942.            char *s1, *s2;          /* components of the string */
  10943.            int pg;                 /* "active" screen page */
  10944.  
  10945.  Description
  10946.       The writemsg() function places a two-part message (either or both
  10947.       parts may be empty) on the specified screen page. If the combined
  10948.       string is too long to fit in the field it is truncated. If it does not
  10949.       fill the field exactly, the field is padded with spaces.
  10950.  
  10951.  Return
  10952.       The number of characters written.
  10953.  
  10954.  ───────────────────────────────────────────────────────────────────────────
  10955.  Function
  10956.       writetty--write a character using "teletype" style
  10957.  
  10958.  Synopsis
  10959.  
  10960.            int writetty(ch, attr, pg)
  10961.            unsigned char ch;       /* character */
  10962.            unsigned char attr;     /* video attribute */
  10963.            int pg;                 /* "active" screen page */
  10964.  
  10965.  Description
  10966.       The writetty() function places a character into the specified screen
  10967.       page and advances the cursor position. The screen is scrolled if
  10968.       necessary to keep the current position within the viewable screen
  10969.       boundaries. The function responds to the usual format effectors
  10970.       (backspace, carriage return, and linefeed) correctly. All other codes,
  10971.       including other control codes, are simply displayed.
  10972.  
  10973.  Return
  10974.       The value of the carry flag.
  10975.  
  10976.  Notes
  10977.       The attribute specification is honored only in text modes. It is
  10978.       simply ignored in graphics modes.
  10979.  
  10980.  ═══════════════════════════════════════════════════════════════════════════
  10981.  DOS LIBRARY
  10982.  ───────────────────────────────────────────────────────────────────────────
  10983.  Only a very limited set of DOS routines are packaged in the local DOS
  10984.  library because the standard library provides rather complete DOS access to
  10985.  disk files, the console keyboard, DOS date and time, and so on.
  10986.  
  10987.  ───────────────────────────────────────────────────────────────────────────
  10988.  Function
  10989.       drvpath--convert a drive name to a full pathname
  10990.  
  10991.  Synopsis
  10992.  
  10993.            char *drvpath(path)
  10994.            char path[];            /* path string buffer */
  10995.  
  10996.  Description
  10997.       A disk drive designation (d:) is a shorthand notation for the current
  10998.       directory on disk drive d. The drvpath() function accepts a disk drive
  10999.       designation as the initial portion of a character buffer and appends
  11000.       the full pathname of the current directory on that drive to it.
  11001.  
  11002.  Return
  11003.       A pointer to a full pathname string starting with the drive letter.
  11004.  
  11005.  Notes
  11006.       The user-supplied buffer (path) must be large enough to hold the
  11007.       longest DOS directory pathname (64 characters) plus a terminating NUL.
  11008.  
  11009.  ───────────────────────────────────────────────────────────────────────────
  11010.  Function
  11011.       first_fm--find the first matching filename
  11012.       next_fm--find the next matching filename
  11013.  
  11014.  Synopsis
  11015.  
  11016.            int first_fm(path, fa)
  11017.            int next_fm()
  11018.            char *path;             /* ambiguous pathname */
  11019.            int fa;                 /* file attributes */
  11020.  
  11021.  Description
  11022.       Under DOS, we can specify filenames ambiguously using the * and ?
  11023.       wildcard characters. The first_fm() function finds the first match in
  11024.       directory order to an ambiguous specification. If the first match is
  11025.       found, additional matches, if any, may be found by calling the
  11026.       next_fm() function repeatedly until no match is found.
  11027.  
  11028.       The file attributes specified by the fa parameter determine which
  11029.       files that match the ambiguous name will be matched. If fa equals 0,
  11030.       the functions attempt to match only ordinary files. If the volume-
  11031.       label attribute is set, the functions search only for a volume label.
  11032.       Setting fa to system, hidden, subdirectory, or any combination of
  11033.       these attributes tells the functions to match files having those
  11034.       attributes in addition to all ordinary files.
  11035.  
  11036.  Return
  11037.       Both functions return the value of the carry flag, which is 0 for
  11038.       success or nonzero for failure.
  11039.  
  11040.  Notes
  11041.       These functions use a disk transfer area of type struct DTA, which is
  11042.       defined in the header file ls.h. The DTA is established by a call to
  11043.       setdta(). If any intervening functions alter the DTA, it must be reset
  11044.       by calling setdta() before making further calls to next_fm.
  11045.  
  11046.  ───────────────────────────────────────────────────────────────────────────
  11047.  Function
  11048.       getdrive--return the ID of the default drive
  11049.  
  11050.  Synopsis
  11051.  
  11052.            int getdrive()
  11053.  
  11054.  Description
  11055.       The getdrive() function asks DOS for the ID of the current disk drive.
  11056.       The ID is the number used internally by DOS where 0 means drive A, 1
  11057.       means B, and so on.
  11058.  
  11059.  Return
  11060.       The internal drive number of the current disk drive.
  11061.  
  11062.  
  11063.  ───────────────────────────────────────────────────────────────────────────
  11064.  Function
  11065.       getkey--get a keystroke
  11066.  
  11067.  Synopsis
  11068.  
  11069.            #include <local\keydefs.h>
  11070.  
  11071.            int getkey()
  11072.  
  11073.  Description
  11074.       Gets the next available key code from the keyboard buffer. If nothing
  11075.       is ready, getkey() waits for the user to type something. If you do not
  11076.       want a "blocking" read, use keyready() (or kbhit()) to determine
  11077.       whether anything is ready to read before calling getkey().
  11078.  
  11079.  Return
  11080.       The code associated with the oldest item in the keyboard buffer. If
  11081.       the returned value is 256 or more, the input was produced by a special
  11082.       key and the returned value represents an extended key code (NUL + scan
  11083.       code).
  11084.  
  11085.  Notes
  11086.       Because getkey() uses DOS function 7 (INT 21H), it is immune to Ctrl-
  11087.       Break and it does not echo the typed input to the screen.
  11088.  
  11089.  ───────────────────────────────────────────────────────────────────────────
  11090.  Function
  11091.       keyready--check the keyboard buffer
  11092.  
  11093.  Synopsis
  11094.  
  11095.            int keyready()
  11096.  
  11097.  Description
  11098.       Checks the console for a waiting keystroke.
  11099.  
  11100.  Return
  11101.       A nonzero value if something is waiting in the keyboard buffer.
  11102.  
  11103.  Notes
  11104.       This function performs the same job as the kbhit() function in the
  11105.       standard Microsoft library. It is presented for those whose C
  11106.       compilers offer no equivalent function.
  11107.  
  11108.  ───────────────────────────────────────────────────────────────────────────
  11109.  Function
  11110.       setdta--set the disk transfer area (DTA)
  11111.  
  11112.  Synopsis
  11113.  
  11114.            void setdta(bp)
  11115.            char *bp;               /* buffer pointer */
  11116.  
  11117.  Description
  11118.       This function establishes a buffer in memory where disk transfers take
  11119.       place. There may be multiple DTAs of varying sizes but only one may be
  11120.       active at any time. If no DTA is established by a program, DOS sets up
  11121.       a 128-byte default DTA in the program segment prefix (PSP) of the
  11122.       running program.
  11123.  
  11124.  ───────────────────────────────────────────────────────────────────────────
  11125.  Function
  11126.       ver--get the DOS major and minor version numbers
  11127.  
  11128.  Synopsis
  11129.  
  11130.            int ver()
  11131.  
  11132.  Description
  11133.       Gets the DOS version major and minor numbers.
  11134.  
  11135.  Return
  11136.       The ver() function returns the DOS major version number in the low
  11137.       byte (bits 0-7) and the minor version number in the high byte (bits
  11138.       8-15).
  11139.  
  11140.  Notes
  11141.       These values duplicate the values in the global _osmajor and _osminor
  11142.       variables supported by the Microsoft C Compiler. They are here for use
  11143.       with compilers that support the bdos() function but not the global
  11144.       version variables.
  11145.  
  11146.  ═══════════════════════════════════════════════════════════════════════════
  11147.  SCREEN-BUFFER LIBRARY
  11148.  ───────────────────────────────────────────────────────────────────────────
  11149.  
  11150.  The screen-buffer library routines are described in Chapter 12. The
  11151.  routines are used to form a display image in an off-screen buffer and to
  11152.  copy the image in the buffer to display memory.
  11153.  
  11154.  ───────────────────────────────────────────────────────────────────────────
  11155.  Function
  11156.       sb_box--draw a box around a window
  11157.  
  11158.  Synopsis
  11159.  
  11160.            #include <local\sbuf.h>
  11161.            #include <local\box.h>
  11162.  
  11163.            int sb_box(win, type, attr)
  11164.            struct REGION *win;             /* target window */
  11165.            short type;                     /* line drawing type */
  11166.            unsigned char attr;             /* video attribute */
  11167.  
  11168.  Description
  11169.       Draws a box shape around a rectangular region (defined by win) in the
  11170.       screen buffer. Types are summarized in the following table:
  11171.  
  11172.  ═══════════════════════════════════════════════════════════════════════════
  11173.  Type           Description
  11174.  ───────────────────────────────────────────────────────────────────────────
  11175.  0              Default type: + for corners, | for vertical edges,
  11176.                 and - for horizontal edges
  11177.  1              Single lines all around
  11178.  2              Double lines all around
  11179.  3              Single horizontal and double vertical lines
  11180.  4              Double horizontal and single vertical lines
  11181.  5              Block shapes for all lines and corners
  11182.  ───────────────────────────────────────────────────────────────────────────
  11183.  
  11184.  Return
  11185.        SB_OK
  11186.  
  11187.  ───────────────────────────────────────────────────────────────────────────
  11188.  Function
  11189.       sb_fill--fill the scrolling region of a window
  11190.       sb_filla--fill with an attribute only
  11191.       sb_fillc--fill with a character only
  11192.  
  11193.  Synopsis
  11194.  
  11195.            #include <local\sbuf.h>
  11196.            extern struct BUFFER Sbuf;
  11197.            extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  11198.  
  11199.            int sb_fill(win, ch, attr)
  11200.            int sb_filla()
  11201.            int sb_fillc()
  11202.            struct REGION *win;             /* target window */
  11203.            unsigned char ch;               /* character */
  11204.            unsigned char attr;             /* video attribute */
  11205.  
  11206.  Description
  11207.       These three functions fill the scrolling region of a window (the
  11208.       entire window if not set explicitly). The sb_fill() function takes
  11209.       both a character and an attribute as parameters. The sb_filla()
  11210.       function sets all cells to a common video attribute while leaving the
  11211.       character in each cell undisturbed. And sb_fillc() sets all cells to a
  11212.       common character value while leaving the attribute in each cell
  11213.       undisturbed.
  11214.  
  11215.  Return
  11216.       SB_OK
  11217.  
  11218.  ───────────────────────────────────────────────────────────────────────────
  11219.  Function
  11220.       sb_init--initialize the buffered-screen interface
  11221.  
  11222.  Synopsis
  11223.  
  11224.            int sb_init()
  11225.  
  11226.  Description
  11227.       Sets up the Sbuf buffer control structure and initializes the flag
  11228.       bits and line variables. The SB_DIRECT bit is set to a logical 1 if
  11229.       the DOS variable UPDATEMODE is set to DIRECT.
  11230.  
  11231.  Return
  11232.       SB_OK if the interface can be initialized and SB_ERR if not (due to a
  11233.       bad update mode specification).
  11234.  
  11235.  ───────────────────────────────────────────────────────────────────────────
  11236.  Function
  11237.       sb_move--locate the cursor within a window
  11238.  
  11239.  Synopsis
  11240.  
  11241.            #include <local\sbuf.h>
  11242.            extern struct BUFFER Sbuf;
  11243.            extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  11244.  
  11245.            int sb_move(win, r, c)
  11246.            struct REGION *win;             /* target window */
  11247.            register short r, c;            /* row and column */
  11248.  
  11249.  Description
  11250.       The cursor is moved to the specified window-relative row and column
  11251.       (r, c). The appropriate data structures are updated to reflect the
  11252.       change.
  11253.  
  11254.  Return
  11255.       SB_OK if the cursor location is valid for the window or SB_ERR if
  11256.       either r or c is an invalid value.
  11257.  
  11258.  ───────────────────────────────────────────────────────────────────────────
  11259.  Function
  11260.       sb_new--establish a window (screen-buffer region)
  11261.  
  11262.  Synopsis
  11263.  
  11264.            #include <local\sbuf.h>
  11265.  
  11266.            struct REGION *sb_new(top, left, height, width)
  11267.            int top;                /* top row */
  11268.            int left;               /* left column */
  11269.            int height;             /* total rows */
  11270.            int width;              /* total columns */
  11271.  
  11272.  Description
  11273.       This function establishes a rectangular region of the screen buffer as
  11274.       a window, defined by the upper left corner position and the height and
  11275.       width values. The scrolling region is initially set to the window
  11276.       values, but may be altered by a call to sb_set_scrl().
  11277.  
  11278.  Return
  11279.       A pointer to a newly allocated window structure or NULL if the window
  11280.       could not be established.
  11281.  
  11282.  ───────────────────────────────────────────────────────────────────────────
  11283.  Function
  11284.       sb_putc--put a character into a window
  11285.       sb_puts--put a string into a window
  11286.  
  11287.  Synopsis
  11288.  
  11289.            #include <local\sbuf.h>
  11290.            extern struct BUFFER Sbuf;
  11291.            extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  11292.  
  11293.            int sb_putc(win, ch)
  11294.            int sb_puts(win, s)
  11295.            struct REGION *win;     /* target window */
  11296.            char ch;
  11297.            unsigned char *s;       /* text string */
  11298.  
  11299.  Description
  11300.       The sb_putc() function puts the specified character into the screen-
  11301.       buffer array at the current window position and advances the window
  11302.       "cursor." If the character is a newline, the remainder of the current
  11303.       window row is cleared and the cursor advances to the first position of
  11304.       the next row. If scrolling is enabled and is required to keep the
  11305.       cursor inside the window, sb_scrl() is called and the cursor lands in
  11306.       the first position of the last window row.
  11307.  
  11308.       A call to sb_puts is treated as a call to sb_putc for each character
  11309.       in the string. A tab is expanded to spaces using a series of calls to
  11310.       sb_putc(). Expansion is limited to the current window row.
  11311.  
  11312.  Return
  11313.       SB_OK or SB_ERR if an error occurs.
  11314.  
  11315.  ───────────────────────────────────────────────────────────────────────────
  11316.  Function
  11317.       sb_ra--read attribute
  11318.       sb_rc--read character
  11319.       sb_rca--read character and attribute
  11320.  
  11321.  Synopsis
  11322.  
  11323.            #include <local\sbuf.h>
  11324.            extern struct BUFFER Sbuf;
  11325.            extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  11326.  
  11327.            unsigned char sb_ra(win)
  11328.            unsigned char sb_rc(win)
  11329.            unsigned short sb_rca(win)
  11330.  
  11331.            union REGION *win;              /* target window */
  11332.  
  11333.  Description
  11334.       Each of these functions is passed a window pointer and window-relative
  11335.       row and column values of a cell, and returns information about the
  11336.       specified cell. The sb_ra() function obtains only the video attribute;
  11337.       sb_rc() obtains only the character value. However, sb_rca() gathers
  11338.       both the character and the video attribute of the specified cell.
  11339.  
  11340.  Return
  11341.       The sb_ra() and sb_rc() functions return a byte that contains the
  11342.       attribute or character at the current window cursor position. A word
  11343.       containing both the character and attribute is returned by sb_rca().
  11344.  
  11345.  ───────────────────────────────────────────────────────────────────────────
  11346.  Function
  11347.       sb_scrl--scroll a window region
  11348.       sb_set_scrl--change the defined scrolling region
  11349.  
  11350.  Synopsis
  11351.  
  11352.            #include <local\sbuf.h>
  11353.            extern struct BUFFER Sbuf;
  11354.            extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  11355.  
  11356.            int sb_scrl(win, n)
  11357.            int sb_set_scrl(win, top, left, bottom, right)
  11358.  
  11359.            union REGION *win;              /* target window */
  11360.            short top, left;                /* upper left corner */
  11361.            short bottom, right;            /* lower right corner */
  11362.            short n;                        /* number of rows to scroll */
  11363.  
  11364.  Description
  11365.       If n is greater than 0, sb_scrl() scrolls the specified window down by
  11366.       n rows. A negative n causes upward scrolling. Vacated lines are filled
  11367.       with spaces in the prevailing attribute. If n equals 0, the entire
  11368.       scrolling region is set to spaces.
  11369.  
  11370.       Scrolling occurs within a window's scrolling region. The sb_set_scrl()
  11371.       function defines the scrolling region, which may be any size up to the
  11372.       full window size.
  11373.  
  11374.  Return
  11375.       SB_OK. If an illegal scrolling region is specified, SB_ERR is returned
  11376.       by sb_set_scrl().
  11377.  
  11378.  ───────────────────────────────────────────────────────────────────────────
  11379.  Function
  11380.       sb_show--copy the screen buffer to display memory
  11381.  
  11382.  Synopsis
  11383.  
  11384.            #include <local\sbuf.h>
  11385.            extern struct BUFFER Sbuf;
  11386.            extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  11387.  
  11388.            int sb_show(pg)
  11389.            short pg;
  11390.  
  11391.  Description
  11392.       The screen-buffer array is copied to display memory by sb_show(),
  11393.       which uses either BIOS routines or direct display memory accesses,
  11394.       depending on the value of the variable UPDATEMODE (BIOS or DIRECT)
  11395.       in the DOS environment.
  11396.  
  11397.  Return
  11398.       SB_OK
  11399.  
  11400.  ───────────────────────────────────────────────────────────────────────────
  11401.  Function
  11402.       sb_wa--write an attribute
  11403.       sb_wc--write a character
  11404.       sb_wca--write a character and attribute
  11405.  
  11406.  Synopsis
  11407.  
  11408.            #include <local\sbuf.h>
  11409.            extern struct BUFFER Sbuf;
  11410.            extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  11411.  
  11412.            int sb_wa(win, attr, n)
  11413.            int sb_wc(win, ch, n)
  11414.            int sb_wca(win, ch, attr, n)
  11415.            union REGION *win;              /* target window */
  11416.            unsigned char ch;               /* character */
  11417.            unsigned char attr;             /* video attribute */
  11418.            short n;                        /* number of repetitions */
  11419.  
  11420.  Description
  11421.       These functions place characters and attributes in the screen-buffer
  11422.       array. The window cursor position is not changed. The repetition
  11423.       number specifies the number of buffer cells that are affected.
  11424.  
  11425.  Return
  11426.       SB_OK if the write is successful or SB_ERR if an error occurs.
  11427.  
  11428.  Notes
  11429.       These functions are valid only within the current row. Behavior is
  11430.       undefined if writes are attempted outside the boundaries of the
  11431.       specified window.
  11432.  
  11433.  ═══════════════════════════════════════════════════════════════════════════
  11434.  UTILITY LIBRARY
  11435.  ───────────────────────────────────────────────────────────────────────────
  11436.  The development of the utility library starts in Chapter 3 and continues
  11437.  throughout the book. These routines are usable in a variety of programming
  11438.  situations and are not easily classified in any of the foregoing
  11439.  categories.
  11440.  
  11441.  ───────────────────────────────────────────────────────────────────────────
  11442.  Function
  11443.       beep--issue a standard terminal beep (Ctrl-G)
  11444.  
  11445.  Synopsis
  11446.  
  11447.            void beep()
  11448.  
  11449.  Description
  11450.       Call beep() to sound the PC's internal speaker.
  11451.  
  11452.  ───────────────────────────────────────────────────────────────────────────
  11453.  Function
  11454.       byte2hex--convert a byte value to hexadecimal
  11455.  
  11456.  Synopsis
  11457.  
  11458.            char *byte2hex(data, buf)
  11459.            unsigned char data;             /* 1-byte data item */
  11460.            char *buf;                      /* hex string buffer */
  11461.  
  11462.  Description
  11463.       The byte2hex() function converts a 1-byte numeric value to a 2-byte
  11464.       hexadecimal representation.
  11465.  
  11466.  Return
  11467.       A pointer to the resulting hexadecimal string.
  11468.  
  11469.  Notes
  11470.       The calling function must provide a buffer that is large enough to
  11471.       receive the 2-byte string and the terminating null byte.
  11472.  
  11473.  ───────────────────────────────────────────────────────────────────────────
  11474.  Function
  11475.       clrprnt--clear printer to default settings
  11476.  
  11477.  Synopsis
  11478.  
  11479.            #include <local\printer.h>
  11480.  
  11481.            int clrprnt(fout)
  11482.            FILE *fout;
  11483.  
  11484.  Description
  11485.       The clrprnt() function sends codes needed to set an attached printer
  11486.       back to its power-on default values for the major print modes. It does
  11487.       so by individually clearing each mode rather than sending a printer
  11488.       reset command to avoid "paper creep" and alteration of the top-of-form
  11489.       setting.
  11490.  
  11491.  Return
  11492.       SUCCESS if all goes well or FAILURE if any attempt at writing to the
  11493.       output stream fails.
  11494.  
  11495.  ───────────────────────────────────────────────────────────────────────────
  11496.  Function
  11497.       fatal--print an error message and exit
  11498.  
  11499.  Synopsis
  11500.  
  11501.            void fatal(pname, str, errlvl)
  11502.            char *pname;
  11503.            char *str;
  11504.            int errlvl;
  11505.  
  11506.  Description
  11507.       If an error occurs in a program, fatal() can be called to display an
  11508.       error message and then exit with a status (ERRORLEVEL in DOS terms)
  11509.       other than 0 to indicate an error. The usual error code is 1, but
  11510.       other values can be used if a program can fail in more ways than one.
  11511.  
  11512.  ───────────────────────────────────────────────────────────────────────────
  11513.  Function
  11514.       fcopy--copy input file to output file
  11515.  
  11516.  Synopsis
  11517.  
  11518.            int fcopy(fin, fout)
  11519.            FILE *fin, *fout;
  11520.  
  11521.  Description
  11522.       This function copies an input stream (FILE *) to an output stream
  11523.       (FILE *) using the standard library functions fgets() and fputs()
  11524.       and a private buffer of BUFSIZ bytes. The ferror() macro is used to
  11525.       check for input errors and testing is done for the unambiguous EOF
  11526.       on the output stream.
  11527.  
  11528.  Return
  11529.       A successful file copy operation produces a 0 return value. Any input
  11530.       or output error that is detected causes a nonzero return.
  11531.  
  11532.  ───────────────────────────────────────────────────────────────────────────
  11533.  Function
  11534.       fixtabs--set fixed-interval tab stops
  11535.  
  11536.  Synopsis
  11537.  
  11538.            void fixtabs(interval)
  11539.            int interval;           /* tabbing interval */
  11540.  
  11541.  Description
  11542.       A set of fixed tab stops can be set using fixtabs() to initialize the
  11543.       private Tabstops array and then set tab stops at the specified
  11544.       intervals. Internally, the Tabstops array has tab stops at column 0
  11545.       and repeating at the specified interval. Standard terminal usage
  11546.       has an interval of 8 columns.
  11547.  
  11548.  ───────────────────────────────────────────────────────────────────────────
  11549.  Function
  11550.       getname--extract filename[.ext] from a pathname
  11551.  
  11552.  Synopsis
  11553.  
  11554.            char *getname(path)
  11555.            char *path;
  11556.  
  11557.  Description
  11558.       This function extracts a full filespec (filename[.ext]) from a
  11559.       pathname. It checks to see that the pathname given as a parameter is a
  11560.       valid pathname. Path separators of \ and / are accepted and may be
  11561.       intermixed.
  11562.  
  11563.  Return
  11564.       A pointer to a valid filespec or NULL if the path parameter is not a
  11565.       valid pathname.
  11566.  
  11567.  ───────────────────────────────────────────────────────────────────────────
  11568.  Function
  11569.       getopt--get option letters and option arguments
  11570.  
  11571.  Synopsis
  11572.  
  11573.            extern int optind;
  11574.            extern int opterr;
  11575.            extern char *optarg;
  11576.  
  11577.            int getopt(argc, argv, opts)
  11578.            int argc;               /* argument count */
  11579.            char *argv[];           /* argument vector array */
  11580.            char *opts;             /* option string */
  11581.  
  11582.  Description
  11583.       The string variable opts is a list of recognized option letters.
  11584.       Option letters that are followed by a colon are expected to take an
  11585.       argument. On each successive call to getopt(), the function returns
  11586.       the next option letter in argv that matches a letter in opts. The
  11587.       optarg variable points to an option argument, if any is expected and
  11588.       found or NULL otherwise.
  11589.  
  11590.       When all options have been processed, getopt() returns EOF. The
  11591.       special option "--" (double dash) may be used to mark the end of
  11592.       options. When found, this causes getopt() to skip the special option
  11593.       and return EOF.
  11594.  
  11595.  Return
  11596.       If an option letter in argv is not found in the opts string, getopt()
  11597.       returns a question mark and prints an error message. Setting opterr to
  11598.       0 silences getopt().
  11599.  
  11600.  Notes
  11601.       This getopt() is the public domain version that is provided by the
  11602.       AT&T UNIX System Toolchest. It is presented in this book with the
  11603.       permission of AT&T in the interest of promoting a common command-line
  11604.       interface for programs.
  11605.  
  11606.  ───────────────────────────────────────────────────────────────────────────
  11607.  Function
  11608.       getpname--extract a program name from a pathname
  11609.  
  11610.  Synopsis
  11611.  
  11612.            void getpname(path, pname)
  11613.            char *path;             /* full or relative pathname */
  11614.            char *pname;            /* filename part of program name */
  11615.  
  11616.  Description
  11617.       The getpname() function receives a full or relative pathname. It
  11618.       strips off any leading path information and any trailing extension.
  11619.       This function accepts both \ and / as path separators. It does not
  11620.       check to see that the path parameter is a valid path.
  11621.  
  11622.  ───────────────────────────────────────────────────────────────────────────
  11623.  Function
  11624.       getxline--get an expanded line of text
  11625.  
  11626.  Synopsis
  11627.  
  11628.            char *getxline(buf, size, fin)
  11629.            char *buf;              /* output buffer */
  11630.            int size;               /* maximum number of bytes */
  11631.            FILE *fin;              /* input stream */
  11632.  
  11633.  Description
  11634.       The getxline() function receives a line of text as input and "expands"
  11635.       the line by converting each tab to the correct number of spaces. The
  11636.       total character count may thus be altered, but the expanded line
  11637.       appears to be visually identical to the input line when displayed.
  11638.  
  11639.  Return
  11640.       A pointer to the buffer that contains the expanded line or NULL if an
  11641.       error occurs.
  11642.  
  11643.  Notes
  11644.       This function queries the tabstop array, so you must first call either
  11645.       fixtabs() or vartabs() to set tabs.
  11646.  
  11647.  ───────────────────────────────────────────────────────────────────────────
  11648.  Function
  11649.       fconfig--get pointer to a configuration file
  11650.  
  11651.  Synopsis
  11652.  
  11653.            FILE *fconfig(varname, fname)
  11654.            char *varname;          /* DOS environment variable name */
  11655.            char *fname;            /* file name */
  11656.  
  11657.  Description
  11658.       This function looks for a configuration file by the name fname. It
  11659.       looks first in the current directory and then in a directory pointed
  11660.       to by the varname DOS variable.
  11661.  
  11662.  Return
  11663.       A pointer to the configuration file if one is found or a NULL pointer
  11664.       if not.
  11665.  
  11666.  ───────────────────────────────────────────────────────────────────────────
  11667.  Function
  11668.       last_ch--get the last character of a string
  11669.  
  11670.  Synopsis
  11671.  
  11672.            char last_ch(s)
  11673.            char *s;                /* source string */
  11674.  
  11675.  Description
  11676.       The last_ch() function finds the last character of the source string,
  11677.       s (just before the NUL termination).
  11678.  
  11679.  Return
  11680.       The character value or -1 if the string is empty.
  11681.  
  11682.  ───────────────────────────────────────────────────────────────────────────
  11683.  Function
  11684.       lines--send blank lines to an output stream
  11685.  
  11686.  Synopsis
  11687.  
  11688.            int lines(n, fp)
  11689.            int n;                  /* number of repetitions */
  11690.            FILE *fp;               /* output stream */
  11691.  
  11692.  Description
  11693.       The lines() function places n newline characters on the output stream.
  11694.  
  11695.  Return
  11696.       The number of newlines actually emitted, which may be less than the
  11697.       number requested if an error occurs on the output stream.
  11698.  
  11699.  ───────────────────────────────────────────────────────────────────────────
  11700.  Function
  11701.       memchk--check for physical memory at an address
  11702.  
  11703.  Synopsis
  11704.  
  11705.            int memchk(seg, os)
  11706.            unsigned int seg;       /* segment */
  11707.            unsigned int os;        /* offset */
  11708.  
  11709.  Description
  11710.       By sequentially writing and reading the memory location specified by
  11711.       the segmented address, memchk() attempts to determine whether any
  11712.       physical memory is installed. To prevent clobbering needed memory
  11713.       values, the original value is preserved and restored upon completion
  11714.       of the test.
  11715.  
  11716.  Return
  11717.       If active memory is found at the specified address, memchk() returns a
  11718.       1. If no active memory is found, 0 is returned.
  11719.  
  11720.  ───────────────────────────────────────────────────────────────────────────
  11721.  Function
  11722.       mkslist--create a selection list (lookup table)
  11723.  
  11724.  Synopsis
  11725.  
  11726.            extern long Highest;
  11727.  
  11728.            int mkslist(list)
  11729.            char *list;             /* pointer to a list in string form */
  11730.  
  11731.  Description
  11732.       The mkslist() function extracts tokens from a string representation of
  11733.       a list of numbers, converts the tokens to numeric values, and stores
  11734.       the values as a set of numeric ranges in an array (up to 10 ranges
  11735.       permitted by the current design).
  11736.  
  11737.       Single numbers are represented as ranges having the same starting and
  11738.       ending number. Open-ended ranges (for example, 3-) are represented as
  11739.       the starting number to some unreasonably high ending value (BIGGEST).
  11740.       The list of ranges is terminated by a -1 starting value in the last
  11741.       array element following the last valid range.
  11742.  
  11743.  Return
  11744.       Always zero.
  11745.  ───────────────────────────────────────────────────────────────────────────
  11746.  Function
  11747.       nlerase--erase a newline in a string
  11748.  
  11749.  Synopsis
  11750.  
  11751.            char *nlerase(s)
  11752.            char *s;
  11753.  
  11754.  Description
  11755.       The nlerase() function scans a string and replaces the first newline
  11756.       it finds with a NUL (\0) character, effectively terminating the
  11757.       string and erasing the newline.
  11758.  
  11759.  Return
  11760.       A pointer to the modified string.
  11761.  
  11762.  ───────────────────────────────────────────────────────────────────────────
  11763.  Function
  11764.       selected--determine whether a number is in a range
  11765.  
  11766.  Synopsis
  11767.  
  11768.            int selected(n)
  11769.            long n;                 /* number to test */
  11770.  
  11771.  Description
  11772.       The selected() function queries the select-list array, Slist, which is
  11773.       established by mkslist(), to determine whether the number parameter is
  11774.       a member of one of the ranges in the list.
  11775.  
  11776.  Return
  11777.       Returns 1 if the number is in one of the ranges and 0 otherwise.
  11778.  
  11779.  ───────────────────────────────────────────────────────────────────────────
  11780.  Function
  11781.       setfont--select a printer font
  11782.  
  11783.  Synopsis
  11784.  
  11785.            #include <local\printer.h>
  11786.  
  11787.            int setfont(ftype, fout)
  11788.            int ftype;              /* font specifier */
  11789.            FILE *fout;             /* output stream */
  11790.  
  11791.  Description
  11792.       This function is used to select a printer font type. The standard font
  11793.       is a 10 cpi (characters per inch) type and it can be obtained by
  11794.       list calling clrprnt(). The special types are chosen from the
  11795.       following list (based on Epson MX/FX-series printer codes):
  11796.  
  11797.  ───────────────────────────────────────────────────────────────────────────
  11798.  Symbolic Name       Description
  11799.  ───────────────────────────────────────────────────────────────────────────
  11800.  CONDENSED           A small but very readable type at about 14 cpi (136
  11801.                      characters per line)
  11802.  DOUBLE              Double strike
  11803.  EMPHASIZED          Emboldened by microspacing and restriking
  11804.  EXPANDED            Wider than normal characters (6 cpi)
  11805.  ITALICS             True italic characters (not supported by all Epson-
  11806.                      compatible printers)
  11807.  UNDERLINE           Continuous underline (without the need to backspace and
  11808.                      restrike the character)
  11809.  ───────────────────────────────────────────────────────────────────────────
  11810.  
  11811.  Return
  11812.       The setfont() function returns SUCCESS to indicate apparent success,
  11813.       or FAILURE if an error occurs or an illegal font combination is
  11814.       requested.
  11815.  
  11816.  Notes
  11817.       The CONDENSED font cannot be used with either DOUBLE or EMPHASIZED on
  11818.       most Epson-compatible printers, so such combinations are disallowed.
  11819.  
  11820.  ───────────────────────────────────────────────────────────────────────────
  11821.  Function
  11822.       setprnt--install printer codes
  11823.  
  11824.  Synopsis
  11825.  
  11826.            #include <local\printer.h>
  11827.  
  11828.            int setprnt()
  11829.  
  11830.  Description
  11831.       This function installs printer codes from printer configuration files
  11832.       or from internal defaults (for Epson MX/FX series and compatible
  11833.       printers).
  11834.  
  11835.  Return
  11836.       The setprnt() function returns SUCCESS to indicate apparent success,
  11837.       or FAILURE if an error occurs. The only detectable failures are too
  11838.       many or too few lines in a configuration file.
  11839.  
  11840.  ───────────────────────────────────────────────────────────────────────────
  11841.  Function
  11842.       spaces--send spaces to an output stream
  11843.  
  11844.  Synopsis
  11845.  
  11846.            int spaces(n, fp)
  11847.            int n;                  /* number of repetitions */
  11848.            FILE *fp;               /* output stream */
  11849.  
  11850.  Description
  11851.       The spaces() function places n space (blank) characters on the output
  11852.       stream.
  11853.  
  11854.  Return
  11855.       The number of spaces actually emitted, which may be less than the
  11856.       number requested if an error occurs on the output stream.
  11857.  
  11858.  ───────────────────────────────────────────────────────────────────────────
  11859.  Function
  11860.       tabstop--determine whether col is a tab stop
  11861.  
  11862.  Synopsis
  11863.  
  11864.            int tabstop(col)
  11865.            register int col;
  11866.  
  11867.  Description
  11868.       The tabstop() function queries the private Tabstops array to determine
  11869.       whether the specified column, col, is a tab stop.
  11870.  
  11871.  Return
  11872.       A nonzero value if col is a tab stop or 0 if it is not.
  11873.  
  11874.  ───────────────────────────────────────────────────────────────────────────
  11875.  Function
  11876.       vartabs--set variable tabs from a list
  11877.  
  11878.  Synopsis
  11879.  
  11880.            void vartabs(list)
  11881.            int *list;              /* pointer to an array of tab stops */
  11882.  
  11883.  Description
  11884.       A set of tab stops can be set from a list using vartabs() to
  11885.       initialize the private Tabstops array and then set the specified tabs.
  11886.  
  11887.  ───────────────────────────────────────────────────────────────────────────
  11888.  Function
  11889.       word2hex--convert a word value to hexadecimal
  11890.  
  11891.  Synopsis
  11892.  
  11893.            char *word2hex(data, buf)
  11894.            unsigned short data;            /* 2-byte data item */
  11895.            char *buf;                      /* hex string buffer */
  11896.  
  11897.  Description
  11898.       The word2hex() function converts a 2-byte numeric value to a 4-byte
  11899.       hexadecimal representation.
  11900.  
  11901.  Return
  11902.       A pointer to the resulting hexadecimal string.
  11903.  
  11904.  Notes
  11905.       The calling function must provide a buffer that is large enough to
  11906.       receive the 4-byte string and the terminating null byte.
  11907.  ───────────────────────────────────────────────────────────────────────────
  11908.  
  11909.  
  11910.  
  11911.  Augie Hansen
  11912.       Augie Hansen started programming on IBM mainframes more
  11913.  than 20 years ago and since then has been involved with
  11914.  computers and programming at various companies, including
  11915.  General Dynamics, Raytheon Company, and E.G. & G., Inc. In
  11916.  addition, Augie spent 7 years with AT&T Bell Laboratories,
  11917.  specializing in UNIX and C programming. He founded and is
  11918.  the president of Omniware, a company that provides academic
  11919.  and commercial training courses on UNIX, C, and MS/PC-DOS
  11920.  and custom programming consulting. Augie is a contributing
  11921.  editor of PC Tech Journal and has written one other book,
  11922.  vi--The UNIX Screen Editor, published in 1986 by Brady
  11923.  Books/Prentice-Hall Press.
  11924.  
  11925.  
  11926.  
  11927.  FIGURE 6-2 ▐ Manual page for CAT
  11928.  ───────────────────────────────────────────────────────────────────────────
  11929.  
  11930.  NAME
  11931.  
  11932.       CAT--concatenate file
  11933.  
  11934.  FORMAT
  11935.  
  11936.       cat [ -s ] [ d: [ path [ filename [ .ext] ] ] ... ]
  11937.  
  11938.  DESCRIPTION
  11939.  
  11940.       The CAT command accepts the -s option (silent), which tells the
  11941.       program not to complain about missing files (this may be useful in
  11942.       batch files). If no filenames are specified, CAT reads from the
  11943.       standard input. DOS wildcard characters (? and *) may be used to
  11944.       specify ambiguous filenames.
  11945.  
  11946.  
  11947.  EXAMPLES
  11948.  
  11949.       Display the contents of a single file.
  11950.  
  11951.       cat hello.c
  11952.  
  11953.       Concatenate all C source files in the current directory into a single
  11954.       source file.
  11955.  
  11956.       cat *.c >program.c
  11957.  
  11958.       Create a new file with text typed by the user.
  11959.  
  11960.       cat >myfile.txt
  11961.       This is a test.
  11962.       ^Z
  11963.  
  11964.  
  11965.  
  11966.  FIGURE 6-3 ▐ Manual page for TIMER
  11967.  ───────────────────────────────────────────────────────────────────────────
  11968.  
  11969.  NAME
  11970.  
  11971.       TIMER--provide timing data for up to four events
  11972.  
  11973.  SYNOPSIS
  11974.  
  11975.       timer [-efs#]
  11976.  
  11977.  DESCRIPTION
  11978.  
  11979.       TIMER uses command-line options to select program actions. The option
  11980.       letters may be combined following a single option flag (-) or invoked
  11981.       separately. If the f option is combined with other options, it must be
  11982.       last. These options are honored by TIMER:
  11983.  
  11984.       ══════════════════════════════════════════════════════════════════════
  11985.       Option     Meaning
  11986.       ──────────────────────────────────────────────────────────────────────
  11987.  
  11988.       -s         Start (or restart) a timer
  11989.       -e         Display or record elapsed time
  11990.       -f file    Record the output of the TIMER command in file
  11991.       -#         The # symbol is a numeric parameter in the range of 0 to 3
  11992.                  that selects one of the four timers. If no number is
  11993.                  specified, TIMER uses 0 by default.
  11994.       ──────────────────────────────────────────────────────────────────────
  11995.  
  11996.  EXAMPLES
  11997.  
  11998.       Start the default timer:
  11999.  
  12000.       timer -s
  12001.  
  12002.       Restart timer 0 and record the output in the file project.dat:
  12003.  
  12004.       timer -s -f project.dat
  12005.  
  12006.  NOTES
  12007.  
  12008.       TIMER uses the intra-application communication area to hold starting
  12009.       times for each of the timers. If another program uses the same memory
  12010.       locations, timer data could be corrupted. When asked to produce an
  12011.       elapsed time, TIMER tries to detect corrupt data and outputs an error
  12012.       message rather than a time value.
  12013.  
  12014.  
  12015.  
  12016.  FIGURE 7-1 ▐ Manual page for MX
  12017.  ───────────────────────────────────────────────────────────────────────────
  12018.  
  12019.  NAME
  12020.  
  12021.       MX--control printer modes
  12022.  
  12023.  SYNOPSIS
  12024.  
  12025.       mx -option(s)
  12026.  
  12027.  DESCRIPTION
  12028.  
  12029.       The MX program sends mode-setting control strings to a printer under
  12030.       control of options. Most of the modes may be set individually or in
  12031.       combinations of two or more. It should be obvious that normal (-n)
  12032.       should be used by itself. Less obvious is that condensed (-c) and
  12033.       either bold (-b) or double-strike (-d) cannot be used together. Within
  12034.       the limitations noted above, options listed below may be used singly
  12035.       or combined in any order:
  12036.  
  12037.  ═══════════════════════════════════════════════════════════════════════════
  12038.       Option     Meaning
  12039.       ──────────────────────────────────────────────────────────────────────
  12040.  
  12041.       -b         Select bold (emphasized) mode
  12042.       -c         Select compressed mode
  12043.       -d         Select double-strike mode
  12044.       -e         Select expanded mode
  12045.       -i         Select italic font
  12046.       -n         Select normal font. This command option clears all special
  12047.                  font and print-mode selections.
  12048.       -o file    Use the specified output file or stream
  12049.       -p         Preview output on screen (may be redirected)
  12050.       -r         Issue a hardware reset command. This clears all attributes
  12051.                  and establishes the current position as the new top-of-form
  12052.                  reference.
  12053.       -t         Eject a page (top-of-form command) by issuing a formfeed
  12054.       -u         Select underline mode
  12055.       ______________________________________________________________________
  12056.  
  12057.       The reset (-r) and top-of-form (-t) options have temporal priority
  12058.       over any other options invoked in the same call to MX. Reset is done
  12059.       first, then formfeed, then font selection. Printer configuration can
  12060.       be controlled via optional local and global configuration files (local
  12061.       overrides global).
  12062.  
  12063.  EXAMPLES
  12064.  
  12065.       Request compressed and italicized printing:
  12066.  
  12067.       mx -ci
  12068.  
  12069.       Eject a page:
  12070.  
  12071.       mx -t
  12072.  
  12073.  
  12074.  
  12075.  FIGURE 7-2 ▐ Manual page for PRTSTR
  12076.  ───────────────────────────────────────────────────────────────────────────
  12077.  
  12078.  NAME
  12079.  
  12080.       PRTSTR--print string(s) on standard printer
  12081.  
  12082.  SYNOPSIS
  12083.  
  12084.       prtstr [options ] [ string ... ]
  12085.  
  12086.  DESCRIPTION
  12087.  
  12088.       Use PRTSTR to send a string or group of strings given as arguments to
  12089.       the standard printer device (stdprn). PRTSTR appends one space after
  12090.       each argument and issues a newline to terminate the line. Use quotes
  12091.       to protect embedded special characters, such as tabs or spaces, in the
  12092.       text-argument list. The following options may be used separately or
  12093.       together to alter the default behavior:
  12094.  
  12095.       ══════════════════════════════════════════════════════════════════════
  12096.       Option     Meaning
  12097.       ──────────────────────────────────────────────────────────────────────
  12098.  
  12099.       -n         Suppress newline and trailing space.
  12100.       -p         Preview output on screen (may be redirected).
  12101.       ──────────────────────────────────────────────────────────────────────
  12102.  
  12103.  EXAMPLES
  12104.  
  12105.       Send the line C is a fantastic language! to the standard printer
  12106.       device (notice that no quotes are needed):
  12107.  
  12108.       prtstr C is a fantastic language!
  12109.  
  12110.       Send the string BASEBALL to a file:
  12111.  
  12112.       prtstr -p BASEBALL > baseball.txt
  12113.  
  12114.       Print some text and stop on the same line:
  12115.  
  12116.       prtstr -n "This is a test: "
  12117.  
  12118.  
  12119.  
  12120.  FIGURE 8-1 ▐ Manual page for TOUCH
  12121.  ───────────────────────────────────────────────────────────────────────────
  12122.  
  12123.  NAME
  12124.  
  12125.       TOUCH--update file modification time(s)
  12126.  
  12127.  SYNOPSIS
  12128.  
  12129.       touch [-cv] file ...
  12130.  
  12131.  DESCRIPTION
  12132.  
  12133.       The TOUCH command updates the file modification times associated with
  12134.       a file or files. Under DOS, the last modification time (and last
  12135.       access time for DOS version 3.00 and later) maintained in the
  12136.       directory entry for a file are identical. TOUCH sets the file time to
  12137.       the current DOS time. TOUCH accepts the following command-line
  12138.       options, singly or in combination:
  12139.  
  12140.       ══════════════════════════════════════════════════════════════════════
  12141.       Option     Meaning
  12142.       ──────────────────────────────────────────────────────────────────────
  12143.  
  12144.       -c         Control file creation. TOUCH usually creates a named file
  12145.                  if it does exist. This option tells TOUCH not to create
  12146.                  files.
  12147.       -v         Verbose mode. Tells TOUCH to send information to the
  12148.                  standard error stream about file times that have been
  12149.                  successfully updated, about files that were created, and so
  12150.                  forth.
  12151.       ──────────────────────────────────────────────────────────────────────
  12152.  
  12153.  EXAMPLES
  12154.  
  12155.       Update the modification times of all C source files in the current
  12156.       directory and tell the user what's going on:
  12157.  
  12158.       touch -v *.c
  12159.  
  12160.  
  12161.       Create a set of empty files in the current directory (assumes these
  12162.       files do not exist yet):
  12163.  
  12164.       touch file1.c file2.c file3.c
  12165.  
  12166.  
  12167.  
  12168.  FIGURE 8-2 ▐ Manual page for TEE
  12169.  ───────────────────────────────────────────────────────────────────────────
  12170.  
  12171.  NAME
  12172.  
  12173.       TEE--split a stream into multiple streams
  12174.  
  12175.  SYNOPSIS
  12176.  
  12177.       tee [-a] [file ... ]
  12178.  
  12179.  DESCRIPTION
  12180.  
  12181.       TEE copies its standard input onto its standard output. If any files
  12182.       are named, they too become destinations for the output of TEE. The
  12183.       files are created if necessary. If they already exist, their prior
  12184.       contents are lost unless the -a option (append) is given on the
  12185.       command line.
  12186.  
  12187.  EXAMPLES
  12188.  
  12189.       Display the contents of a file (FILE1) and simultaneously copy it to
  12190.       another file (FILE2):
  12191.  
  12192.       type file1 tee file2
  12193.  
  12194.       Use CAT to display the file timer.c on the console and append
  12195.       a copy of the output to two existing files (SRCFILE1 and
  12196.       SRCFILE2):
  12197.  
  12198.       cat timer.c tee -a srcfile1 srcfile2
  12199.  
  12200.  NOTES
  12201.  
  12202.       TEE does not work with files that contain binary data. As designed, it
  12203.       handles only ASCII text.
  12204.  
  12205.       The number of destination files is limited by the operating system and
  12206.       by the use of the value _NFILE in the stdio.h header file (_NFILE is
  12207.       typically 20). There are five opened files (stdin, stdout, stderr,
  12208.       stdaux, and stdprn) when a program starts executing, which reduces the
  12209.       number of files you can open.
  12210.  
  12211.  
  12212.  
  12213.  FIGURE 8-3 ▐ Manual page for PWD
  12214.  ───────────────────────────────────────────────────────────────────────────
  12215.  
  12216.  NAME
  12217.  
  12218.       PWD--print working directory
  12219.  
  12220.  
  12221.  SYNOPSIS
  12222.  
  12223.       pwd
  12224.  
  12225.  DESCRIPTION
  12226.  
  12227.       PWD displays the full pathname of the current ("working") directory.
  12228.       It is designed to ease the transition between UNIX/XENIX and DOS. It
  12229.       provides the same function as the DOScommand CD if CD is typed without
  12230.       a pathname argument.
  12231.  
  12232.  EXAMPLE
  12233.  
  12234.       Display the current directory pathname:
  12235.  
  12236.       pwd
  12237.  
  12238.  NOTES
  12239.  
  12240.       Under UNIX and XENIX, the CD command typed without a pathname argument
  12241.       moves the user's context to the "home" directory, which might surprise
  12242.       an experienced DOS user trying to get the current pathname by the
  12243.       standard technique.
  12244.  
  12245.  
  12246.  
  12247.  FIGURE 8-4 ▐ Manual page for RM
  12248.  ───────────────────────────────────────────────────────────────────────────
  12249.  
  12250.  NAME
  12251.  
  12252.       RM--remove file(s)
  12253.  
  12254.  SYNOPSIS
  12255.  
  12256.       rm [-i] file ...
  12257.  
  12258.  DESCRIPTION
  12259.  
  12260.       RM removes a file or a group of files. Each file specification may be
  12261.       a full or relative pathname and may contain wildcards to name files
  12262.       ambiguously.
  12263.       The -i option causes RM to operate in an interactive mode. The program
  12264.       prompts the user to confirm each removal before it is executed. A
  12265.       response that starts with y or Y for "yes" confirms the removal. Any
  12266.       other initial character is interpreted as a "no" response.
  12267.  
  12268.  EXAMPLES
  12269.  
  12270.       Remove all backup files in the current directory:
  12271.  
  12272.       rm *.bak
  12273.  
  12274.       Remove object files and "map" files in the current directory
  12275.       interactively:
  12276.  
  12277.       rm -i *.obj *.map
  12278.  
  12279.  
  12280.  
  12281.  FIGURE 8-5 ▐ Manual page for LS
  12282.  ───────────────────────────────────────────────────────────────────────────
  12283.  
  12284.  NAME
  12285.  
  12286.       LS--list information about a directory
  12287.  
  12288.  SYNOPSIS
  12289.  
  12290.       ls [-aCFlrt] [filespec]
  12291.  
  12292.  DESCRIPTION
  12293.  
  12294.       The LS command is a general-purpose directory lister. It has enough
  12295.       features to be useful without being threateningly complex. The most
  12296.       common use of LS is to find out what files are in the current
  12297.       directory. This requires no argument on the command line. To get
  12298.       greater amounts of detail or to modify the output of LS in some way,
  12299.       use one or more of the following options separately or grouped in any
  12300.       meaningful combination.
  12301.  
  12302.       ══════════════════════════════════════════════════════════════════════
  12303.       Option     Meaning
  12304.       ──────────────────────────────────────────────────────────────────────
  12305.  
  12306.       -a         All files in the named directory are listed, even those
  12307.                  marked hidden or system. The default is to shield hidden
  12308.                  and system files from view.
  12309.       -C         Produce multi-column output with items sorted down each
  12310.                  column. Without this option, single-column output is
  12311.                  employed. This option causes output to be in the largest
  12312.                  number of columns possible (determined by the length of the
  12313.                  longest item name).
  12314.       -F         Show file type in the output listing. Directories are
  12315.                  marked by a trailing backslash (mydir\).
  12316.       -l         Produce a single-column "long" listing, including a summary
  12317.                  of mode bits, file date and time stamp, and file size in
  12318.                  bytes. This option overrides the -C and -F options.
  12319.       -r         Reverse the sorting order. Works for the default sorting of
  12320.                  file and directory names and for the sort on file
  12321.                  modification time data (see -t option).
  12322.       -t         Sort by file time rather than name. The default is oldest
  12323.                  first, but most-recent-first may be obtained by combining
  12324.                  this option with -r.
  12325.       ──────────────────────────────────────────────────────────────────────
  12326.  
  12327.  EXAMPLES
  12328.  
  12329.       Display a long list of the current directory:
  12330.  
  12331.       ls -l
  12332.  
  12333.       Display a multi-column listing of the root directory on the current
  12334.       disk from anywhere in the directory hierarchy:
  12335.  
  12336.       ls -C \
  12337.  
  12338.  
  12339.  
  12340.  FIGURE 9-1 ▐ Manual page for PR
  12341.  ───────────────────────────────────────────────────────────────────────────
  12342.  
  12343.  NAME
  12344.  
  12345.       PR--print file(s)
  12346.  
  12347.  SYNOPSIS
  12348.  
  12349.       pr [ options ] [ file ... ]
  12350.  
  12351.  DESCRIPTION
  12352.  
  12353.       The PR program takes a list of filenames and sends the contents of
  12354.       each in turn to the standard output. Formatting of the output "pages"
  12355.       is controlled via built-in defaults and may be modified by external
  12356.       configuration files and command-line options. If no filenames are
  12357.       specified, PR reads from its standard input (usually the keyboard,
  12358.       unless input is redirected).
  12359.  
  12360.       Each page starts with a header composed of the filename, the file-
  12361.       relative page number, and the file's modification date and time. When
  12362.       reading from the standard input, the filename is omitted and the
  12363.       current date and time are used.
  12364.  
  12365.       Long lines are folded onto the next line rather than being truncated.
  12366.       The line count is not affected by this. PR continues to track the
  12367.       number of logical lines in the source file, not the number of physical
  12368.       lines printed.
  12369.  
  12370.       The options listed below may be used singly or combined in any
  12371.       order.
  12372.  
  12373.       ══════════════════════════════════════════════════════════════════════
  12374.       Option     Action
  12375.       ──────────────────────────────────────────────────────────────────────
  12376.       -e          Toggle the Epson-compatible printer mode
  12377.       -f          Use a formfeed to eject a page (the default is spaces)
  12378.       -h hdr      Replace the filename part of the header with the text
  12379.                   given in hdr (must be quoted if it contains white space)
  12380.       -l len      Set the page length to len lines (the default is 66)
  12381.       -n          Enable the line-numbering feature (the initial setting
  12382.                   is off)
  12383.       -o cols     Set offset to cols columns from left edge of the paper
  12384.       -p          Preview output on screen (may be redirected)
  12385.       -s list     Output only those lines selected by the comma-separated
  12386.                   list (must be quoted if it contains any white space)
  12387.       -w cols     Set the output line width to cols columns
  12388.       ──────────────────────────────────────────────────────────────────────
  12389.  
  12390.       Program configuration may be controlled via optional local and global
  12391.       configuration files (local overrides global). Most settings
  12392.       established by configuration files may be altered by the user via the
  12393.       command-line arguments listed above. The following listing (comments
  12394.       are optional--only the numbers and strings at the beginning of each
  12395.       line are required) is the default global configuration file, which, if
  12396.       it exists, must reside in a subdirectory pointed to by the CONFIG
  12397.       environment variable:
  12398.  
  12399.  
  12400.         2    top1
  12401.         2    top2
  12402.         3    bottom
  12403.         132  width
  12404.         5    left margin
  12405.         3    right margin
  12406.         66   lines per page
  12407.         6    lines per inch
  12408.         1    printer mode (Epson)
  12409.         1    line number enabled
  12410.         1    use formfeeds
  12411.         8    standard tabs
  12412.         PRN  output destination
  12413.  
  12414.  EXAMPLES
  12415.  
  12416.       Print all of the C source files in the current directory, with
  12417.       linenumbering turned on, to an Epson printer attached to the standard
  12418.       printer port:
  12419.  
  12420.       pr -en *.c
  12421.  
  12422.       Display the contents of the \include\std.h header file
  12423.       with an offset of 10 columns and a text file:
  12424.  
  12425.       pr -o10 \include\std.h
  12426.  
  12427.       Print the file myprog.pas on the default printer starting
  12428.       at page 4 and continuing up to and including page 10:
  12429.  
  12430.       pr -s4,10 myprog.pas
  12431.  
  12432.  
  12433.  
  12434.  FIGURE 10-1 ▐ Manual page for DUMP
  12435.  ───────────────────────────────────────────────────────────────────────────
  12436.  
  12437.  NAME
  12438.  
  12439.       DUMP--dump file in hexadecimal and text formats
  12440.  
  12441.  SYNOPSIS
  12442.  
  12443.       dump [ -sv] [file ... ]
  12444.  
  12445.  DESCRIPTION
  12446.  
  12447.       The DUMP program is a stand-alone program that can be used as a
  12448.       filter. It receives a stream of data and transforms it into a combined
  12449.       hexadecimal and text output stream. Each line of output contains three
  12450.       fields: an offset from the start of the incoming stream; a hexadecimal
  12451.       display of a 16-byte block of data; and a text representation of the
  12452.       same block of bytes.
  12453.  
  12454.       The output of DUMP contains non-ASCII characters that may confuse
  12455.       printers and other output devices. The -s option causes these to be
  12456.       stripped out of the output stream and replaced by dots (.). When this
  12457.       option is used, the output of DUMP may be safely sent to any other
  12458.       program that can read from its standard input, and to any device that
  12459.       can accept an ASCII text stream.
  12460.  
  12461.       The -v option tells DUMP to be verbose. For each file given as a
  12462.       command-line argument, DUMP outputs a blank line, then the name of the
  12463.       file being dumped, and then moves to a new line before outputting the
  12464.       formatted file dump. When the standard input is the data source, the
  12465.       verbose option is ignored.
  12466.  
  12467.  EXAMPLES
  12468.  
  12469.       Display the converted contents of hex.obj on the
  12470.       screen:
  12471.  
  12472.       dump hex.obj
  12473.  
  12474.       Print the converted contents of PROG.COM:
  12475.  
  12476.       dump prog.com > prn
  12477.  
  12478.  
  12479.  
  12480.  FIGURE 10-3 ▐ Manual page for SHOW
  12481.  ───────────────────────────────────────────────────────────────────────────
  12482.  
  12483.  NAME
  12484.  
  12485.       SHOW--make all characters in a stream visible
  12486.  
  12487.  SYNOPSIS
  12488.  
  12489.       show [-svw] [file ... ]
  12490.  
  12491.  DESCRIPTION
  12492.  
  12493.       The SHOW program is a stand-alone program that can be used as a
  12494.       filter. It receives a stream of data and transforms it into a straight
  12495.       ASCII output stream. Normal displayable characters pass through
  12496.       unchanged, but most nonprintable characters are converted to a two-
  12497.       digit hexadecimal form.
  12498.  
  12499.       The -s option strips all control codes and IBM extended ASCII
  12500.       characters from the text stream, except for the primary format
  12501.       effectors (tab and newline). The -v option tells SHOW to be verbose.
  12502.       When the -w (word-processing) option is used, a carriage return that
  12503.       is not paired with a newline is assumed to be a "soft" return and is
  12504.       replaced by a newline. The -w option also causes SHOW to convert all
  12505.       extended codes (eighth bit on) to 7-bit ASCII and outputs those whose
  12506.       converted values fall into the displayable ASCII range.
  12507.  
  12508.  EXAMPLES
  12509.  
  12510.       Display the contents of a spreadsheet data file:
  12511.  
  12512.       show data.wks
  12513.  
  12514.       Convert a word processor's formatted document file to straight ASCII
  12515.       text and save output in a file:
  12516.  
  12517.       show -w letter.doc >letter.txt
  12518.  
  12519.  
  12520.  
  12521.  FIGURE 13-4 ▐ Manual page for SetColor
  12522.  ───────────────────────────────────────────────────────────────────────────
  12523.  
  12524.  NAME
  12525.  
  12526.       SC--control video attributes
  12527.  
  12528.  SYNOPSIS
  12529.  
  12530.       sc [ attribute ]
  12531.  
  12532.       sc [ foreground [ background [ border ] ] ]
  12533.  
  12534.  DESCRIPTION
  12535.  
  12536.       The SetColor program may be used in either batch mode or interactive
  12537.       mode to set the foreground, background, and border attributes of a
  12538.       color display system. The interactive mode, invoked by typing SC with
  12539.       no arguments, uses on-screen instructions to guide the user in
  12540.       selecting attributes via function keys. The display is updated with
  12541.       each keypress to reflect the current selections.
  12542.  
  12543.       The attribute selections in batch mode can be formed from the
  12544.       following list of argument values. Only the first three letters are
  12545.       significant and case is not relevant.
  12546.  
  12547.       ══════════════════════════════════════════════════════════════════════
  12548.       Values For     Values For Foreground,
  12549.       Attribute      Background and Border
  12550.       ──────────────────────────────────────────────────────────────────────
  12551.  
  12552.       Normal         Black       Red
  12553.       Reverse        Blue        Magenta
  12554.                      Green       Brown
  12555.                      Cyan        White
  12556.       ──────────────────────────────────────────────────────────────────────
  12557.  
  12558.       All color specifications can be modified by prefixing bold, bright, or
  12559.       light to obtain high-intensity values of the base color. Yellow may be
  12560.       used as a synonym for bold brown. If "blink" is enabled (the default),
  12561.       selecting a high-intensity background will cause the background to be
  12562.       a low-intensity color and the foreground will blink.
  12563.  
  12564.  EXAMPLES
  12565.  
  12566.       Run SetColor in the interactive mode:
  12567.  
  12568.       sc
  12569.  
  12570.       Request bright white on a blue background with a cyan border:
  12571.  
  12572.       sc bold white blue cyan
  12573.  
  12574.       Set up a reverse video screen:
  12575.  
  12576.       sc reverse
  12577.  
  12578.  NOTES
  12579.  
  12580.       The ANSI.SYS device driver must be loaded into memory. This requires
  12581.       DOS version 2.00 or later and the line
  12582.  
  12583.       device=ansi.sys
  12584.  
  12585.       in the CONFIG.SYS file.
  12586.  
  12587.       The border cannot be set on some machines, notably the AT&T PC6300.
  12588.       Also, the ANSI.SYS device driver supplied with MS-DOS version 2.11
  12589.       erases to Normal attribute (white on black) regardless of the
  12590.       specified attribute. However, displayed characters written after the
  12591.       erasure will have the specified attributes.
  12592.  
  12593.  
  12594.  
  12595.  FIGURE 14-1 ▐ Manual page for ViewFile
  12596.  ───────────────────────────────────────────────────────────────────────────
  12597.  
  12598.  NAME
  12599.  
  12600.       VF_a full-screen file-viewing program
  12601.  
  12602.  SYNOPSIS
  12603.  
  12604.       vf [-n] file ...
  12605.  
  12606.  DESCRIPTION
  12607.  
  12608.       ViewFile is a file-viewing program that features fast vertical and
  12609.       horizontal scrolling through any text file. VF uses the PC arrow and
  12610.       special-purpose keys to move around quickly and easily in a file. Most
  12611.       commands have single-letter synonyms.
  12612.  
  12613.       Absolute go-to-line and search-for-string commands may also be used to
  12614.       seek a position in the file being viewed. Line-numbering is optional;
  12615.       it can be turned on from the command line by using the -n option.
  12616.       Numbering can also be toggled on and off while VF is running by using
  12617.       the N key.
  12618.  
  12619.       The following commands (and synonyms) are understood by VF:
  12620.  
  12621.       ══════════════════════════════════════════════════════════════════════
  12622.       COMMAND          MEANING
  12623.       ──────────────────────────────────────────────────────────────────────
  12624.       PgUp (U)         Scroll up the file one screen page
  12625.       PgDn (D)         Scroll down the file one screen page
  12626.       Up arrow (-)     Scroll up in the file one line
  12627.       Down arrow (+)   Scroll down in the file one line
  12628.       Right arrow (>)  Scroll right by 20 columns
  12629.       Left arrow (<)   Scroll left by 20 columns
  12630.       Home (B)         Go to beginning of file buffer
  12631.       End (E)          Go to end of file buffer
  12632.       Alt-g (G)        Go to a specified line in the buffer
  12633.       Alt-h (H or ?)   Display the help frame
  12634.       Alt-n (N)        Toggle line-numbering feature
  12635.       \ (R)            Reverse search for a literal string
  12636.       / (S)            Search forward for a literal string
  12637.       Esc              Next file from list (quits if none)
  12638.       Alt-q (Q)        Quit
  12639.       ──────────────────────────────────────────────────────────────────────
  12640.  
  12641.       This information is also available on-line in the built-in ViewFile
  12642.       help frame.
  12643.  
  12644.  EXAMPLES
  12645.  
  12646.       "View" sequentially all of the C source files in the current
  12647.       directory:
  12648.  
  12649.       vf -n *.c
  12650.  
  12651.  
  12652.  
  12653.  std.h
  12654.  ───────────────────────────────────────────────────────────────────────────
  12655.  
  12656.  /*
  12657.   *      std.h
  12658.   */
  12659.  
  12660.  /* data type aliases */
  12661.  #define META    short
  12662.  #define UCHAR   unsigned char
  12663.  #define UINT    unsigned int
  12664.  #define ULONG   unsigned long
  12665.  #define USHORT  unsigned short
  12666.  
  12667.  /* Boolean data type */
  12668.  typedef enum {
  12669.          FALSE, TRUE
  12670.  } BOOLEAN;
  12671.  
  12672.  /* function return values and program exit codes */
  12673.  #define OK      0
  12674.  #define BAD     1
  12675.  #define SUCCESS 0
  12676.  #define FAILURE 1
  12677.  
  12678.  /* infinite loop */
  12679.  #define FOREVER while (1)
  12680.  
  12681.  /* masks */
  12682.  #define HIBYTE  0xFF00
  12683.  #define LOBYTE  0x00FF
  12684.  #define ASCII   0x7F
  12685.  #define HIBIT   0x80
  12686.  
  12687.  /* lengths */
  12688.  #define MAXNAME 8
  12689.  #define MAXEXT  3
  12690.  #define MAXLINE 256
  12691.  #define MAXPATH 64
  12692.  
  12693.  /* special number */
  12694.  #define BIGGEST 65535
  12695.  
  12696.  
  12697.  
  12698.  replay
  12699.  ───────────────────────────────────────────────────────────────────────────
  12700.  
  12701.  /*
  12702.   *      replay -- echo the command-line arguments
  12703.   *      to standard output
  12704.   */
  12705.  
  12706.  #include <stdio.h>
  12707.  #include <stdlib.h>
  12708.  #include <local\std.h>
  12709.  
  12710.  main(argc, argv)
  12711.  int argc;
  12712.  char **argv;
  12713.  {
  12714.          int i;
  12715.          char **p;
  12716.          static char pgm[MAXNAME + 1] = { "replay" };
  12717.  
  12718.          void getpname(char *, char *);
  12719.  
  12720.          /* get program name from DOS (version 3.00 and later) */
  12721.          if (_osmajor >= 3)
  12722.                  getpname(*argv, pgm);
  12723.  
  12724.          /* check for arguments */
  12725.          if (argc == 1)
  12726.                  exit(1);        /* none given */
  12727.  
  12728.          /* echo the argument list, one per line */
  12729.          p = argv;
  12730.          printf("%s arguments:\n\n", pgm);
  12731.          for (--argc, ++argv; argc > 0; --argc, ++argv)
  12732.                  printf("argv[%d] -> %s\n", argv - p, *argv);
  12733.          exit(0);
  12734.  } /* end main() */
  12735.  
  12736.  
  12737.  
  12738.  getpname
  12739.  ───────────────────────────────────────────────────────────────────────────
  12740.  
  12741.  /*
  12742.   *      getpname -- extract the base name of a program from the
  12743.   *      pathname string (deletes a drive specifier, any leading
  12744.   *      path node information, and the extension)
  12745.   */
  12746.  
  12747.  #include <stdio.h>
  12748.  #include <ctype.h>
  12749.  
  12750.  void getpname(path, pname)
  12751.  char *path;     /* full or relative pathname */
  12752.  char *pname;    /* program name pointer */
  12753.  {
  12754.          char *cp;
  12755.  
  12756.          /* find the end of the pathname string */
  12757.          cp = path;      /* start of pathname */
  12758.          while (*cp != '\0')
  12759.                  ++cp;
  12760.          --cp;           /* went one too far */
  12761.  
  12762.          /* find the start of the filename part */
  12763.          while (cp > path && *cp != '\\' && *cp != ':' && *cp != '/')
  12764.                  --cp;
  12765.          if (cp > path)
  12766.                  ++cp;   /* move to right of pathname separator */
  12767.  
  12768.          /* copy the filename part only */
  12769.          while ((*pname = tolower(*cp)) != '.' && *pname != '\0') {
  12770.                  ++cp;
  12771.                  ++pname;
  12772.          }
  12773.          *pname = '\0';
  12774.  }
  12775.  
  12776.  
  12777.  
  12778.  showenv
  12779.  ───────────────────────────────────────────────────────────────────────────
  12780.  
  12781.  /*
  12782.   *      showenv -- display the values of any DOS variables
  12783.   *      named on the invocation command line
  12784.   */
  12785.  
  12786.  #include <stdio.h>
  12787.  #include <stdlib.h>
  12788.  #include <string.h>
  12789.  #include <local\std.h>
  12790.  
  12791.  main(argc, argv, envp)
  12792.  int argc;
  12793.  char **argv;
  12794.  char **envp;
  12795.  {
  12796.          register char *ep;
  12797.          static char pgm[MAXNAME + 1] = { "showenv" };
  12798.          extern void getpname(char *, char *);
  12799.  
  12800.          /* use an alias if one is given to this program */
  12801.          if (_osmajor >= 3)
  12802.                  getpname(*argv, pgm);
  12803.  
  12804.          /* if no arguments, show full environment list */
  12805.          if (argc == 1)
  12806.                  for (; *envp; ++envp)
  12807.                          printf("%s\n", *envp);
  12808.          else {
  12809.                  /*
  12810.                   *  treat all args as DOS variable names and
  12811.                   *  display values of only specified variables
  12812.                   */
  12813.                  ++argv; /* skip past command name */
  12814.                  --argc;
  12815.                  while (argc > 0) {
  12816.                          if ((ep = getenv(strupr(*argv))) == NULL)
  12817.                                  fprintf(stderr, "%s not defined\n", *argv);
  12818.                          else
  12819.                                  printf("%s=%s\n", *argv, ep);
  12820.                          ++argv;
  12821.                          --argc;
  12822.                  }
  12823.          }
  12824.          exit(0);
  12825.  }
  12826.  
  12827.  
  12828.  
  12829.  setmydir
  12830.  ───────────────────────────────────────────────────────────────────────────
  12831.  
  12832.  /*
  12833.   *      setmydir -- try to change the DOS environment
  12834.   */
  12835.  
  12836.  #include <stdio.h>
  12837.  #include <stdlib.h>
  12838.  #include <local\std.h>
  12839.  
  12840.  main(argc, argv)
  12841.  int argc;
  12842.  char **argv;
  12843.  {
  12844.          register char **p;
  12845.          static char var[] = { "MYDIR" };
  12846.          static char pgm[MAXNAME + 1] = { "setmydir" };
  12847.          extern void fatal(char *, int);
  12848.          extern void getpname(char *, char *);
  12849.  
  12850.          /* use an alias if one is given to this program */
  12851.          if (_osmajor >= 3)
  12852.                  getpname(*argv, pgm);
  12853.  
  12854.          /* try to add the MYDIR variable to the environment */
  12855.          if (putenv("MYDIR=c:\\mydir") == -1)
  12856.                  fatal(pgm, "Error changing environment", 1);
  12857.  
  12858.          /* display the environment for this process */
  12859.          for (p = environ; *p; p++) {
  12860.                  printf("%s\n", *p);
  12861.          }
  12862.          exit(0);
  12863.  }
  12864.  
  12865.  
  12866.  
  12867.  timedata
  12868.  ───────────────────────────────────────────────────────────────────────────
  12869.  
  12870.  /*
  12871.   *      timedata -- time zone and time value tests
  12872.   */
  12873.  
  12874.  #include <stdio.h>
  12875.  #include <time.h>
  12876.  
  12877.  main()
  12878.  {
  12879.          long now;
  12880.          struct tm *tbuf;
  12881.  
  12882.          /* get TZ data into global variables */
  12883.          tzset();
  12884.  
  12885.          /* display the global time values */
  12886.          printf("daylight savings time flag = %d\n", daylight);
  12887.          printf("difference (in seconds) from GMT = %ld\n", timezone);
  12888.          printf("standard time zone string is %s\n", tzname[0]);
  12889.          printf("daylight time zone string is %s\n", tzname[1]);
  12890.  
  12891.          /*
  12892.           *  display the current date and time values for
  12893.           *  local and universal time
  12894.           */
  12895.          now = time(NULL);
  12896.          printf("\nctime():\t%s\n", ctime(&now));
  12897.          tbuf = localtime(&now);
  12898.          printf("local time:\t%s\n", asctime(tbuf));
  12899.          tbuf = gmtime(&now);
  12900.          printf("universal time:\t%s\n", asctime(tbuf));
  12901.  
  12902.          exit(0);
  12903.  }
  12904.  
  12905.  
  12906.  
  12907.  notes1
  12908.  ───────────────────────────────────────────────────────────────────────────
  12909.  
  12910.  /*
  12911.   *      notes1 -- add an entry to a "notes" text file
  12912.   *
  12913.   *      version 1: appends new data to NOTES.TXT in the
  12914.   *      current directory -- uses local date/time stamp
  12915.   *      as a header for each new entry
  12916.   */
  12917.  
  12918.  #include <stdio.h>
  12919.  #include <stdlib.h>
  12920.  #include <time.h>
  12921.  #include <local\std.h>
  12922.  
  12923.  main()
  12924.  {
  12925.          FILE *fp;
  12926.          static char notesfile[MAXPATH + 1] = { "notes.txt" };
  12927.          char ch;
  12928.          long ltime;
  12929.          static char pgm[MAXNAME + 1] = { "notes1" };
  12930.  
  12931.          extern void fatal(char *, char *, int);
  12932.  
  12933.          /* try to open notes file in current directory */
  12934.          if ((fp = fopen(notesfile, "a")) == NULL)
  12935.                  fatal(pgm, notesfile, 1);
  12936.  
  12937.          /* append a header and date/time tag */
  12938.          ltime = time(NULL);
  12939.          fprintf(stderr, "Appending to %s: %s\n",
  12940.                   notesfile, ctime(<ime));
  12941.          fprintf(fp, "%s\n", ctime(<ime));
  12942.  
  12943.          /* append new text */
  12944.          while ((ch = getchar()) != EOF)
  12945.                  putc(ch, fp);
  12946.  
  12947.          /* clean up */
  12948.          if (fclose(fp))
  12949.                  fatal(pgm, notesfile, 2);
  12950.          exit(0);
  12951.  }
  12952.  
  12953.  
  12954.  
  12955.  notes2
  12956.  ───────────────────────────────────────────────────────────────────────────
  12957.  
  12958.  /*
  12959.   *      notes2 -- add a date/time stamped entry to a
  12960.   *      "notes" data file.  Allow user to optionally
  12961.   *      edit the data file upon completion of the entry.
  12962.   */
  12963.  
  12964.  #include <stdio.h>
  12965.  #include <stdlib.h>
  12966.  #include <time.h>
  12967.  #include <signal.h>
  12968.  #include <process.h>
  12969.  #include <local\std.h>
  12970.  
  12971.  /* length of date/time string */
  12972.  #define DT_STR  26
  12973.  
  12974.  main(argc, argv)
  12975.  int argc;
  12976.  char **argv;
  12977.  {
  12978.          int n;          /* number of lines added */
  12979.          int exitcode = 0;
  12980.          FILE *fp;
  12981.          static char notesfile[MAXPATH + 1] = { "notes.txt" };
  12982.          static char editname[MAXPATH + 1] = { "edlin" };
  12983.          char ch;
  12984.          char dt_stamp[DT_STR];
  12985.          char *s;
  12986.          long ltime;
  12987.          static char pgm[MAXNAME + 1] = { "notes2" };
  12988.  
  12989.          extern void fatal(char *, char *, int);
  12990.          extern void getpname(char *, char *);
  12991.          static int addtxt(FILE *, FILE *);
  12992.  
  12993.          if (_osmajor >= 3)
  12994.                  getpname(*argv, pgm);
  12995.  
  12996.          /* locate the "notes" database file and open it */
  12997.          if (argc > 1)
  12998.                  strcpy(notesfile, *++argv);
  12999.          else if (s = getenv("NOTESFILE"))
  13000.                  strcpy(notesfile, s);
  13001.          if ((fp = fopen(notesfile, "a")) == NULL)
  13002.                  fatal(pgm, notesfile, 1);
  13003.  
  13004.          /* disable Ctrl-Break interrupt */
  13005.          if (signal(SIGINT, SIG_IGN) == (int(*)())-1)
  13006.                  perror("Cannot set signal");
  13007.  
  13008.          /* append a header and date/time tag */
  13009.          ltime = time(NULL);
  13010.          strcpy(dt_stamp, ctime(<ime));
  13011.          fprintf(stderr, "Appending to %s: %s", notesfile, dt_stamp);
  13012.          fprintf(fp, "\n%s", dt_stamp);
  13013.  
  13014.          /* add text to notes file */
  13015.          if ((n = addtxt(stdin, fp)) == 0) {
  13016.                  fputs("No new text", stderr);
  13017.                  if (fclose(fp))
  13018.                          fatal(pgm, notesfile, 2);
  13019.                  exit(0);
  13020.          }
  13021.          else
  13022.                  fprintf(stderr, "%d line(s) added to %s\n", n, notesfile);
  13023.          if (fclose(fp))
  13024.                  fatal(pgm, notesfile, 2);
  13025.  
  13026.          /* optionally edit text in the notes file */
  13027.          fprintf(stderr, "E + ENTER to edit; ENTER alone to quit: ");
  13028.          while ((ch = tolower(getchar())) != '\n')
  13029.                  if (ch == 'e') {
  13030.                          if (s = getenv("EDITOR"))
  13031.                                  strcpy(editname, s);
  13032.                          if ((exitcode = spawnlp(P_WAIT, editname, editname,
  13033.                                  notesfile, NULL)) == -1)
  13034.                                  fatal(pgm, editname, 3);
  13035.                  }
  13036.          exit(exitcode);
  13037.  }
  13038.  
  13039.  /*
  13040.   *      addtxt -- append new text to notes file
  13041.   */
  13042.  static int addtxt(fin, fout)
  13043.  FILE *fin, *fout;
  13044.  {
  13045.          int ch;
  13046.          int col = 0;    /* column */
  13047.          int n = 0;      /* number of lines added */
  13048.  
  13049.          while ((ch = fgetc(fin)) != EOF) {
  13050.                  if (ch == '.' && col == 0) {
  13051.                          ch = fgetc(fin); /* trash the newline */
  13052.                          break;
  13053.                  }
  13054.                  fputc(ch, fout);
  13055.                  if (ch == '\n') {
  13056.                          col = 0;
  13057.                          ++n;
  13058.                  }
  13059.                  else
  13060.                          ++col;
  13061.          }
  13062.          return (n);
  13063.  }
  13064.  
  13065.  
  13066.  
  13067.  run_once
  13068.  ───────────────────────────────────────────────────────────────────────────
  13069.  
  13070.  /*
  13071.   *      run_once -- run a program one time and then
  13072.   *      "hang" the system to prevent unwanted use
  13073.   */
  13074.  
  13075.  #include <stdio.h>
  13076.  #include <stdlib.h>
  13077.  #include <process.h>
  13078.  #include <local\std.h>
  13079.  
  13080.  main(argc, argv)
  13081.  int argc;
  13082.  char *argv[];
  13083.  {
  13084.          extern void fatal(char *, *, int);
  13085.  
  13086.          ++argv;         /* skip over the program name */
  13087.  
  13088.          /* run the specified command line [pgmname arg(s)] */
  13089.          if (spawnvp(P_WAIT, *argv, argv) == -1)
  13090.                  fatal("run_once" "Error running specified program", 1);
  13091.          fprintf(stderr, "Please turn off the power to the computer.\n");
  13092.  
  13093.          FOREVER         /* do nothing */
  13094.                   ;
  13095.  }
  13096.  
  13097.  
  13098.  
  13099.  dos.h
  13100.  ───────────────────────────────────────────────────────────────────────────
  13101.  
  13102.  /*
  13103.   * dos.h
  13104.   *
  13105.   * Defines the structs and unions used to handle the input and output
  13106.   * registers for the DOS interface routines defined in the V2.0 to V3.0
  13107.   * compatibility package.  It also includes macros to access the segment
  13108.   * and offset values of MS C "far" pointers, so that they may be used by
  13109.   * these routines.
  13110.   *
  13111.   * Copyright (C) Microsoft Corporation, 1984, 1985, 1986
  13112.   */
  13113.  
  13114.  /* word registers */
  13115.  
  13116.  struct WORDREGS {
  13117.          unsigned int ax;
  13118.          unsigned int bx;
  13119.          unsigned int cx;
  13120.          unsigned int dx;
  13121.          unsigned int si;
  13122.          unsigned int di;
  13123.          unsigned int cflag;
  13124.          };
  13125.  
  13126.  /* byte registers */
  13127.  
  13128.  struct BYTEREGS {
  13129.          unsigned char al, ah;
  13130.          unsigned char bl, bh;
  13131.          unsigned char cl, ch;
  13132.          unsigned char dl, dh;
  13133.          };
  13134.  
  13135.  /* general purpose registers union - overlays the corresponding word and
  13136.   * byte registers.
  13137.   */
  13138.  
  13139.  union REGS {
  13140.          struct WORDREGS x;
  13141.          struct BYTEREGS h;
  13142.          };
  13143.  
  13144.  /* segment registers */
  13145.  
  13146.  struct SREGS {
  13147.          unsigned int es;
  13148.          unsigned int cs;
  13149.          unsigned int ss;
  13150.          unsigned int ds;
  13151.          };
  13152.  
  13153.  /* dosexterror struct */
  13154.  
  13155.  struct DOSERROR {
  13156.          int exterror;
  13157.          char class;
  13158.          char action;
  13159.          char locus;
  13160.          };
  13161.  
  13162.  /* macros to break MS C "far" pointers into their segment and offset
  13163.   * components
  13164.   */
  13165.  
  13166.  #define FP_SEG(fp) (*((unsigned *)&(fp) + 1))
  13167.  #define FP_OFF(fp) (*((unsigned *)&(fp)))
  13168.  
  13169.  /* function declarations for those who want strong type checking
  13170.   * on arguments to library function calls
  13171.   */
  13172.  
  13173.  #ifdef LINT_ARGS        /* argument checking enabled */
  13174.  
  13175.  #ifndef NO_EXT_KEYS     /* extended keywords are enabled */
  13176.  int cdecl bdos(int, unsigned int, unsigned int);
  13177.  int cdecl dosexterr(struct DOSERROR *);
  13178.  int cdecl intdos(union REGS *, union REGS *);
  13179.  int cdecl intdosx(union REGS *, union REGS *, struct SREGS *);
  13180.  int cdecl int86(int, union REGS *, union REGS *);
  13181.  int cdecl int86x(int, union REGS *, union REGS *, struct SREGS *);
  13182.  void cdecl segread(struct SREGS *);
  13183.  #else                   /* extended keywords not enabled */
  13184.  int bdos(int, unsigned int, unsigned int);
  13185.  int dosexterr(struct DOSERROR *);
  13186.  int intdos(union REGS *, union REGS *);
  13187.  int intdosx(union REGS *, union REGS *, struct SREGS *);
  13188.  int int86(int, union REGS *, union REGS *);
  13189.  int int86x(int, union REGS *, union REGS *, struct SREGS *);
  13190.  void segread(struct SREGS *);
  13191.  #endif  /* NO_EXT_KEYS */
  13192.  
  13193.  #else
  13194.  
  13195.  #ifndef NO_EXT_KEYS     /* extended keywords are enabled */
  13196.  int cdecl bdos();
  13197.  int cdecl dosexterr();
  13198.  int cdecl intdos();
  13199.  int cdecl intdosx();
  13200.  int cdecl int86();
  13201.  int cdecl int86x();
  13202.  void cdecl segread();
  13203.  #else                   /* extended keywords not enabled */
  13204.  void segread();
  13205.  #endif  /* NO_EXT_KEYS */
  13206.  
  13207.  #endif  /* LINT_ARGS */
  13208.  
  13209.  
  13210.  
  13211.  doslib.h
  13212.  ───────────────────────────────────────────────────────────────────────────
  13213.  /*
  13214.   *      doslib.h
  13215.   */
  13216.  
  13217.  /* DOS interrupts */
  13218.  #define PGM_TERMINATE    0x20
  13219.  #define BDOS_REQ         0x21
  13220.  #define TERMINATE_ADDR   0x22
  13221.  #define CB_EXIT_ADDR     0x23
  13222.  #define CRITICAL_ERR     0x24
  13223.  #define ABS_DISK_READ    0x25
  13224.  #define ABS_DISK_WRITE   0x26
  13225.  #define PGM_TERM_RES     0x27
  13226.  
  13227.  /* DOS functions -- usually placed in ah register for C86 calls */
  13228.  #define PGM              0x0
  13229.  #define KEYIN_ECHO_CB    0x1
  13230.  #define DSPY_CHAR        0x2
  13231.  #define AUX_IN           0x3
  13232.  #define AUX_OUT          0x4
  13233.  #define PRNT_OUT         0x5
  13234.  #define KEYIN_ECHO       0x6
  13235.  #define KEYIN            0x7
  13236.  #define KEYIN_CB         0x8
  13237.  #define DSPY_STR         0x9
  13238.  #define KEYIN_BUF        0xA
  13239.  #define CH_READY         0xB
  13240.  #define GET_CH           0xC
  13241.  #define DISK_RESET       0xD
  13242.  #define SELECT_DISK      0xE
  13243.  #define OPEN_FILE        0xF
  13244.  #define CLOSE_FILE       0x10
  13245.  #define FIRST_FM         0x11
  13246.  #define NEXT_FM          0x12
  13247.  #define DELETE_FILE      0x13
  13248.  #define READ_SEQ         0x14
  13249.  #define WRITE_SEQ        0x15
  13250.  #define CREATE_FILE      0x16
  13251.  #define RENAME_FILE      0x17
  13252.          /* 0x18 reserved */
  13253.  #define CURRENT_DISK     0x19
  13254.  #define SET_DTA          0x1A
  13255.  #define DFLT_FAT_INFO    0x1B
  13256.  #define FAT_INFO         0x1C
  13257.          /* 0x1D - 0x20 reserved */
  13258.  #define READ_RANDOM      0x21
  13259.  #define WRITE_RANDOM     0x22
  13260.  #define FILE_SIZE        0x23
  13261.  #define SET_INT_VEC      0x25
  13262.  #define NEW_PGM_SEG      0x26
  13263.  #define RAND_BLK_READ    0x27
  13264.  #define RAND_BLK_WRITE   0x28
  13265.  #define PARSE_FILENAME   0x29
  13266.  #define GET_DATE         0x2A
  13267.  #define SET_DATE         0x2B
  13268.  #define GET_TIME         0x2C
  13269.  #define SET_TIME         0x2D
  13270.  #define TOGGLE_VERIFY    0x2E
  13271.  #define GET_DTA          0x2F
  13272.  #define DOS_VERSION      0x30
  13273.  #define PGM_TERM_KEEP    0x31
  13274.          /* 0x32 reserved */
  13275.  #define CB_CHECK         0x33
  13276.          /* 0x34 reserved */
  13277.  #define GET_INTR         0x35
  13278.  #define GET_FREE_SPACE   0x36
  13279.          /* 0x37 reserved */
  13280.  #define INTL_INFO        0x38
  13281.  #define MKDIR            0x39
  13282.  #define RMDIR            0x3A
  13283.  #define CHDIR            0x3B
  13284.  #define CREAT            0x3C
  13285.  #define OPEN_FD          0x3D
  13286.  #define CLOSE_FD         0x3E
  13287.  #define WRITE_FD         0x40
  13288.  #define UNLINK           0x41
  13289.  #define LSEEK            0x42
  13290.  #define CHMOD            0x43
  13291.  #define IOCTL            0x44
  13292.  #define DUP              0x45
  13293.  #define FORCE_DUP        0x46
  13294.  #define GET_CUR_DIR      0x47
  13295.  #define ALLOC            0x48
  13296.  #define FREE             0x49
  13297.  #define SET_BLOCK        0x4A
  13298.  #define EXEC             0x4B
  13299.  #define EXIT             0x4C
  13300.  #define WAIT             0x4D
  13301.  #define FIND_FIRST       0x4E
  13302.  #define FIND_NEXT        0x4F
  13303.          /* 0x50 - 53 reserved */
  13304.  #define VERIFY_STATE     0x54
  13305.          /* 0x55 reserved */
  13306.  #define RENAME           0x56
  13307.  #define FILE_MTIME       0x57
  13308.  
  13309.  
  13310.  
  13311.  keydefs
  13312.  ───────────────────────────────────────────────────────────────────────────
  13313.  
  13314.  /*      keydefs -- values for special keys on IBM PC and clones      */
  13315.  
  13316.  
  13317.  #define XF      0x100         /* extended key flag */
  13318.  
  13319.  #define K_F1    59 | XF       /* function keys */
  13320.  #define K_F2    60 | XF
  13321.  #define K_F3    61 | XF
  13322.  #define K_F4    62 | XF
  13323.  #define K_F5    63 | XF
  13324.  #define K_F6    64 | XF
  13325.  #define K_F7    65 | XF
  13326.  #define K_F8    66 | XF
  13327.  #define K_F9    67 | XF
  13328.  #define K_F10   68 | XF
  13329.  
  13330.  #define K_SF1   84 | XF       /* shifted function keys */
  13331.  #define K_SF2   85 | XF
  13332.  #define K_SF3   86 | XF
  13333.  #define K_SF4   87 | XF
  13334.  #define K_SF5   88 | XF
  13335.  #define K_SF6   89 | XF
  13336.  #define K_SF7   90 | XF
  13337.  #define K_SF8   91 | XF
  13338.  #define K_SF9   92 | XF
  13339.  #define K_SF10  93 | XF
  13340.  
  13341.  #define K_CF1   94 | XF       /* control function keys */
  13342.  #define K_CF2   95 | XF
  13343.  #define K_CF3   96 | XF
  13344.  #define K_CF4   97 | XF
  13345.  #define K_CF5   98 | XF
  13346.  #define K_CF6   99 | XF
  13347.  #define K_CF7   100 | XF
  13348.  #define K_CF8   101 | XF
  13349.  #define K_CF9   102 | XF
  13350.  #define K_CF10  103 | XF
  13351.  
  13352.  #define K_AF1   104 | XF      /* alternate function keys */
  13353.  #define K_AF2   105 | XF
  13354.  #define K_AF3   106 | XF
  13355.  #define K_AF4   107 | XF
  13356.  #define K_AF5   108 | XF
  13357.  #define K_AF6   109 | XF
  13358.  #define K_AF7   110 | XF
  13359.  #define K_AF8   111 | XF
  13360.  #define K_AF9   112 | XF
  13361.  #define K_AF10  113 | XF
  13362.  
  13363.  #define K_HOME  71 | XF       /* cursor keypad (NumLock off; */
  13364.  #define K_END   79 | XF       /*  not shifted) */
  13365.  #define K_PGUP  73 | XF
  13366.  #define K_PGDN  81 | XF
  13367.  #define K_LEFT  75 | XF
  13368.  #define K_RIGHT 77 | XF
  13369.  #define K_UP    72 | XF
  13370.  #define K_DOWN  80 | XF
  13371.  
  13372.  #define K_CHOME 119 | XF      /* control cursor keypad */
  13373.  #define K_CEND  117 | XF
  13374.  #define K_CPGUP 132 | XF
  13375.  #define K_CPGDN 118 | XF
  13376.  #define K_CLEFT 115 | XF
  13377.  #define K_CRGHT 116 | XF
  13378.  
  13379.  #define K_CTRLA 1             /* standard control keys */
  13380.  #define K_CTRLB 2
  13381.  #define K_CTRLC 3
  13382.  #define K_CTRLD 4
  13383.  #define K_CTRLE 5
  13384.  #define K_CTRLF 6
  13385.  #define K_CTRLG 7
  13386.  #define K_CTRLH 8
  13387.  #define K_CTRLI 9
  13388.  #define K_CTRLJ 10
  13389.  #define K_CTRLK 11
  13390.  #define K_CTRLL 12
  13391.  #define K_CTRLM 13
  13392.  #define K_CTRLN 14
  13393.  #define K_CTRLO 15
  13394.  #define K_CTRLP 16
  13395.  #define K_CTRLQ 17
  13396.  #define K_CTRLR 18
  13397.  #define K_CTRLS 19
  13398.  #define K_CTRLT 20
  13399.  #define K_CTRLU 21
  13400.  #define K_CTRLV 22
  13401.  #define K_CTRLW 23
  13402.  #define K_CTRLX 24
  13403.  #define K_CTRLY 25
  13404.  #define K_CTRLZ 26
  13405.  
  13406.  #define K_ALTA  30 | XF       /* alternate keys */
  13407.  #define K_ALTB  48 | XF
  13408.  #define K_ALTC  46 | XF
  13409.  #define K_ALTD  32 | XF
  13410.  #define K_ALTE  18 | XF
  13411.  #define K_ALTF  33 | XF
  13412.  #define K_ALTG  34 | XF
  13413.  #define K_ALTH  35 | XF
  13414.  #define K_ALTI  23 | XF
  13415.  #define K_ALTJ  36 | XF
  13416.  #define K_ALTK  37 | XF
  13417.  #define K_ALTL  38 | XF
  13418.  #define K_ALTM  50 | XF
  13419.  #define K_ALTN  49 | XF
  13420.  #define K_ALTO  24 | XF
  13421.  #define K_ALTP  25 | XF
  13422.  #define K_ALTQ  16 | XF
  13423.  #define K_ALTR  19 | XF
  13424.  #define K_ALTS  31 | XF
  13425.  #define K_ALTT  20 | XF
  13426.  #define K_ALTU  22 | XF
  13427.  #define K_ALTV  47 | XF
  13428.  #define K_ALTW  17 | XF
  13429.  #define K_ALTX  45 | XF
  13430.  #define K_ALTY  21 | XF
  13431.  #define K_ALTZ  44 | XF
  13432.  
  13433.  #define K_ALT1        120 | XF   /* additional alternate key */
  13434.  #define K_ALT2        121 | XF   /* combinations */
  13435.  #define K_ALT3        122 | XF
  13436.  #define K_ALT4        123 | XF
  13437.  #define K_ALT5        124 | XF
  13438.  #define K_ALT6        125 | XF
  13439.  #define K_ALT7        126 | XF
  13440.  #define K_ALT8        127 | XF
  13441.  #define K_ALT9        128 | XF
  13442.  #define K_ALT0        129 | XF
  13443.  #define K_ALTDASH     130 | XF
  13444.  #define K_ALTEQU      131 | XF
  13445.  
  13446.  #define K_ESC         27         /* miscellaneous special keys */
  13447.  #define K_SPACE       32
  13448.  #define K_INS         82 | XF
  13449.  #define K_DEL         83 | XF
  13450.  #define K_TAB         K_CTRLI
  13451.  #define K_BACKTAB     K_CTRLO
  13452.  
  13453.  #define K_CTRL_PRTSC  114 | XF   /* printer echoing toggle */
  13454.  
  13455.  #define K_RETURN      13         /* return key variations */
  13456.  #define K_SRETURN     13
  13457.  #define K_CRETURN     10
  13458.  
  13459.  
  13460.  
  13461.  bioslib.h
  13462.  ───────────────────────────────────────────────────────────────────────────
  13463.  
  13464.  /*      bioslib.h      */
  13465.  
  13466.  #define PRINT_SCRN      0x05    /* BIOS interrupts */
  13467.  #define TOD_INIT        0x08
  13468.  #define KEYBD_INIT      0x09
  13469.  #define DISK_INIT       0x0E
  13470.  #define VIDEO_IO        0x10
  13471.  #define EQUIP_CK        0x11
  13472.  #define MEM_SIZE        0x12
  13473.  #define DISK_IO         0x13
  13474.  #define RS232_IO        0x14
  13475.  #define CASS_IO         0x15
  13476.  #define KEYBD_IO        0x16
  13477.  #define PRINT_IO        0x17
  13478.  #define TOD             0x1A
  13479.  #define VIDEO_INIT      0x1D
  13480.  #define GRAPHICS        0x1F
  13481.  
  13482.  #define SET_MODE        0       /* video routine numbers */
  13483.  #define CUR_TYPE        1       /* (placed in register AH */
  13484.  #define CUR_POS         2       /*  before a BIOS interrupt 10H) */
  13485.  #define GET_CUR         3
  13486.  #define LPEN_POS        4
  13487.  #define SET_PAGE        5
  13488.  #define SCROLL_UP       6
  13489.  #define SCROLL_DN       7
  13490.  #define READ_CHAR_ATTR  8
  13491.  #define WRITE_CHAR_ATTR 9
  13492.  #define WRITE_CHAR      10
  13493.  #define PALETTE         11
  13494.  #define WRITE_DOT       12
  13495.  #define READ_DOT        13
  13496.  #define WRITE_TTY       14
  13497.  #define GET_STATE       15
  13498.  #define ALT_FUNCTION    18      /* EGA only */
  13499.  #define WRITE_STR       19      /* AT only */
  13500.  
  13501.  #define RESET_DISK      0       /* disk routine numbers */
  13502.  #define DISK_STATUS     1
  13503.  #define READ_SECTOR     2
  13504.  #define WRITE_SECTOR    3
  13505.  #define VERIFY_SECTOR   4
  13506.  #define FORMAT_TRACK    5
  13507.  
  13508.  #define KBD_READ        0       /* keyboard routine numbers */
  13509.  #define KBD_READY       1
  13510.  #define KBD_STATUS      2
  13511.  
  13512.  
  13513.  
  13514.  equipchk
  13515.  ───────────────────────────────────────────────────────────────────────────
  13516.  
  13517.  /*      equipchk -- get equipment list      */
  13518.  
  13519.  #include <dos.h>
  13520.  #include <local\bioslib.h>
  13521.  #include <local\equip.h>
  13522.  
  13523.  struct EQUIP Eq;
  13524.  
  13525.  int equipchk()
  13526.  {
  13527.          union REGS inregs, outregs;
  13528.  
  13529.          /* call BIOS equipment check routine */
  13530.          int86(EQUIP_CK, &inregs, &outregs);
  13531.          /* extract data from returned data word */
  13532.          Eq.nprint  = (outregs.x.ax & 0xC000) / 0x8000;
  13533.          Eq.game_io = ((outregs.x.ax & 0x1000) / 0x1000) ? 1 : 0;
  13534.          Eq.nrs232  = (outregs.x.ax & 0x0E00) / 0x0200;
  13535.          Eq.ndrive  = ((outregs.x.ax & 0x00C0) / 0x0040) + 1;
  13536.          Eq.vmode   = (outregs.x.ax & 0x0030) / 0x0010;
  13537.          Eq.basemem = ((outregs.x.ax & 0x000C) / 0x0004) + 1;
  13538.          Eq.disksys = outregs.x.ax & 0x0001 == 1;
  13539.          return (outregs.x.cflag);
  13540.  }
  13541.  
  13542.  
  13543.  
  13544.  video.h
  13545.  ───────────────────────────────────────────────────────────────────────────
  13546.  
  13547.  /*      video.h      */
  13548.  
  13549.  /* current video state/mode information */
  13550.  extern short Vmode;
  13551.  extern short Vwidth;
  13552.  extern short Vpage;
  13553.  
  13554.  #define MAXVMODE  17
  13555.  
  13556.  /* video limit tables */
  13557.  extern short Maxrow[MAXVMODE];
  13558.  extern short Maxcol[MAXVMODE];
  13559.  extern short Maxpage[MAXVMODE];
  13560.  
  13561.  /* active display */
  13562.  #define MONO    1
  13563.  #define COLOR   2
  13564.  
  13565.  /* cursor modes */
  13566.  #define CURSOR_OFF      0
  13567.  #define CURSOR_ON       1
  13568.  
  13569.  /* installed display adapters */
  13570.  #define MDA     1
  13571.  #define CGA     2
  13572.  #define EGA     4
  13573.  
  13574.  /* --- video modes --- */
  13575.  /* CGA modes */
  13576.  #define CGA_M40         0
  13577.  #define CGA_C40         1
  13578.  #define CGA_M80         2
  13579.  #define CGA_C80         3
  13580.  #define CGA_CMRES       4
  13581.  #define CGA_MMRES       5
  13582.  #define CGA_MHRES       6
  13583.  /* MDA mode */
  13584.  #define MDA_M80         7
  13585.  /* PCjr modes */
  13586.  #define PCJR_CLRES      8
  13587.  #define PCJR_CMRES      9
  13588.  #define PCJR_CHRES      10
  13589.  /* modes 11 and 12 are not currently used */
  13590.  /* EGA modes */
  13591.  #define EGA_CMRES       13
  13592.  #define EGA_CHRES       14
  13593.  #define EGA_MHRES       15
  13594.  #define EGA_EHRES       16
  13595.  
  13596.  /* miscellaneous video masks */
  13597.  #define CMASK   0x00FF    /* character mask */
  13598.  #define AMASK   0xFF00    /* attribute mask */
  13599.  
  13600.  /* attribute modifiers */
  13601.  #define BRIGHT  8
  13602.  #define BLINK   128
  13603.  
  13604.  /* primary video attributes */
  13605.  #define BLU     1
  13606.  #define GRN     2
  13607.  #define RED     4
  13608.  
  13609.  /* composite video attributes */
  13610.  #define BLK     0
  13611.  #define CYAN    (BLU | GRN)             /* 3 */
  13612.  #define MAGENTA (BLU | RED)             /* 5 */
  13613.  #define BRN     (GRN | RED)             /* 6 */
  13614.  #define WHT     (BLU | GRN | RED)       /* 7 */
  13615.  #define GRAY    (BLK | BRIGHT)
  13616.  #define LBLU    (BLU | BRIGHT)
  13617.  #define LGRN    (GRN | BRIGHT)
  13618.  #define LCYAN   (CYAN | BRIGHT)
  13619.  #define LRED    (RED | BRIGHT)
  13620.  #define LMAG    (MAG | BRIGHT)
  13621.  #define YEL     (BRN | BRIGHT)
  13622.  #define BWHT    (WHT | BRIGHT)
  13623.  #define NORMAL  WHT
  13624.  #define REVERSE 112
  13625.  
  13626.  /*      drawing characters -- items having two numbers use
  13627.   *      the first number as the horizontal specifier */
  13628.  
  13629.  /* single-line boxes */
  13630.  #define VBAR1   179
  13631.  #define VLINE   179     /* alias */
  13632.  #define HBAR1   196
  13633.  #define HLINE   196     /* alias */
  13634.  #define ULC11   218
  13635.  #define URC11   191
  13636.  #define LLC11   192
  13637.  #define LRC11   217
  13638.  #define TL11    195
  13639.  #define TR11    180
  13640.  #define TT11    194
  13641.  #define TB11    193
  13642.  #define X11     197
  13643.  
  13644.  /* double-line boxes */
  13645.  #define VBAR2   186
  13646.  #define HBAR2   205
  13647.  #define ULC22   201
  13648.  #define URC22   187
  13649.  #define LLC22   200
  13650.  #define LRC22   188
  13651.  #define TL22    204
  13652.  #define TR22    185
  13653.  #define TT22    203
  13654.  #define TB22    202
  13655.  #define X22     206
  13656.  
  13657.  /* single-line horizontal & double-line vertical boxes */
  13658.  #define ULC12   214
  13659.  #define URC12   183
  13660.  #define LLC12   211
  13661.  #define LRC12   189
  13662.  #define TL12    199
  13663.  #define TR12    182
  13664.  #define TT12    210
  13665.  #define TB12    208
  13666.  #define X12     215
  13667.  
  13668.  /* double-line horizontal & single-line vertical boxes */
  13669.  #define ULC21   213
  13670.  #define URC21   184
  13671.  #define LLC21   212
  13672.  #define LRC21   190
  13673.  #define TL21    198
  13674.  #define TR21    181
  13675.  #define TT21    209
  13676.  #define TB21    207
  13677.  #define X21     216
  13678.  
  13679.  /* full and partial blocks */
  13680.  #define BLOCK   219
  13681.  #define VBAR    219     /* alias */
  13682.  #define VBARL   221
  13683.  #define VBARR   222
  13684.  #define HBART   223
  13685.  #define HBARB   220
  13686.  
  13687.  /* special character-graphic symbols */
  13688.  #define BLANK           32
  13689.  #define DIAMOND         4
  13690.  #define UPARROW         24
  13691.  #define DOWNARROW       25
  13692.  #define RIGHTARROW      26
  13693.  #define LEFTARROW       27
  13694.  #define SLASH           47
  13695.  
  13696.  
  13697.  
  13698.  getstate
  13699.  ───────────────────────────────────────────────────────────────────────────
  13700.  /*      getstate -- update video state structure      */
  13701.  
  13702.  #include <dos.h>
  13703.  #include <local\std.h>
  13704.  #include <local\bioslib.h>
  13705.  
  13706.  /* current video state/mode information */
  13707.  short Vmode;
  13708.  short Vwidth;
  13709.  short Vpage;
  13710.  
  13711.  /*      video tables -- these tables of video parameters use
  13712.   *      a value of -1 to indicate that an item is not supported
  13713.   *      and 0 to indicate that an item has a variable value.      */
  13714.  
  13715.  /* video limit tables */
  13716.  short Maxrow[] = {
  13717.          25, 25, 25, 25, 25, 25, 25,      /* CGA modes */
  13718.          25,                              /* MDA mode */
  13719.          25, 25, 25,                      /* PCjr modes */
  13720.          -1, -1,                          /* not used */
  13721.          25, 25, 25, 43                   /* EGA modes */
  13722.  };
  13723.  
  13724.  short Maxcol[] = {
  13725.          40, 40, 80, 80, 40, 40, 80,      /* CGA modes */
  13726.          80,                              /* MDA mode */
  13727.          -1, 40, 80,                      /* PCjr modes */
  13728.          -1, -1,                          /* not used */
  13729.          80, 80, 80, 80                   /* EGA modes */
  13730.  };
  13731.  
  13732.  short Maxpage[] = {
  13733.          8, 8, 4, 4, 1, 1, 1,             /* CGA modes */
  13734.          1,                               /* MDA mode */
  13735.          0, 0, 0,                         /* PCjr modes */
  13736.          -1, -1,                          /* not used */
  13737.          8, 4, 1, 1                       /* EGA modes */
  13738.  };
  13739.  
  13740.  int getstate()
  13741.  {
  13742.          union REGS inregs, outregs;
  13743.  
  13744.          inregs.h.ah = GET_STATE;
  13745.          int86(VIDEO_IO, &inregs, &outregs);
  13746.  
  13747.          Vmode = outregs.h.al;
  13748.          Vwidth = outregs.h.ah;
  13749.          Vpage = outregs.h.bh;
  13750.          return (outregs.x.cflag);
  13751.  }
  13752.  
  13753.  
  13754.  
  13755.  setvmode
  13756.  ───────────────────────────────────────────────────────────────────────────
  13757.  
  13758.  /*     setvmode -- set the video mode (color/graphics systems only)     */
  13759.  
  13760.  #include <dos.h>
  13761.  #include <local\std.h>
  13762.  #include <local\bioslib.h>
  13763.  
  13764.  /***********************************************************
  13765.  * mode #       description
  13766.  * ------------ ----------------------------------
  13767.  * PC MODES:
  13768.  *       0       40x25 Mono text (c/g default)
  13769.  *       1       40x25 Color text
  13770.  *       2       80x25 Mono text
  13771.  *       3       80x25 Color text
  13772.  *       4       320x200 4-color graphics (med res)
  13773.  *       5       320x200 Mono graphics (med res)
  13774.  *       6       640x200 2-color graphics (hi res)
  13775.  *       7       80x25 on monochrome adapter
  13776.  *
  13777.  * PCjr MODES:
  13778.  *       8       160x200 16-color graphics
  13779.  *       9       320x200 16-color graphics
  13780.  *       10      640x200 4-color fraphics
  13781.  *
  13782.  * EGA MODES:
  13783.  *       13      320x200 16-color graphics
  13784.  *       14      620x200 16-color graphics
  13785.  *       15      640x350 mono graphics
  13786.  *       16      640x350 color graphics (4- or 16-color)
  13787.  ***********************************************************/
  13788.  
  13789.  int setvmode(vmode)
  13790.  unsigned int vmode;     /* user-specified mode number */
  13791.  {
  13792.          union REGS inregs, outregs;
  13793.  
  13794.          inregs.h.ah = SET_MODE;
  13795.          inregs.h.al = vmode;    /* value not checked */
  13796.          int86(VIDEO_IO, &inregs, &outregs);
  13797.  
  13798.          getstate();      /* update video structure */
  13799.          return (outregs.x.cflag);
  13800.  }
  13801.  
  13802.  
  13803.  
  13804.  setpage
  13805.  ───────────────────────────────────────────────────────────────────────────
  13806.  
  13807.  /*      setpage -- select "visual" screen page for viewing
  13808.   *      (the "active" page is the one being written to)      */
  13809.  
  13810.  #include <dos.h>
  13811.  #include <local\std.h>
  13812.  #include <local\bioslib.h>
  13813.  #include <local\video.h>
  13814.  
  13815.  int setpage(pg)
  13816.  int pg; /* visual screen page number */
  13817.  {
  13818.          union REGS inregs, outregs;
  13819.  
  13820.          /* check page number against table */
  13821.          if (Maxpage[Vmode] > 0 && (pg < 0 || pg >= Maxpage[Vmode]))
  13822.                  return (-1);
  13823.  
  13824.          /* change the visual page */
  13825.          inregs.h.ah = SET_PAGE;
  13826.          inregs.h.al = pg;
  13827.          int86(VIDEO_IO, &inregs, &outregs);
  13828.          return (outregs.x.cflag);
  13829.  }
  13830.  
  13831.  
  13832.  
  13833.  readcur
  13834.  ───────────────────────────────────────────────────────────────────────────
  13835.  
  13836.  /*      readcur -- pass back the cursor position (row, col)      */
  13837.  
  13838.  #include <dos.h>
  13839.  #include <local\std.h>
  13840.  #include <local\bioslib.h>
  13841.  
  13842.  unsigned int readcur(row, col, pg)
  13843.  unsigned int *row;      /* current row */
  13844.  unsigned int *col;      /* current column */
  13845.  unsigned int pg;        /* screen page */
  13846.  {
  13847.          union REGS inregs, outregs;
  13848.  
  13849.          inregs.h.ah = GET_CUR;
  13850.          inregs.h.bh = pg;
  13851.  
  13852.          int86(VIDEO_IO, &inregs, &outregs);
  13853.  
  13854.          *col = outregs.h.dl;            /* col */
  13855.          *row = outregs.h.dh;            /* row */
  13856.  
  13857.          return (outregs.x.cflag);
  13858.  }
  13859.  
  13860.  
  13861.  
  13862.  putcur
  13863.  ───────────────────────────────────────────────────────────────────────────
  13864.  
  13865.  /*      putcur -- put cursor at specified position (row, col)      */
  13866.  
  13867.  #include <dos.h>
  13868.  #include <local\std.h>
  13869.  #include <local\bioslib.h>
  13870.  
  13871.  unsigned int putcur(r, c, pg)
  13872.  unsigned int
  13873.          r,      /* row */
  13874.          c,      /* column */
  13875.          pg;     /* screen page for writes */
  13876.  {
  13877.          union REGS inregs, outregs;
  13878.  
  13879.          inregs.h.ah = CUR_POS;
  13880.          inregs.h.bh = pg & 0x07;
  13881.          inregs.h.dh = r & 0xFF;
  13882.          inregs.h.dl = c & 0xFF;
  13883.  
  13884.          int86(VIDEO_IO, &inregs, &outregs);
  13885.  
  13886.          return (outregs.x.cflag);
  13887.  }
  13888.  
  13889.  
  13890.  
  13891.  getctype
  13892.  ───────────────────────────────────────────────────────────────────────────
  13893.  
  13894.  /*      getctype -- pass back cursor type info (scan lines)      */
  13895.  
  13896.  #include <dos.h>
  13897.  #include <local\std.h>
  13898.  #include <local\bioslib.h>
  13899.  
  13900.  #define LO_NIBBLE       0x0F
  13901.  
  13902.  int getctype(start_scan, end_scan, pg)
  13903.  int *start_scan;        /* starting scan line */
  13904.  int *end_scan;          /* ending scan line */
  13905.  int pg;                 /* "visual" page */
  13906.  {
  13907.          union REGS inregs, outregs;
  13908.  
  13909.          inregs.h.bh = pg;
  13910.          inregs.h.ah = GET_CUR;
  13911.  
  13912.          int86(VIDEO_IO, &inregs, &outregs);
  13913.  
  13914.          /* end_scan = low 4 bits of cl */
  13915.          *end_scan = outregs.h.cl & LO_NIBBLE;
  13916.  
  13917.          /* starting_scan = low 4 bits of ah */
  13918.          *start_scan = outregs.h.ch & LO_NIBBLE;
  13919.  
  13920.          return (outregs.x.cflag);
  13921.  }
  13922.  
  13923.  
  13924.  
  13925.  setctype
  13926.  ───────────────────────────────────────────────────────────────────────────
  13927.  
  13928.  /*      setctype -- set the cursor start and end raster scan lines      */
  13929.  
  13930.  #include <dos.h>
  13931.  #include <local\bioslib.h>
  13932.  
  13933.  #define LO_NIBBLE       0x0F
  13934.  #define CURSOR_OFF      0x2
  13935.  #define MAXSCANLN       15
  13936.  
  13937.  int setctype(start, end)
  13938.  int start;      /* starting raster scan line */
  13939.  int end;        /* ending raster scan line */
  13940.  {
  13941.          union REGS inregs, outregs;
  13942.  
  13943.          inregs.h.ah = CUR_TYPE;
  13944.          inregs.h.ch = start & LO_NIBBLE;
  13945.          inregs.h.cl = end & LO_NIBBLE;
  13946.          if (start >= MAXSCANLN) {
  13947.                  inregs.h.ah |= CURSOR_OFF;
  13948.                  inregs.h.al = MAXSCANLN;
  13949.          }
  13950.          int86(VIDEO_IO, &inregs, &outregs);
  13951.  
  13952.          return (outregs.x.cflag);
  13953.  }
  13954.  
  13955.  
  13956.  
  13957.  readca
  13958.  ───────────────────────────────────────────────────────────────────────────
  13959.  
  13960.  /*      readca -- read character and attribute at current position      */
  13961.  
  13962.  #include <dos.h>
  13963.  #include <local\std.h>
  13964.  #include <local\bioslib.h>
  13965.  
  13966.  int readca(ch, attr, pg)
  13967.  unsigned char *ch;
  13968.  unsigned char *attr;
  13969.  unsigned int pg;        /* screen page for reads */
  13970.  {
  13971.          union REGS inregs, outregs;
  13972.  
  13973.          inregs.h.ah = READ_CHAR_ATTR;
  13974.          inregs.h.bh = pg;               /* display page */
  13975.  
  13976.          int86(VIDEO_IO, &inregs, &outregs);
  13977.  
  13978.          *ch = outregs.h.al;             /* character */
  13979.          *attr = outregs.h.ah;           /* attribute */
  13980.  
  13981.          /* return the value in AX register */
  13982.          return (outregs.x.cflag);
  13983.  }
  13984.  
  13985.  
  13986.  
  13987.  writeca
  13988.  ───────────────────────────────────────────────────────────────────────────
  13989.  
  13990.  /*      writeca -- write character and attribute to the screen      */
  13991.  
  13992.  #include <dos.h>
  13993.  #include <local\std.h>
  13994.  #include <local\bioslib.h>
  13995.  
  13996.  int writeca(ch, attr, count, pg)
  13997.  unsigned char ch;       /* character */
  13998.  unsigned char attr;     /* attribute */
  13999.  int count;              /* number of repetitions */
  14000.  int pg;                 /* screen page for writes */
  14001.  {
  14002.          union REGS inregs, outregs;
  14003.  
  14004.          inregs.h.ah = WRITE_CHAR_ATTR;
  14005.          inregs.h.al = ch;
  14006.          inregs.h.bh = pg;
  14007.          inregs.h.bl = attr;
  14008.          inregs.x.cx = count;
  14009.          int86(VIDEO_IO, &inregs, &outregs);
  14010.  
  14011.          return (outregs.x.cflag);
  14012.  }
  14013.  
  14014.  
  14015.  
  14016.  clrscrn
  14017.  ───────────────────────────────────────────────────────────────────────────
  14018.  
  14019.  /*      clrscrn -- clear the "visual" screen page      */
  14020.  
  14021.  #include <dos.h>
  14022.  #include <local\std.h>
  14023.  #include <local\bioslib.h>
  14024.  #include <local\video.h>
  14025.  
  14026.  int clrscrn(a)
  14027.  unsigned int a;        /* video attribute for new lines */
  14028.  {
  14029.          union REGS inregs, outregs;
  14030.  
  14031.          inregs.h.ah = SCROLL_UP;
  14032.          inregs.h.al = 0;                 /* blank entire window */
  14033.          inregs.h.bh = a;                 /* use specified attribute */
  14034.          inregs.h.bl = 0;
  14035.          inregs.x.cx = 0;                 /* upper left corner */
  14036.          inregs.h.dh = Maxrow[Vmode] - 1; /* bottom screen row */
  14037.          inregs.h.dl = Maxcol[Vmode] - 1; /* rightmost column */
  14038.          int86(VIDEO_IO, &inregs, &outregs);
  14039.  
  14040.          return (outregs.x.cflag);
  14041.  }
  14042.  
  14043.  
  14044.  
  14045.  clrw
  14046.  ───────────────────────────────────────────────────────────────────────────
  14047.  
  14048.  /*      clrw -- clear specified region of "visual" screen page      */
  14049.  
  14050.  #include <dos.h>
  14051.  #include <local\std.h>
  14052.  #include <local\bioslib.h>
  14053.  
  14054.  int clrw(t, l, b, r, a)
  14055.  int t;            /* top row of region to clear */
  14056.  int l;            /* left column */
  14057.  int b;            /* bottom row */
  14058.  int r;            /* right column */
  14059.  unsigned char a;  /* attribute for cleared region */
  14060.  {
  14061.          union REGS inregs, outregs;
  14062.  
  14063.          inregs.h.ah = SCROLL_UP; /* scroll visual page up */
  14064.          inregs.h.al = 0;         /* blank entire window */
  14065.          inregs.h.bh = a;         /* attribute of blank lines */
  14066.          inregs.h.bl = 0;
  14067.          inregs.h.ch = t;         /* upper left of scroll region */
  14068.          inregs.h.cl = l;
  14069.          inregs.h.dh = b;         /* lower right of scroll region */
  14070.          inregs.h.dl = r;
  14071.          int86(VIDEO_IO, &inregs, &outregs);
  14072.  
  14073.          return (outregs.x.cflag);
  14074.  }
  14075.  
  14076.  
  14077.  
  14078.  scroll
  14079.  ───────────────────────────────────────────────────────────────────────────
  14080.  
  14081.  /*      scroll -- scroll a region of the "visual" screen
  14082.   *      page up or down by n rows (0 = initialize region)      */
  14083.  
  14084.  #include <dos.h>
  14085.  #include <local\std.h>
  14086.  #include <local\bioslib.h>
  14087.  
  14088.  int scroll(t, l, b, r, n, a)
  14089.  int t;  /* top row of scroll region */
  14090.  int l;  /* left column */
  14091.  int b;  /* bottom row */
  14092.  int r;  /* right column */
  14093.  int n;  /* number of lines to scroll */
  14094.          /* sign indicates direction to scroll */
  14095.          /* 0 means scroll all lines in the region (initialize) */
  14096.  unsigned char a;/* attribute for new lines */
  14097.  {
  14098.          union REGS inregs, outregs;
  14099.  
  14100.          if (n < 0) {
  14101.                  /* scroll visual page down n lines */
  14102.                  inregs.h.ah = SCROLL_DN;
  14103.                  inregs.h.al = -n;
  14104.          }
  14105.          else {
  14106.                  /* scroll visual page up n lines */
  14107.                  inregs.h.ah = SCROLL_UP;
  14108.                  inregs.h.al = n;
  14109.          }
  14110.          inregs.h.bh = a;        /* attribute of blank lines */
  14111.          inregs.h.bl = 0;
  14112.          inregs.h.ch = t;        /* upper-left of scroll region */
  14113.          inregs.h.cl = l;
  14114.          inregs.h.dh = b;        /* lower-right of scroll region */
  14115.          inregs.h.dl = r;
  14116.          int86(VIDEO_IO, &inregs, &outregs);
  14117.  
  14118.          return (outregs.x.cflag);
  14119.  }
  14120.  
  14121.  
  14122.  
  14123.  writea
  14124.  ───────────────────────────────────────────────────────────────────────────
  14125.  
  14126.  /*      writea -- write attribute only to screen memory (faked by
  14127.   *      reading char and attr and writing back the original
  14128.   *      character and the new attribute at each position)      */
  14129.  
  14130.  #include <local\std.h>
  14131.  
  14132.  int writea(a, n, pg)
  14133.  unsigned char a;/* video attribute */
  14134.  int n;          /* number of positions to write */
  14135.  int pg;         /* screen page */
  14136.  {
  14137.          int i;
  14138.          int status;
  14139.          unsigned short chx, attrx;
  14140.          unsigned short r, c;
  14141.  
  14142.          /* get starting (current) position */
  14143.          status = 0;
  14144.          status = readcur(&r, &c, pg);
  14145.          for (i = 0; i < n; ++i) {
  14146.                  status += putcur(r, c + i, pg);
  14147.                  status += readca(&chx, &attrx, pg);
  14148.                  status += writeca(chx, a, 1, pg);
  14149.          }
  14150.  
  14151.          /* restore cursor position */
  14152.          status += putcur(r, c, pg);
  14153.  
  14154.          return (status);
  14155.  }
  14156.  
  14157.  
  14158.  
  14159.  drawbox
  14160.  ───────────────────────────────────────────────────────────────────────────
  14161.  
  14162.  /*      drawbox -- create a box with IBM line-drawing characters      */
  14163.  
  14164.  #include <local\video.h>
  14165.  
  14166.  int drawbox(top, lft, btm, rgt, pg)
  14167.  int top, lft, btm, rgt, pg;
  14168.  {
  14169.          int i;
  14170.          int x;  /* interior line length for top and bottom segments */
  14171.  
  14172.          x = rgt - lft - 1;
  14173.  
  14174.          /* draw the top row */
  14175.          putcur(top, lft, pg);
  14176.          put_ch(ULC11, pg);
  14177.          writec(HBAR1, x, pg);
  14178.          putcur(top, rgt, pg);
  14179.          put_ch(URC11, pg);
  14180.  
  14181.          /* draw the sides */
  14182.          for (i = 1; i < btm - top; ++i)
  14183.          {
  14184.                  putcur(top + i, lft, pg);
  14185.                  put_ch(VBAR1, pg);
  14186.                  putcur(top + i, rgt, pg);
  14187.                  put_ch(VBAR1, pg);
  14188.          }
  14189.  
  14190.          /* draw the bottom row */
  14191.          putcur(btm, lft, pg);
  14192.          put_ch(LLC11, pg);
  14193.          writec(HBAR1, x, pg);
  14194.          putcur(btm, rgt, pg);
  14195.          put_ch(LRC11, pg);
  14196.  }
  14197.  
  14198.  
  14199.  
  14200.  cursor
  14201.  ───────────────────────────────────────────────────────────────────────────
  14202.  
  14203.  /*      cursor -- interactively set cursor shape      */
  14204.  
  14205.  #include <dos.h>
  14206.  #include <stdlib.h>
  14207.  #include <local\video.h>
  14208.  #include <local\keydefs.h>
  14209.  
  14210.  /* additional drawing characters (others are defined in video.h) */
  14211.  #define DOT     254
  14212.  #define NO_DOT  196
  14213.  #define D_POINT 31
  14214.  #define R_POINT 16
  14215.  #define L_POINT 17
  14216.  
  14217.  /* dimensions of the help frame */
  14218.  #define BOX_Y   6
  14219.  #define BOX_X   30
  14220.  
  14221.  /* upper-left row and column of big cursor */
  14222.  int Ulr, Ulc;
  14223.  int Mid;
  14224.  
  14225.  /* cursor scan-line-selection modes */
  14226.  typedef enum { STARTSCAN, ENDSCAN } CMODE;
  14227.  
  14228.  int main()
  14229.  {
  14230.         int i, j, ch;
  14231.         int start, end;
  14232.         int height, width;
  14233.         static char spoint[] = { "Start\020" }; /* contains right pointer */
  14234.         static char epoint[] = { "\021Stop" };  /* contains left pointer */
  14235.         static char title[] = { "CURSOR: Control cursor shape (V1.0)" };
  14236.         unsigned char
  14237.                 oldattr,        /* video attribute upon entry */
  14238.                 headattr,       /* video attribute of header */
  14239.                 attr,           /* primary video attribute */
  14240.                 standout;       /* highlighting video attribute */
  14241.         CMODE mode;
  14242.  
  14243.         static void drawdspy(int, int, int, int, int);
  14244.         static void drawstart(int, char *);
  14245.         static void drawend(int, int, char *);
  14246.         static void drawactive(int, int, CMODE);
  14247.         static void showhelp(int, int);
  14248.         static void writestr(char *, int);
  14249.  
  14250.         /* get video information and initialize */
  14251.         getstate();
  14252.         Mid = Vwidth / 2;
  14253.         readca(&ch, &oldattr, Vpage);  /* preserve user's video attribute */
  14254.         getctype(&start, &end, Vpage); /* and cursor shape */
  14255.         headattr = (WHT << 4) | BLK;
  14256.  
  14257.         /* set parameters based on video mode (default = CGA) */
  14258.         height = width = 8;     /* use an 8 by 8 block character cell */
  14259.         attr = (BLU << 4) | CYAN | BRIGHT;
  14260.         standout = YEL;
  14261.         if (Vmode == MDA_M80) {
  14262.                 /* uses a 14 by 9 dot block character cell */
  14263.                 height = 14;
  14264.                 width = 9;
  14265.                 attr = NORMAL;
  14266.                 standout = BWHT;
  14267.         }
  14268.         setctype(height + 1, height + 1);       /* cursor off */
  14269.  
  14270.         /* basic text and layout */
  14271.         Ulr = 2;
  14272.         Ulc = Mid - width / 2;
  14273.         clrscrn(attr);
  14274.         putcur(0, 0, Vpage);
  14275.         writeca(' ', headattr, Vwidth, Vpage);
  14276.         putcur(0, Mid - strlen(title) / 2, Vpage);
  14277.         writestr(title, Vpage);
  14278.         showhelp(Ulr + height + 1, Mid - BOX_X / 2);
  14279.  
  14280.         /* interactively select cursor shape */
  14281.         mode = STARTSCAN;
  14282.         drawdspy(start, end, standout, width, height);
  14283.         drawstart(start, spoint);
  14284.         drawend(end, width, epoint);
  14285.         drawactive(height, width, mode);
  14286.         while (1) {
  14287.                 switch (ch = getkey()) {
  14288.                 case K_UP:
  14289.                         /* move up one scan line */
  14290.                         if (mode == STARTSCAN)
  14291.                                 drawstart(start--, "      ");
  14292.                         else
  14293.                                 drawend(end--, width, "     ");
  14294.                         break;
  14295.                 case K_DOWN:
  14296.                         /* move down one scan line */
  14297.                         if (mode == STARTSCAN)
  14298.                                 drawstart(start++, "      ");
  14299.                         else
  14300.                                 drawend(end++, width, "     ");
  14301.                         break;
  14302.                 case K_LEFT:
  14303.                         /* starting scan-line-selection mode */
  14304.                         mode = STARTSCAN;
  14305.                         drawactive(height, width, mode);
  14306.                         continue;
  14307.                 case K_RIGHT:
  14308.                         /* ending scan-line-selection mode */
  14309.                         mode = ENDSCAN;
  14310.                         drawactive(height, width, mode);
  14311.                         continue;
  14312.                 case K_RETURN:
  14313.                         /* set the new cursor shape */
  14314.                         setctype(start, end);
  14315.                         clrscrn(oldattr);
  14316.                         putcur(0, 0, Vpage);
  14317.                         exit(0);
  14318.                 }
  14319.  
  14320.                 /* make corrections at cursor image boundaries */
  14321.                 if (start < 0)
  14322.                         start = 0;
  14323.                 else if (start > height)
  14324.                         start = height;
  14325.                 if (end < 0)
  14326.                         end = 0;
  14327.                 else if (end >= height)
  14328.                         end = height - 1;
  14329.  
  14330.                 /* show updated cursor shape and pointers */
  14331.                 drawdspy(start, end, standout, width, height);
  14332.                 drawstart(start, spoint);
  14333.                 drawend(end, width, epoint);
  14334.         }
  14335.         exit(0);
  14336.  } /* end main() */
  14337.  
  14338.  
  14339.  /*      drawdspy -- draw a magnified image of a cursor with the
  14340.   *      currently active scan lines depicted as a sequence of dots
  14341.   *      and inactive lines depicted as straight lines      */
  14342.  
  14343.  static void drawdspy(s, e, a, w, h)
  14344.  int s;  /* starting scan line */
  14345.  int e;  /* ending scan line */
  14346.  int a;  /* video attribute */
  14347.  int w;  /* width */
  14348.  int h;  /* height */
  14349.  {
  14350.          int i;
  14351.  
  14352.          /* display an exploded image of each scan line */
  14353.          for (i = 0; i < h; ++i) {
  14354.                  putcur(Ulr + i, Ulc, Vpage);
  14355.                  if (s >= h)
  14356.                          /* cursor is effectively off */
  14357.                          writeca(NO_DOT, a, w, Vpage);
  14358.                  else if ((s <= e && i >= s && i <= e) || /* a full block */
  14359.                          (s > e && (i <= e || i >= s)))  /* a split block */
  14360.                          writeca(DOT, a, w, Vpage);
  14361.                  else
  14362.                          /* outside start/end range */
  14363.                          writeca(NO_DOT, a, w, Vpage);
  14364.          }
  14365.  } /* end drawdspy() */
  14366.  
  14367.  
  14368.  /*      drawstart -- display a pointer to the displayed starting
  14369.   *      scan line in the magnified cursor image      */
  14370.  
  14371.  static void drawstart(s, sp)
  14372.  int s;          /* starting scan line number */
  14373.  char *sp;       /* visual pointer to the displayed starting scan line */
  14374.  {
  14375.          putcur(Ulr + s, Ulc - strlen(sp), Vpage);
  14376.          putstr(sp, Vpage);
  14377.  } /* end drawstart() */
  14378.  
  14379.  
  14380.  /*      drawend -- display a pointer to the displayed ending
  14381.   *      scan line in the magnified cursor image      */
  14382.  
  14383.  static void drawend(e, w, ep)
  14384.  int e;          /* ending scan line number */
  14385.  int w;          /* width of the cursor image */
  14386.  char *ep;       /* visual pointer to the displayed ending scan line */
  14387.  {
  14388.          putcur(Ulr + e, Ulc + w, Vpage);
  14389.          putstr(ep, Vpage);
  14390.  } /* end drawend() */
  14391.  
  14392.  static void drawactive(h, w, m)
  14393.  int h, w;
  14394.  CMODE m;
  14395.  {
  14396.          int col;
  14397.  
  14398.          /* clear active selector row */
  14399.          putcur(Ulr - 1, Ulc, Vpage);
  14400.          writec(' ', w, Vpage);
  14401.  
  14402.          /* point to active selector */
  14403.          col = (m == STARTSCAN) ? 0 : w - 1;
  14404.          putcur(Ulr - 1, Ulc + col, Vpage);
  14405.          writec(D_POINT, 1, Vpage);
  14406.  } /* end drawactive() */
  14407.  
  14408.  /      showhelp -- display a set of instructions about the
  14409.   *      use of the cursor program in a fine-ruled box      */
  14410.  
  14411.  static void showhelp(r, c)
  14412.  int r, c;       /* upper-left corner of help frame */
  14413.  {
  14414.          static char title[] = { " Instructions " };
  14415.          extern int drawbox(int, int, int, int, int);
  14416.  
  14417.          /* fine-ruled box */
  14418.          clrw(r, c, r + BOX_Y, c + BOX_X, (WHT << 4) | GRN | BRIGHT);
  14419.          drawbox(r, c, r + BOX_Y, c + BOX_X, Vpage);
  14420.  
  14421.          /* centered title */
  14422.          putcur(r, c + (BOX_X - strlen(title)) / 2, Vpage);
  14423.          putstr(title, Vpage);
  14424.  
  14425.          /* display symbols and text using brute-force positioning */
  14426.          putcur(r + 2, c + 2, Vpage);
  14427.          put_ch(LEFTARROW, Vpage);
  14428.          put_ch(RIGHTARROW, Vpage);
  14429.          putstr("  Change selection mode", Vpage);
  14430.          putcur(r + 3, c + 2, Vpage);
  14431.          put_ch(UPARROW, Vpage);
  14432.          put_ch(DOWNARROW, Vpage);
  14433.          putstr("  Select scan lines", Vpage);
  14434.          putcur(r + 4, c + 2, Vpage);
  14435.          put_ch(L_POINT, Vpage);
  14436.          put_ch(LRC11, Vpage);
  14437.          putstr("  Set shape and exit", Vpage);
  14438.  } /* end showhelp() */
  14439.  
  14440.  
  14441.  /*     writestr -- write a string in the prevailing video attribute      */
  14442.  
  14443.  static void writestr(s, pg)
  14444.  char *s                 /* string to write*/
  14445.  unsigned int pg;        /* screen page for writes */
  14446.  {
  14447.          unsigned int r, c, cO;
  14448.          readcur(&r, &c, pg);
  14449.          for (cO = c; *s != '\0'; ++s, ++c) {
  14450.                  putcur(r, c, pg);
  14451.                  writec(*s, 1, pg);
  14452.          }
  14453.          /* restore cursor position */
  14454.          putcur(r, cO, pg);
  14455.  } /* end writestr() */
  14456.  
  14457.  
  14458.  
  14459.  makefile for the BIOS library
  14460.  ───────────────────────────────────────────────────────────────────────────
  14461.  
  14462.  # makefile for the BIOS library
  14463.  LINC = c:\include\local
  14464.  LLIB = c:\lib\local
  14465.  
  14466.  # --- inference rules ---
  14467.  .c.obj:
  14468.         msc $*;
  14469.  
  14470.  # --- objects ---
  14471.  O1 = clrscrn.obj clrw.obj drawbox.obj equipchk.obj getctype.obj
  14472.        getstate.obj
  14473.  O2 = kbd_stat.obj memsize.obj palette.obj putcur.obj putstr.obj put_ch.obj
  14474.  O3 = readca.obj readcur.obj readdot.obj scroll.obj setctype.obj setpage.obj
  14475.  O4 = setvmode.obj writea.obj writec.obj writeca.obj writedot.obj
  14476.        writetty.obj
  14477.  
  14478.  # --- compile sources ---
  14479.  clrscrn.obj:    clrscrn.c $(LINC)\bioslib.h $(LINC)\std.h
  14480.  
  14481.  clrw.obj:      clrw.c $(LINC)\bioslib.h $(LINC)\std.h
  14482.  
  14483.  drawbox.obj:   drawbox.c $(LINC)\video.h
  14484.  
  14485.  equipchk.obj:  equipchk.c $(LINC)\bioslib.h $(LINC)\equip.h
  14486.  
  14487.  getctype.obj:  getctype.c $(LINC)\bioslib.h $(LINC)\std.h
  14488.  
  14489.  getstate.obj:  getstate.c $(LINC)\bioslib.h $(LINC)\std.h
  14490.  
  14491.  kbd_stat.obj:  kbd_stat.c $(LINC)\bioslib.h $(LINC)\keybdlib.h
  14492.  
  14493.  palette.obj:   palette.c $(LINC)\bioslib.h
  14494.  
  14495.  putcur.obj:    putcur.c $(LINC)\bioslib.h $(LINC)\std.h
  14496.  
  14497.  putstr.obj:    putstr.c $(LINC)\bioslib.h
  14498.  
  14499.  put_ch.obj:    put_ch.c $(LINC)\bioslib.h
  14500.  
  14501.  readca.obj:    readca.c $(LINC)\bioslib.h $(LINC)\std.h
  14502.  
  14503.  readcur.obj:   readcur.c $(LINC)\bioslib.h $(LINC)\std.h
  14504.  
  14505.  readdot.obj:   readdot.c $(LINC)\bioslib.h $(LINC)\std.h
  14506.  
  14507.  scroll.obj:    scroll.c $(LINC)\bioslib.h $(LINC)\std.h
  14508.  
  14509.  setctype.obj:  setctype.c $(LINC)\bioslib.h
  14510.  
  14511.  setpage.obj:   setpage.c $(LINC)\bioslib.h $(LINC)\std.h $(LINC)\video.h
  14512.  
  14513.  setvmode.obj:  setvmode.c $(LINC)\bioslib.h $(LINC)\std.h
  14514.  
  14515.  writea.obj:    writea.c $(LINC)\std.h
  14516.  
  14517.  writec.obj:    writec.c $(LINC)\bioslib.h $(LINC)\std.h
  14518.  
  14519.  writeca.obj:   writeca.c $(LINC)\bioslib.h $(LINC)\std.h
  14520.  
  14521.  writedot.obj:  writedot.c $(LINC)\bioslib.h $(LINC)\std.h
  14522.  
  14523.  writetty.obj:  writetty.c $(LINC)\bioslib.h $(LINC)\std.h
  14524.  
  14525.  
  14526.  # --- create and install the library ---
  14527.  $(LLIB)\bios.lib:   $(O1) $(O2) $(O3) $(O4)
  14528.          del $(LLIB)\bios.lib
  14529.          lib $(LLIB)\bios +$(O1);
  14530.          lib $(LLIB)\bios +$(O2);
  14531.          lib $(LLIB)\bios +$(O3);
  14532.          lib $(LLIB)\bios +$(O4);
  14533.          del $(LLIB)\bios.bak
  14534.  
  14535.  
  14536.  
  14537.  writemsg
  14538.  ───────────────────────────────────────────────────────────────────────────
  14539.  
  14540.  /*   writemsg -- displays a message in a field of the prevailing
  14541.   *   video attribute and returns the number of displayable message
  14542.   *   characters written; truncates the message if its too long
  14543.   *   to fit in the field   */
  14544.  
  14545.  #include <stdio.h>
  14546.  #include <local\std.h>
  14547.  
  14548.  int writemsg(r, c, w, s1, s2, pg)
  14549.  int r, c, w ;
  14550.  char *s1, *s2;
  14551.  int pg;
  14552.  {
  14553.       int n = 0;
  14554.       char *cp;
  14555.  
  14556.       /* display first part of the message */
  14557.       if (s1 != NULL)
  14558.               for (cp = s1; *cp != '\0' && n < w; ++n, ++cp) {
  14559.                       putcur(r, c + n, pg);
  14560.                       writec(*cp, 1, pg);
  14561.               }
  14562.  
  14563.       /* display second part of the message */
  14564.       if (s2 != NULL)
  14565.               for (cp = s2; *cp != '\0' && n < w; ++n, ++cp) {
  14566.                       putcur(r, c + n, pg);
  14567.                       writec(*cp, 1, pg);
  14568.               }
  14569.  
  14570.       /* pad the remainder of the field, if any, with spaces */
  14571.       if (n < w) {
  14572.               putcur(r, c + n, pg);
  14573.               writec(' ', w - n, pg);
  14574.       }
  14575.       return (n);
  14576.  }
  14577.  
  14578.  
  14579.  
  14580.  getopt()
  14581.  ───────────────────────────────────────────────────────────────────────────
  14582.  
  14583.  /*
  14584.   *      Copyright (c) 1984, 1985 AT&T
  14585.   *      All Rights Reserved
  14586.   *
  14587.   *      ----- Author's Note -----
  14588.   *      getopt() is reproduced with permission of the AT&T UNIX(R) System
  14589.   *      Toolchest. This is a public domain version of getopt(3) that is
  14590.   *      distributed to registered Toolchest participants.
  14591.   *      Defining DOS_MODS alters the code slightly to obtain compatibility
  14592.   *      with DOS and support libraries provided with most DOS C compilers.
  14593.   */
  14594.  
  14595.  #define DOS_MODS
  14596.  
  14597.  #if defined (DOS_MODS)
  14598.          /* getopt() for DOS */
  14599.  #else
  14600.  #ident  "@(#)getopt.c   1.9"
  14601.  #endif
  14602.  
  14603.  /*      3.0 SID #       1.2     */
  14604.  /*LINTLIBRARY*/
  14605.  #define NULL    0
  14606.  #define EOF     (-1)
  14607.  
  14608.  /*
  14609.   *      For this to work under versions of DOS prior to 3.00, argv[0]
  14610.   *      must be set in main() to point to a valid program name or a
  14611.   *      reasonable substitute string.   (ARH, 10-8-86)
  14612.   */
  14613.  #define ERR(s, c)       if(opterr){\
  14614.          char errbuf[2];\
  14615.          errbuf[0] = c; errbuf[1] = '\n';\
  14616.          (void) write(2, argv[0], (unsigned)strlen(argv[0]));\
  14617.          (void) write(2, s, (unsigned)strlen(s));\
  14618.          (void) write(2, errbuf, 2);}
  14619.  
  14620.  #if defined (DOS_MODS)
  14621.  /* permit function prototyping under DOS */
  14622.  #include <stdlib.h>
  14623.  #include <string.h>
  14624.  #else
  14625.  /* standard UNIX declarations */
  14626.  extern int strcmp();
  14627.  extern char *strchr();
  14628.  /*
  14629.   *      The following line was moved here from the ERR definition
  14630.   *      to prevent a "duplicate definition" error message when the
  14631.   *      code is compiled under DOS.  (ARH, 10-8-86)
  14632.   */
  14633.  extern int strlen(), write();
  14634.  #endif
  14635.  
  14636.  int     opterr = 1;
  14637.  int     optind = 1;
  14638.  int     optopt;
  14639.  char    *optarg;
  14640.  
  14641.  int
  14642.  getopt(argc, argv, opts)
  14643.  int     argc;
  14644.  char    **argv, *opts;
  14645.  {
  14646.          static int sp = 1;
  14647.          register int c;
  14648.          register char *cp;
  14649.  
  14650.          if(sp == 1)
  14651.                  if(optind >= argc ||
  14652.                     argv[optind][0] != '-' || argv[optind][1] == '\0')
  14653.                          return(EOF);
  14654.                  else if(strcmp(argv[optind], "--") == NULL) {
  14655.                          optind++;
  14656.                          return(EOF);
  14657.                  }
  14658.          optopt = c = argv[optind][sp];
  14659.          if(c == ':' || (cp=strchr(opts, c)) == NULL) {
  14660.                  ERR(": illegal option -- ", c);
  14661.                  if(argv[optind][++sp] == '\0') {
  14662.                          optind++;
  14663.                          sp = 1;
  14664.                  }
  14665.                  return('?');
  14666.          }
  14667.          if(*++cp == ':') {
  14668.                  if(argv[optind][sp+1] != '\0')
  14669.                          optarg = &argv[optind++][sp+1];
  14670.                  else if(++optind >= argc) {
  14671.                          ERR(": option requires an argument -- ", c);
  14672.                          sp = 1;
  14673.                          return('?');
  14674.                  } else
  14675.                          optarg = argv[optind++];
  14676.                  sp = 1;
  14677.          } else {
  14678.                  if(argv[optind][++sp] == '\0') {
  14679.                          sp = 1;
  14680.                          optind++;
  14681.                  }
  14682.                  optarg = NULL;
  14683.          }
  14684.          return(c);
  14685.  }
  14686.  
  14687.  
  14688.  
  14689.  cat
  14690.  ───────────────────────────────────────────────────────────────────────────
  14691.  
  14692.  /*
  14693.   *      cat -- concatenate files
  14694.   */
  14695.  
  14696.  #include <stdio.h>
  14697.  #include <stdlib.h>
  14698.  #include <local\std.h>
  14699.  
  14700.  main(argc, argv)
  14701.  int argc;
  14702.  char **argv;
  14703.  {
  14704.          int ch;
  14705.          char *cp;
  14706.          FILE *fp;
  14707.          BOOLEAN errflag, silent;
  14708.          static char pgm[MAXNAME + 1] = { "cat" };
  14709.  
  14710.          extern void getpname(char *, char *);
  14711.          extern int fcopy(FILE *, FILE *);
  14712.          extern int getopt(int, char **, char *);
  14713.          extern int optind;
  14714.          extern char *optarg;
  14715.  
  14716.          /* use an alias if one is given to this program */
  14717.          if (_osmajor >= 3)
  14718.                  getpname(*argv, pgm);
  14719.  
  14720.          /* process optional arguments, if any */
  14721.          errflag = FALSE;
  14722.          silent = FALSE;
  14723.          while ((ch = getopt(argc, argv, "s")) != EOF)
  14724.                  switch (ch) {
  14725.                  case 's':
  14726.                          /* don't complain about nonexistent files */
  14727.                          silent = TRUE;
  14728.                          break;
  14729.                  case '?':
  14730.                          /* say what? */
  14731.                          errflag = TRUE;
  14732.                          break;
  14733.                  }
  14734.          if (errflag == TRUE) {
  14735.                  fprintf(stderr, "Usage: %s [-s] file...\n", pgm);
  14736.                  exit(1);
  14737.          }
  14738.  
  14739.          /* process any remaining arguments */
  14740.          argc -= optind;
  14741.          argv += optind;
  14742.          if (argc == 0)
  14743.                  /* no file names -- use standard input */
  14744.                  if (fcopy(stdin, stdout) != 0) {
  14745.                          fprintf(stderr, "error copying stdin");
  14746.                          exit(2);
  14747.                  }
  14748.                  else
  14749.                          exit(0);
  14750.  
  14751.          /* copy the contents of each named file to standard output */
  14752.          for (; argc-- > 0; ++argv) {
  14753.                  if ((fp = fopen(*argv, "r")) == NULL) {
  14754.                          if (silent == FALSE)
  14755.                                  fprintf(stderr, "%s: can't open %s\n",
  14756.                                          pgm, *argv);
  14757.                          continue;
  14758.                  }
  14759.                  if (fcopy(fp, stdout) != 0) {
  14760.                          fprintf(stderr, "%s: Error while copying %s",
  14761.                                  pgm, *argv);
  14762.                          exit(3);
  14763.                  }
  14764.                  if (fclose(fp) != 0) {
  14765.                          fprintf(stderr, "%s: Error closing %s",
  14766.                                  pgm, *argv);
  14767.                          exit(4);
  14768.                  }
  14769.          }
  14770.  
  14771.          exit(0);
  14772.  }
  14773.  
  14774.  
  14775.  
  14776.  timer
  14777.  ───────────────────────────────────────────────────────────────────────────
  14778.  
  14779.  /*
  14780.   *      timer -- general purpose timer program; uses the
  14781.   *      PC's intra-application communication area (ICA) as
  14782.   *      a time and date buffer
  14783.   */
  14784.  
  14785.  #include <dos.h>
  14786.  #include <stdio.h>
  14787.  #include <stdlib.h>
  14788.  #include <string.h>
  14789.  #include <time.h>
  14790.  #include <memory.h>
  14791.  #include <local\std.h>
  14792.  
  14793.  #define NBYTES          16
  14794.  #define ICA_SEG         0x4F
  14795.  #define MAXTNUM         3
  14796.  #define TIMEMASK        0x3FFFFFFF
  14797.  #define FLAGBIT         0x80000000
  14798.  #define IDBIT           0x40000000
  14799.  
  14800.  main(argc, argv)
  14801.  int argc;
  14802.  char *argv[];
  14803.  {
  14804.          int ch;
  14805.          char *cp;
  14806.          int tn;                 /* timer number */
  14807.          int errflag;            /* error flag */
  14808.          int eflag;              /* elapsed time flag */
  14809.          int sflag;              /* start timer flag */
  14810.          char dest[MAXPATH + 1]; /* destination file name */
  14811.          char timestr[MAXLINE];  /* buffer for elapsed time string */
  14812.          long now;               /* current time */
  14813.          long then;              /* previously recorded time */
  14814.          FILE *fout;
  14815.          unsigned long tdata[MAXTNUM];
  14816.  
  14817.          struct SREGS segregs;
  14818.  
  14819.          static char pgm[MAXNAME + 1] = { "timer" };
  14820.  
  14821.          static void usage(char *, char *);
  14822.          extern char interval(long, char *);
  14823.          extern void getpname(char *, char *);
  14824.          extern int getopt(int, char **, char *);
  14825.          extern int optind, opterr;
  14826.          extern char *optarg;
  14827.  
  14828.          if (_osmajor >= 3)
  14829.                  getpname(*argv, pgm);
  14830.  
  14831.          /* process optional arguments */
  14832.          fout = stdout;
  14833.          tn = 0;
  14834.          errflag = eflag = sflag = 0;
  14835.          while ((ch = getopt(argc, argv, "0123ef:s")) != EOF) {
  14836.                  switch (ch) {
  14837.                  case 'e':
  14838.                          /* report elapsed timing */
  14839.                          ++eflag;
  14840.                          break;
  14841.                  case 'f':
  14842.                          /* use specified log file or stream */
  14843.                          strcpy(dest, optarg);
  14844.                          if ((fout = fopen(dest, "a")) == NULL) {
  14845.                                  fprintf(stderr, "%s: Cannot open %s\n",
  14846.                                          pgm, dest);
  14847.                                  exit(1);
  14848.                          }
  14849.                          break;
  14850.                  case 's':
  14851.                          /* start (or restart) timing an interval */
  14852.                          ++sflag;
  14853.                          break;
  14854.                  case '0':
  14855.                  case '1':
  14856.                  case '2':
  14857.                  case '3':
  14858.                          /* use specified timer */
  14859.                          tn = ch - 0x30;
  14860.                          break;
  14861.                  case '?':
  14862.                          /* bad option flag */
  14863.                          ++errflag;
  14864.                          break;
  14865.                  }
  14866.          }
  14867.          argc -= optind;
  14868.          argv += optind;
  14869.  
  14870.          /* check for errors */
  14871.          if (errflag > 0 || argc > 0)
  14872.                  usage(pgm, "Bad command line option(s)");
  14873.  
  14874.          segread(&segregs);
  14875.  
  14876.          /* report current date and time */
  14877.          now = time(NULL);
  14878.          fprintf(fout, "%s", ctime(&now));
  14879.  
  14880.          /* control and report timer data */
  14881.          if (eflag) {
  14882.                  /* report elapsed time for specified timer */
  14883.                  movedata(ICA_SEG, 0, segregs.ds, tdata, NBYTES);
  14884.                  then = tdata[tn];
  14885.                  if ((then & FLAGBIT) != FLAGBIT || (then & IDBIT) !=
  14886.                       IDBIT) {
  14887.                          fprintf(stderr, "Timer database corrupted or not
  14888.                                  set\n");
  14889.                          exit(1);
  14890.                  }
  14891.                  interval(now - (then & TIMEMASK), timestr);
  14892.                  fprintf(stdout, "Elapsed time = %s\n", timestr);
  14893.          }
  14894.          if (sflag) {
  14895.                  /* start (or restart) specified timer */
  14896.                  movedata(ICA_SEG, 0, segregs.ds, tdata, NBYTES);
  14897.                  tdata[tn] = (now & TIMEMASK) | FLAGBIT | IDBIT;
  14898.                  movedata(segregs.ds, tdata, ICA_SEG, 0, NBYTES);
  14899.          }
  14900.          fputc('\n', fout);
  14901.  
  14902.          exit(0);
  14903.  }
  14904.  
  14905.  /*
  14906.   *      usage -- display a usage message and exit
  14907.   *      with an error indication
  14908.   */
  14909.  
  14910.  static void
  14911.  usage(pname, mesg)
  14912.  char *pname;
  14913.  char *mesg;
  14914.  {
  14915.          fprintf(stderr, "%s\n", mesg);
  14916.          fprintf(stderr, "Usage: %s [-efs#]\n", pname);
  14917.          fprintf(stderr, "\t-e \tshow an elapsed time (must use start
  14918.                  first)\n");
  14919.          fprintf(stderr, "\t-f file\tappend output to specified file\n");
  14920.          fprintf(stderr, "\t-s \tstart (or restart) an interval timer\n");
  14921.          fprintf(stderr, "\t-# \tselect a timer (0 to 3; default is 0)\n");
  14922.          exit(2);
  14923.  }
  14924.  
  14925.  
  14926.  
  14927.  interval
  14928.  ───────────────────────────────────────────────────────────────────────────
  14929.  
  14930.  /*
  14931.   *      interval -- report the interval given in seconds as
  14932.   *      a human-readable null-terminated string
  14933.   */
  14934.  
  14935.  #include <stdio.h>
  14936.  
  14937.  char *
  14938.  interval(seconds, buf)
  14939.  long seconds;
  14940.  char *buf;
  14941.  {
  14942.          int hh, mm, ss;
  14943.          long remainder;
  14944.  
  14945.          /* calculate the values */
  14946.          hh = seconds / 3600;
  14947.          remainder = seconds % 3600;
  14948.          mm = remainder / 60;
  14949.          ss = remainder - (mm * 60);
  14950.          sprintf(buf, "%02d:%02d:%02d\0", hh, mm, ss);
  14951.  
  14952.          return (buf);
  14953.  }
  14954.  
  14955.  
  14956.  
  14957.  delay
  14958.  ───────────────────────────────────────────────────────────────────────────
  14959.  
  14960.  /*
  14961.   *      delay -- provide a delay of ** approximately ** the
  14962.   *      specified duration (resolution is about 0.055 second)
  14963.   */
  14964.  
  14965.  #include <local\timer.h>
  14966.  
  14967.  void
  14968.  delay(d)
  14969.  float d;/* duration in seconds and fractional seconds */
  14970.  {
  14971.          long ticks, then;
  14972.          extern long getticks();
  14973.  
  14974.          /* convert duration to number of PC clock ticks */
  14975.          ticks = d * TICKRATE;
  14976.  
  14977.          /* delay for the specified interval */
  14978.          then = getticks() + ticks;
  14979.          while (1)
  14980.                  if (getticks() >= then)
  14981.                          break;
  14982.  }
  14983.  
  14984.  
  14985.  
  14986.  getticks
  14987.  ───────────────────────────────────────────────────────────────────────────
  14988.  
  14989.  /*
  14990.   *      getticks -- get the current bios clock ticks value
  14991.   */
  14992.  
  14993.  #include <dos.h>
  14994.  
  14995.  #define BIOS_DATA_SEG   0x40
  14996.  #define TIMER_DATA      0x6C
  14997.  #define TICKS_PER_DAY   0x01800B0L
  14998.  
  14999.  long getticks()
  15000.  {
  15001.          static long total = 0;  /* accumulated count of timer ticks */
  15002.          long count;             /* current BIOS TOD count */
  15003.          long far *lp            /* far pointer */
  15004.          /* set up the far pointera to the BIOS TOD counter */
  15005.          FP_SEG(lp) = BIOS_DATA_SEG;
  15006.          FP_OFF(lp) = TIMER_DATA;
  15007.          while (1) {             /* read the TOD count */
  15008.                  count = *lp
  15009.                  if (*lp == count)
  15010.                          break;  /* two matching TOD readings */
  15011.          }
  15012.          /* correct for clock roll-over, if necessary */
  15013.          total = (count < total) ? count + TICKS_PER_DAY : count;
  15014.          return (total);
  15015.  }
  15016.  
  15017.  
  15018.  
  15019.  sweep
  15020.  ───────────────────────────────────────────────────────────────────────────
  15021.  
  15022.  /*
  15023.   *      sweep -- produce a sound that sweeps from
  15024.   *      a low to a high frequency repeatedly until a
  15025.   *      key is pressed
  15026.   */
  15027.  
  15028.  #include <conio.h>
  15029.  #include <local\sound.h>
  15030.  
  15031.  main()
  15032.  {
  15033.          unsigned int f;
  15034.          int d, n;
  15035.          extern void setfreq(unsigned int);
  15036.  
  15037.          SPKR_ON;
  15038.          while (1) {
  15039.                  /* give the user a way out */
  15040.                  if (kbhit())
  15041.                          break;
  15042.                  n = 10;
  15043.                  for (f = 100; f <= 5000; f += n) {
  15044.                          setfreq(f);
  15045.                          d = 1000;
  15046.                          /* fake a short delay (machine dependent) */
  15047.                          while (d-- > 0)
  15048.                                  ;
  15049.                          n += 10;
  15050.                  }
  15051.          }
  15052.          SPKR_OFF;
  15053.          exit(0);
  15054.  }
  15055.  
  15056.  
  15057.  
  15058.  sound
  15059.  ───────────────────────────────────────────────────────────────────────────
  15060.  
  15061.  /*
  15062.   *      sound -- produce a constant tone for a specified duration
  15063.   */
  15064.  
  15065.  #include <conio.h>
  15066.  #include <local\sound.h>
  15067.  
  15068.  void
  15069.  sound(f, dur)
  15070.  unsigned int f; /* frequency of pitch in hertz */
  15071.  float dur;      /* in seconds and tenths of seconds */
  15072.  {
  15073.          extern void setfreq(unsigned int);
  15074.          extern void delay(float);
  15075.  
  15076.          /* set the frequency in hertz */
  15077.          setfreq(f);
  15078.  
  15079.          /* turn the speaker on for specified duration */
  15080.          SPKR_ON;
  15081.          delay(dur);
  15082.          SPKR_OFF;
  15083.  }
  15084.  
  15085.  
  15086.  
  15087.  sounds
  15088.  ───────────────────────────────────────────────────────────────────────────
  15089.  
  15090.  /*
  15091.   *      sounds -- make various sounds on demand
  15092.   */
  15093.  
  15094.  #include <stdio.h>
  15095.  #include <conio.h>
  15096.  #include <math.h>
  15097.  
  15098.  #define ESC     27
  15099.  
  15100.  extern void sound(unsigned int, float);
  15101.  
  15102.  main()
  15103.  {
  15104.          int ch;
  15105.  
  15106.          fprintf(stderr, "1=warble 2=error 3=confirm 4=warn\n");
  15107.          fprintf(stderr, "Esc=quit\n");
  15108.          while ((ch = getch()) != ESC)
  15109.                  switch (ch) {
  15110.                  case '1':
  15111.                          warble();
  15112.                          break;
  15113.                  case '2':
  15114.                          error();
  15115.                          break;
  15116.                  case '3':
  15117.                          confirm();
  15118.                          break;
  15119.                  case '4':
  15120.                          warn();
  15121.                          break;
  15122.                  }
  15123.          exit(0);
  15124.  }
  15125.  
  15126.  #define CYCLES  3
  15127.  #define LOTONE  600
  15128.  #define HITONE  1200
  15129.  #define PERIOD  0.1
  15130.  
  15131.  warble()
  15132.  {
  15133.          int i;
  15134.  
  15135.          for (i = 0; i < 2 * CYCLES; ++i)
  15136.                  if (i % 2)
  15137.                          sound(LOTONE, PERIOD);
  15138.                  else
  15139.                          sound(HITONE, PERIOD);
  15140.  }
  15141.  
  15142.  error()
  15143.  {
  15144.          float d = 0.1;
  15145.  
  15146.          sound(440, d);
  15147.          sound(220, d);
  15148.  }
  15149.  
  15150.  confirm()
  15151.  {
  15152.          float d = 0.1;
  15153.  
  15154.          sound(440, d);
  15155.          sound(880, d);
  15156.  }
  15157.  
  15158.  warn()
  15159.  {
  15160.          float d = 0.2;
  15161.  
  15162.          sound(100, d);
  15163.  }
  15164.  
  15165.  
  15166.  
  15167.  getreply
  15168.  ───────────────────────────────────────────────────────────────────────────
  15169.  
  15170.  /*
  15171.   *      getreply -- display a message and wait for a reply
  15172.   */
  15173.  
  15174.  #include <stdio.h>
  15175.  #include <stdlib.h>
  15176.  #include <memory.h>
  15177.  #include <ctype.h>
  15178.  #include <local\std.h>
  15179.  #include <local\keydefs.h>
  15180.  #include "linebuf.h"
  15181.  
  15182.  char *
  15183.  getreply(row, col, width, mesg, lp, size, attr, pg)
  15184.  short row, col, width;  /* window location and width */
  15185.  char *mesg;             /* message text */
  15186.  LINEBUF *lp;            /* line pointer */
  15187.  short size;             /* size of line buffer */
  15188.  short attr;             /* video attribute for response field */
  15189.  short pg;               /* active display page */
  15190.  {
  15191.          int n, k, len;
  15192.          short mfw;      /* message field width */
  15193.          short rfw;      /* response field width */
  15194.          short ccol;     /* visible cursor column */
  15195.          int msgflag;    /* nonzero after a message is displayed */
  15196.          char *cp;       /* character pointer */
  15197.          char *wp;       /* pointer to window start */
  15198.          char *tmp;      /* temporary char pointer */
  15199.  
  15200.          extern int writemsg(short, short, short, char *, char *, short);
  15201.  
  15202.          /* display the prompt string and calculate response field width */
  15203.          putcur(row, col, pg);
  15204.          mfw = writemsg(row, col, width, mesg, NULL, pg);
  15205.          rfw = width - mfw;
  15206.          writea(attr, rfw, pg);
  15207.  
  15208.          /* collect the user's response */
  15209.          memset(lp->l_buf, '\0', size);
  15210.          wp = cp = lp->l_buf;
  15211.          putcur(row, col + mfw, pg);
  15212.          msgflag = 0;
  15213.          while ((k = getkey()) != K_RETURN) {
  15214.                  if (msgflag) {
  15215.                          /* clear old messages */
  15216.                          errmsg("");
  15217.                          putcur(row, ccol, pg);
  15218.                          msgflag = 0;
  15219.                  }
  15220.                  if (isascii(k) && isprint(k)) {
  15221.                          len = strlen(cp);
  15222.                          if (cp + len - lp->l_buf < size - 1) {
  15223.                                  memcpy(cp + 1, cp, len);
  15224.                                  *cp = k;
  15225.                                  ++cp;
  15226.                          }
  15227.                          else {
  15228.                                  errmsg("input buffer full");
  15229.                                  ++msgflag;
  15230.                          }
  15231.                  }
  15232.                  else
  15233.                          switch (k) {
  15234.                          case K_LEFT:
  15235.                                  /* move left one character */
  15236.                                  if (cp > lp->l_buf)
  15237.                                          --cp;
  15238.                                  break;
  15239.                          case K_RIGHT:
  15240.                                  /* move right one character */
  15241.                                  if (*cp != '\0')
  15242.                                          ++cp;
  15243.                                  break;
  15244.                          case K_UP:
  15245.                                  /* pop a line off the stack */
  15246.                                  if (lp->l_prev != NULL) {
  15247.                                          lp = lp->l_prev;
  15248.                                          wp = cp = lp->l_buf;
  15249.                                  }
  15250.                                  break;
  15251.                          case K_DOWN:
  15252.                                  /* push a line onto the stack */
  15253.                                  if (lp->l_next != NULL) {
  15254.                                          lp = lp->l_next;
  15255.                                          wp = cp = lp->l_buf;
  15256.                                  }
  15257.                                  break;
  15258.                          case K_HOME:
  15259.                                  /* beginning of buffer */
  15260.                                  cp = lp->l_buf;
  15261.                                  break;
  15262.                          case K_END:
  15263.                                  /* end of buffer */
  15264.                                  while (*cp != '\0')
  15265.                                          ++cp;
  15266.                                  break;
  15267.                          case K_CTRLH:
  15268.                                  if (cp > lp->l_buf) {
  15269.                                          tmp = cp - 1;
  15270.                                          memcpy(tmp, cp, strlen(tmp));
  15271.                                          --cp;
  15272.                                  }
  15273.                                  break;
  15274.                          case K_DEL:
  15275.                                  /* delete character at cursor */
  15276.                                  memcpy(cp, cp + 1, strlen(cp));
  15277.                                  break;
  15278.                          case K_ESC:
  15279.                                  /* cancel current input */
  15280.                                  lp->l_buf[0] = '\0';
  15281.                                  putcur(row, col, pg);
  15282.                                  writec(' ', width, pg);
  15283.                                  return (NULL);
  15284.                          default:
  15285.                                  errmsg("unknown command");
  15286.                                  ++msgflag;
  15287.                                  break;
  15288.                          }
  15289.  
  15290.                  /* adjust the window pointer if necessary */
  15291.                  if (cp < wp)
  15292.                          wp = cp;
  15293.                  else if (cp >= wp + rfw)
  15294.                          wp = cp + 1 - rfw;
  15295.  
  15296.                  /* display the reply window */
  15297.                  ccol = col + mfw;
  15298.                  writemsg(row, ccol, rfw, wp, NULL, pg);
  15299.  
  15300.                  /* reposition the cursor */
  15301.                  ccol = col + mfw + (cp - wp);
  15302.                  putcur(row, ccol, pg);
  15303.          }
  15304.          putcur(row, col, pg);
  15305.          writec(' ', width, pg); /* blank message area */
  15306.          return (lp->l_buf);
  15307.  }
  15308.  
  15309.  
  15310.  
  15311.  tstreply
  15312.  ───────────────────────────────────────────────────────────────────────────
  15313.  
  15314.  /*
  15315.   *      tstreply -- test the getreply function
  15316.   */
  15317.  
  15318.  #include <stdio.h>
  15319.  #include <string.h>
  15320.  #include <local\std.h>
  15321.  #include <local\video.h>
  15322.  #include "linebuf.h"
  15323.  
  15324.  #define INPUT_ROW       0
  15325.  #define INPUT_COL       40
  15326.  #define WIDTH           40
  15327.  
  15328.  int Apage = 0;
  15329.  BOOLEAN Silent = FALSE;
  15330.  
  15331.  main(argc, argv)
  15332.  int argc;
  15333.  char *argv[];
  15334.  {
  15335.          unsigned int r, c, ch, attr, revattr;
  15336.          char reply[MAXPATH + 1];
  15337.          LINEBUF buf;
  15338.  
  15339.          extern char *getreply(short, short, short,
  15340.                  char *, LINEBUF *, short, short, short);
  15341.  
  15342.          /* process command line */
  15343.          if (argc == 2 && strcmp(argv[1], "-s") == 0)
  15344.                  Silent = TRUE;
  15345.          else if (argc > 2) {
  15346.                  fprintf(stderr, "Usage: tstreply [-s]\n");
  15347.                  exit(1);
  15348.          }
  15349.  
  15350.          /* initial setup */
  15351.          getstate();
  15352.          readca(&ch, &attr, Apage);
  15353.          revattr = ((attr << 4) | (attr >> 4)) & 0x77;
  15354.          clrscrn(attr);
  15355.          putcur(0, 0, Apage);
  15356.          writestr("TSTREPLY", Apage);
  15357.          putcur(1, 0, Apage);
  15358.          writec(HLINE, Maxcol[Vmode} - 1, Apage);
  15359.          buf.l_buf = reply;
  15360.          buf.l_next = buf.l_prev = (LINEBUF *)NULL;
  15361.  
  15362.          /* demo getreply() */
  15363.  
  15364.          if (getreply(INPUT_ROW, INPUT_COL, WIDTH, "File: ", &buf,
  15365.                  MAXPATH, revattr, 0) == NULL) {
  15366.                  putcur(INPUT_ROW, INPUT_COL, Apage);
  15367.                  writeca(' ', attr, WIDTH, Apage);
  15368.                  putcur(2, 0, Apage);
  15369.                  fprintf(stderr, "input aborted\n");
  15370.                  exit(1);
  15371.          }
  15372.          putcur(INPUT_ROW, INPUT_COL, Apage);
  15373.          writeca(' ', attr, WIDTH, Apage);
  15374.          putcur(2, 0, Apage);
  15375.          fprintf(stderr, "reply = %s\n", reply);
  15376.          exit(0);
  15377.  }
  15378.  
  15379.  #define MSG_ROW 24
  15380.  #define MSG_COL 0
  15381.  
  15382.  int errmsg(mesg)
  15383.  char *mesg;
  15384.  {
  15385.          int n;
  15386.          extern void sound(unsigned int, float);
  15387.  
  15388.          putcur(MSG_ROW, MSG_COL, Apage);
  15389.          if ((n = strlen(mesg)) > 0) {
  15390.                  writestr(mesg, Apage);
  15391.                  if (Silent == FALSE)
  15392.                          sound(100, 0.2);
  15393.          }
  15394.          else
  15395.                  writec(' ', Maxcol[Vmode] - 1 - MSG_COL, Apage);
  15396.          return (n);
  15397.  }
  15398.  
  15399.  
  15400.  
  15401.  fconfig
  15402.  ───────────────────────────────────────────────────────────────────────────
  15403.  
  15404.  /*
  15405.   *      fconfig -- return a FILE pointer to a local or
  15406.   *      global configuration file, or NULL if none found
  15407.   */
  15408.  
  15409.  #include <stdio.h>
  15410.  #include <stdlib.h>
  15411.  #include <string.h>
  15412.  #include <ctype.h>
  15413.  #include <local\std.h>
  15414.  
  15415.  FILE *
  15416.  fconfig(varname, fname)
  15417.  char *varname;
  15418.  char *fname;
  15419.  {
  15420.          FILE *fp;
  15421.          char pname[MAXPATH + 1];
  15422.          char *p;
  15423.  
  15424.          /* look for a local configuration file */
  15425.          if ((fp = fopen(fname, "r")) != NULL)
  15426.                  return (fp);
  15427.  
  15428.          /* look for a directory variable */
  15429.          if ((p = getenv(strupr(varname))) != NULL) {
  15430.                  strcpy(pname, p);
  15431.                  strcat(pname, "\\");
  15432.                  strcat(pname, fname);
  15433.                  if ((fp = fopen(pname, "r")) != NULL)
  15434.                          return (fp);
  15435.          }
  15436.  
  15437.          /* didn't find anything to read */
  15438.          return (NULL);
  15439.  }
  15440.  
  15441.  
  15442.  
  15443.  printer.h
  15444.  ───────────────────────────────────────────────────────────────────────────
  15445.  
  15446.  /*
  15447.   *      printer.h -- header for printer-control functions
  15448.   */
  15449.  
  15450.  
  15451.  /* ASCII codes used for Epson MX/FX-series printer-control */
  15452.  #define DC2     18      /* cancel condensed type mode */
  15453.  #define DC4     20      /* cancel expanded type mode */
  15454.  #define ESC     27      /* signal start of printer-control sequences */
  15455.  #define FF      12      /* top of page next page */
  15456.  #define SO      14      /* start expanded type mode */
  15457.  #define SI      15      /* start condensed type mode */
  15458.  
  15459.  /* font types */
  15460.  #define NORMAL          0x00
  15461.  #define CONDENSED       0x01
  15462.  #define DOUBLE          0x02
  15463.  #define EMPHASIZED      0x04
  15464.  #define EXPANDED        0x08
  15465.  #define ITALICS         0x10
  15466.  #define UNDERLINE       0x20
  15467.  
  15468.  /* miscellaneous constants */
  15469.  #define MAXPSTR 32      /* maximum printer-control string length */
  15470.  
  15471.  /* primary printer-control data structure */
  15472.  typedef struct printer_st {
  15473.          /* hardware initialize/reset */
  15474.          char p_init[MAXPSTR];
  15475.  
  15476.          /* set option strings and codes */
  15477.          char p_bold[MAXPSTR];   /* bold (emphasized) on */
  15478.          char p_cmp[MAXPSTR];    /* compressed on */
  15479.          char p_ds[MAXPSTR];     /* double strike on */
  15480.          char p_exp[MAXPSTR];    /* expanded (double width) on */
  15481.          char p_ul[MAXPSTR];     /* underline on */
  15482.          char p_ital[MAXPSTR];   /* italic on */
  15483.  
  15484.          /* reset option strings and codes */
  15485.          char p_norm[MAXPSTR];   /* restore normal font */
  15486.          char p_xbold[MAXPSTR];  /* bold (emphasized) off */
  15487.          char p_xcmp[MAXPSTR];   /* compressed off */
  15488.          char p_xds[MAXPSTR];    /* double strike off */
  15489.          char p_xexp[MAXPSTR];   /* expanded (double width) off */
  15490.          char p_xul[MAXPSTR];    /* underline off */
  15491.          char p_xital[MAXPSTR];  /* italic off */
  15492.  } PRINTER;
  15493.  
  15494.  
  15495.  
  15496.  printer
  15497.  ───────────────────────────────────────────────────────────────────────────
  15498.  
  15499.  /*
  15500.   *      printer -- interface functions for printer
  15501.   */
  15502.  
  15503.  #include <stdio.h>
  15504.  #include <stdlib.h>
  15505.  #include <string.h>
  15506.  #include <local\std.h>
  15507.  #include <local\printer.h>
  15508.  
  15509.  PRINTER prt;    /* printer data */
  15510.  
  15511.  /*
  15512.   *      setprnt -- install printer codes from configuration
  15513.   *      file for printer (defaults to Epson MX/FX series)
  15514.   */
  15515.  
  15516.  #define NSELEM  13
  15517.  
  15518.  int
  15519.  setprnt()
  15520.  {
  15521.          int n;
  15522.          char *s, line[MAXLINE];
  15523.          FILE *fp, *fconfig(char *, char *);
  15524.  
  15525.          /* use local or global config file, if any */
  15526.          if ((fp = fconfig("CONFIG", "printer.cnf")) != NULL) {
  15527.                  n = 0;
  15528.                  while (fgets(line, MAXLINE, fp) != NULL) {
  15529.                          if ((s = strtok(line, " \t\n")) == NULL)
  15530.                                  return (-1);
  15531.                          switch (n) {
  15532.                          case 0:
  15533.                                  strcpy(prt.p_init, s);
  15534.                                  break;
  15535.                          case 1:
  15536.                                  strcpy(prt.p_bold, s);
  15537.                                  break;
  15538.                          case 2:
  15539.                                  strcpy(prt.p_ds, s);
  15540.                                  break;
  15541.                          case 3:
  15542.                                  strcpy(prt.p_ital, s);
  15543.                                  break;
  15544.                          case 4:
  15545.                                  strcpy(prt.p_cmp, s);
  15546.                                  break;
  15547.                          case 5:
  15548.                                  strcpy(prt.p_exp, s);
  15549.                                  break;
  15550.                          case 6:
  15551.                                  strcpy(prt.p_ul, s);
  15552.                                  break;
  15553.                          case 7:
  15554.                                  strcpy(prt.p_xbold, s);
  15555.                                  break;
  15556.                          case 8:
  15557.                                  strcpy(prt.p_xds, s);
  15558.                                  break;
  15559.                          case 9:
  15560.                                  strcpy(prt.p_xital, s);
  15561.                                  break;
  15562.                          case 10:
  15563.                                  strcpy(prt.p_xcmp, s);
  15564.                                  break;
  15565.                          case 11:
  15566.                                  strcpy(prt.p_xexp, s);
  15567.                                  break;
  15568.                          case 12:
  15569.                                  strcpy(prt.p_xul, s);
  15570.                                  break;
  15571.                          default:
  15572.                                  /* too many lines */
  15573.                                  return (-1);
  15574.                          }
  15575.                          ++n;
  15576.                  }
  15577.                  if (n != NSELEM)
  15578.                          /* probably not enough lines */
  15579.                          return (-1);
  15580.          }
  15581.  
  15582.          /* or use Epson defaults */
  15583.          strcpy(prt.p_init, "\033@");    /* hardware reset */
  15584.          strcpy(prt.p_bold, "\033E");    /* emphasized mode */
  15585.          strcpy(prt.p_ds, "\033G");      /* double-strike mode */
  15586.          strcpy(prt.p_ital, "\0334");    /* italic mode */
  15587.          strcpy(prt.p_cmp, "\017");      /* condensed mode */
  15588.          strcpy(prt.p_exp, "\016");      /* expanded mode */
  15589.          strcpy(prt.p_ul, "\033-1");     /* underline mode */
  15590.          strcpy(prt.p_xbold, "\033F");   /* cancel emphasized mode */
  15591.          strcpy(prt.p_xds, "\033H");     /* cancel double-strike mode */
  15592.          strcpy(prt.p_xital, "\0335");   /* cancel italic mode */
  15593.          strcpy(prt.p_xcmp, "\022");     /* cancel condensed mode */
  15594.          strcpy(prt.p_xexp, "\024");     /* cancel expanded mode */
  15595.          strcpy(prt.p_xul, "\033-0");    /* cancel underline mode */
  15596.  
  15597.          return (0);
  15598.  }
  15599.  
  15600.  /*
  15601.   *      clrprnt -- clear printer options to default values
  15602.   *      (clears individual options to avoid the "paper creep"
  15603.   *      that occurs with repeated printer resets and to avoid
  15604.   *      changing the printer's notion of top-of-form position)
  15605.   */
  15606.  
  15607.  int
  15608.  clrprnt(fout)
  15609.  FILE *fout;
  15610.  {
  15611.          fputs(prt.p_xbold, fout);       /* cancel emphasized mode */
  15612.          fputs(prt.p_xds, fout);         /* cancel double-strike mode */
  15613.          fputs(prt.p_xital, fout);       /* cancel italic mode */
  15614.          fputs(prt.p_xcmp, fout);        /* cancel condensed mode */
  15615.          fputs(prt.p_xexp, fout);        /* cancel expanded mode */
  15616.          fputs(prt.p_xul, fout);         /* cancel underline mode */
  15617.  } /* end clrprnt() */
  15618.  
  15619.  /*
  15620.   *      setfont -- set the printing font to the type specified
  15621.   *      by the argument (may be a compound font specification)
  15622.   */
  15623.  
  15624.  int
  15625.  setfont(ftype, fout)
  15626.  int ftype;      /* font type specifier */
  15627.  FILE *fout;     /* output stream */
  15628.  {
  15629.          clrprnt(fout);
  15630.          if ((ftype & CONDENSED) == CONDENSED)
  15631.                  if ((ftype & DOUBLE) == DOUBLE ||
  15632.                          (ftype & EMPHASIZED) == EMPHASIZED)
  15633.                          return FAILURE;
  15634.                  else if (*prt.p_cmp)
  15635.                          fputs(prt.p_cmp, fout);
  15636.          if (*prt.p_ds && (ftype & DOUBLE) == DOUBLE)
  15637.                  fputs(prt.p_ds, fout);
  15638.          if (*prt.p_bold && (ftype & EMPHASIZED) == EMPHASIZED)
  15639.                  fputs(prt.p_bold, fout);
  15640.          if (*prt.p_exp && (ftype & EXPANDED) == EXPANDED)
  15641.                  fputs(prt.p_exp, fout);
  15642.          if (*prt.p_ital && (ftype & ITALICS) == ITALICS)
  15643.                  fputs(prt.p_ital, fout);
  15644.          if (*prt.p_ul && (ftype & UNDERLINE) == UNDERLINE)
  15645.                  fputs(prt.p_ul, fout);
  15646.  
  15647.          return SUCCESS;
  15648.  } /* end setfont() */
  15649.  
  15650.  
  15651.  
  15652.  mx
  15653.  ───────────────────────────────────────────────────────────────────────────
  15654.  
  15655.  /*
  15656.   *      mx -- control Epson MX-series printer
  15657.   */
  15658.  
  15659.  #include <stdio.h>
  15660.  #include <stdlib.h>
  15661.  #include <local\std.h>
  15662.  #include <local\printer.h>
  15663.  
  15664.  extern PRINTER prt;     /* printer data */
  15665.  
  15666.  main(argc, argv)
  15667.  int argc;
  15668.  char **argv;
  15669.  {
  15670.          int ch, font;
  15671.          BOOLEAN errflag;        /* option error */
  15672.          BOOLEAN clrflag;        /* clear special fonts */
  15673.          BOOLEAN rflag;          /* hardware reset */
  15674.          BOOLEAN tflag;          /* top-of-form */
  15675.          FILE *fout;
  15676.          static char pgm[MAXNAME + 1] = { "mx" };
  15677.          extern void getpname(char *, char *);
  15678.          extern int getopt(int, char **, char *);
  15679.          extern char *optarg;
  15680.          extern int optind, opterr;
  15681.          extern int setprnt();
  15682.          extern int clrprnt(FILE *);
  15683.          extern int setfont(int, FILE *);
  15684.  
  15685.          if (_osmajor >= 3)
  15686.                  getpname(*argv, pgm);
  15687.  
  15688.          if (setprnt() == -1) {
  15689.                  fprintf(stderr, "%s: Bad printer configuration\n", pgm);
  15690.                  exit(1);
  15691.          }
  15692.  
  15693.          /* interpret command line */
  15694.          errflag = clrflag = rflag = tflag = FALSE;
  15695.          font = 0;
  15696.          fout = stdprn;
  15697.          while ((ch = getopt(argc, argv, "bcdefino:prtu")) != EOF) {
  15698.                  switch (ch) {
  15699.                  case 'b':
  15700.                          /* set bold */
  15701.                          font |= EMPHASIZED;
  15702.                          break;
  15703.                  case 'c':
  15704.                          /* set compressed */
  15705.                          font |= CONDENSED;
  15706.                          break;
  15707.                  case 'd':
  15708.                          /* set double strike */
  15709.                          font |= DOUBLE;
  15710.                          break;
  15711.                  case 'e':
  15712.                          /* set double strike */
  15713.                          font |= EXPANDED;
  15714.                          break;
  15715.                  case 'i':
  15716.                          /* set italic */
  15717.                          font |= ITALICS;
  15718.                          break;
  15719.                  case 'n':
  15720.                          /* set normal (clear all special fonts) */
  15721.                          clrflag = TRUE;
  15722.                          break;
  15723.                  case 'o':
  15724.                          /* use specified output stream */
  15725.                          if ((fout = fopen(optarg, "w")) == NULL)
  15726.                                  fatal(pgm, "cannot open output stream");
  15727.                          break;
  15728.                  case 'p':
  15729.                          /* preview control strings on stdout */
  15730.                          fout = stdout;
  15731.                          break;
  15732.                  case 'r':
  15733.                          /* hardware reset */
  15734.                          rflag = TRUE;
  15735.                          break;
  15736.                  case 't':
  15737.                          /* top of form */
  15738.                          tflag = TRUE;
  15739.                          break;
  15740.                  case 'u':
  15741.                          /* set underline */
  15742.                          font |= UNDERLINE;
  15743.                          break;
  15744.                  case '?':
  15745.                          /* unknown option */
  15746.                          errflag = TRUE;
  15747.                          break;
  15748.                  }
  15749.          }
  15750.  
  15751.          /* report errors, if any */
  15752.          if (errflag == TRUE || argc == 1) {
  15753.                  fprintf(stderr, "Usage: %s -option\n", pgm);
  15754.                  fprintf(stderr,
  15755.                          "b=bold, c=compressed, d=double strike,
  15756.                           e=expanded\n");
  15757.                  fprintf(stderr,
  15758.                          "i=italic, n=normal, o file=output to file\n");
  15759.                  fprintf(stderr,
  15760.                          "p=preview, r=reset, t=top-of-form,
  15761.                           u=underline\n");
  15762.                  exit(2);
  15763.          }
  15764.  
  15765.          /* do hardware reset and formfeed first */
  15766.          if (rflag == TRUE)
  15767.                  fputs(prt.p_init, fout);
  15768.          else if (tflag == TRUE)
  15769.                  fputc('\f', fout);
  15770.  
  15771.          /* clear or set the aggregate font */
  15772.          if (clrflag == TRUE)
  15773.                  clrprnt(fout);
  15774.          else if (setfont(font, fout) == FAILURE) {
  15775.                  fprintf(stderr, "%s: Bad font spec\n", pgm);
  15776.                  exit(3);
  15777.          }
  15778.  
  15779.          exit(0);
  15780.  }
  15781.  
  15782.  
  15783.  
  15784.  prtstr
  15785.  ───────────────────────────────────────────────────────────────────────────
  15786.  
  15787.  /*
  15788.   *      prtstr -- send text string(s) to standard printer
  15789.   */
  15790.  
  15791.  #include <stdio.h>
  15792.  #include <stdlib.h>
  15793.  #include <local\std.h>
  15794.  
  15795.  main(argc, argv)
  15796.  int argc;
  15797.  char **argv;
  15798.  {
  15799.          int ch;
  15800.          BOOLEAN errflag, lineflag;
  15801.          static char pgm[MAXNAME + 1] = { "prtstr" };
  15802.          FILE *fout;
  15803.  
  15804.          extern char *getpname(char *, char *);
  15805.          extern int getopt(int, char **, char *);
  15806.          extern int optind, opterr;
  15807.          extern char *optarg;
  15808.  
  15809.          if (_osmajor >= 3)
  15810.                  getpname(*argv, pgm);
  15811.  
  15812.          errflag = FALSE;        /* process options, if any */
  15813.          lineflag = TRUE;
  15814.          fout = stdprn;
  15815.          while ((ch = getopt(argc, argv, "np")) != EOF)
  15816.                  switch (ch) {
  15817.                  case 'n':       /* don't emit the trailing newline */
  15818.                          lineflag = FALSE;
  15819.                          break;
  15820.                  case 'p':       /* preview on stdout */
  15821.                          fout = stdout;
  15822.                          break;
  15823.                  case '?':       /* bad option */
  15824.                          errflag = TRUE;
  15825.                          break;
  15826.                  }
  15827.          if (errflag == TRUE) {
  15828.                  fprintf(stderr, "Usage: %s [-np] [string...]\n", pgm);
  15829.                  exit(1);
  15830.          }
  15831.  
  15832.          /* print the string(s) */
  15833.          argc -= optind;
  15834.          argv += optind;
  15835.          while (argc-- > 1 ) {
  15836.                  fputs(*argv++, fout);
  15837.                  fputc(' ', fout);
  15838.          }
  15839.          fputs(*argv++, fout);
  15840.          if (lineflag == TRUE)
  15841.                  fputc(' ', fout);
  15842.          if (lineflag == TRUE)
  15843.                  fputc('\n', fout);
  15844.  
  15845.          exit(0);
  15846.  }
  15847.  
  15848.  
  15849.  
  15850.  sample.bat
  15851.  ───────────────────────────────────────────────────────────────────────────
  15852.  
  15853.  echo off
  15854.  mx -n
  15855.  prtstr This is normal text.
  15856.  mx -b
  15857.  prtstr This is bold (emphasized) text.
  15858.  mx -n
  15859.  prtstr We can mix fonts, too.
  15860.  mx -be
  15861.  prtstr This is bold and expanded together.
  15862.  mx -n
  15863.  prtstr -n We can mix fonts in a line also:
  15864.  mx -b
  15865.  prtstr -n " bold"
  15866.  mx -i
  15867.  prtstr -n " and "
  15868.  mx -e
  15869.  prtstr expanded.
  15870.  mx -u
  15871.  prtstr This is underlined text...
  15872.  mx -i
  15873.  prtstr ... this is italicized.
  15874.  mx -n
  15875.  prtstr And finally back to normal type.
  15876.  
  15877.  
  15878.  
  15879.  select
  15880.  ───────────────────────────────────────────────────────────────────────────
  15881.  
  15882.  /*
  15883.   *      select -- functions to create a selection table and
  15884.   *      to determine whether an item is an entry in the table
  15885.   */
  15886.  
  15887.  #include <stdio.h>
  15888.  #include <stdlib.h>
  15889.  #include <string.h>
  15890.  #include <local\std.h>
  15891.  
  15892.  #define NRANGES 10
  15893.  #define NDIGITS 5
  15894.  
  15895.  struct slist_st {
  15896.          long int s_min;
  15897.          long int s_max;
  15898.  } Slist[NRANGES + 1];
  15899.  
  15900.  long Highest = 0;
  15901.  
  15902.  /*
  15903.   *      mkslist -- create the selection lookup table
  15904.   */
  15905.  
  15906.  int
  15907.  mkslist(list)
  15908.  char *list;
  15909.  {
  15910.          int i;
  15911.          char *listp, *s;
  15912.          long tmp;
  15913.          static long save_range();
  15914.  
  15915.          if (*list == '\0') {          /* fill in table of selected items */
  15916.                  Slist[0].s_min = 0;   /* if no list, select all */
  15917.                  Slist[0].s_max = Highest = BIGGEST;
  15918.                  Slist[1].s_min = -1;
  15919.          }
  15920.          else {
  15921.                  listp = list;
  15922.                  for (i = 0; i < NRANGES; ++i) {
  15923.                          if ((s = strtok(listp, ", \t")) == NULL)
  15924.                                  break;
  15925.                          if ((tmp = save_range(i, s)) > Highest)
  15926.                                  Highest = tmp;
  15927.                          listp = NULL;
  15928.                  }
  15929.                  Slist[i].s_min = -1;
  15930.          }
  15931.          return (0);
  15932.  } /* end mkslist() */
  15933.  
  15934.  /*
  15935.   *      selected -- return non-zero value if the number
  15936.   *      argument is a member of the selection list
  15937.   */
  15938.  
  15939.  int
  15940.  selected(n)
  15941.  long n;
  15942.  {
  15943.          int i;
  15944.  
  15945.          /* look for converted number in selection list */
  15946.          for (i = 0; Slist[i].s_min != -1; ++i)
  15947.                  if (n >= Slist[i].s_min && n <= Slist[i].s_max)
  15948.                          return (1);
  15949.          return (0);
  15950.  } /* end selected() */
  15951.  
  15952.  /*
  15953.   *      save_range -- convert a string number spec to a
  15954.   *      numeric range in the selection table and return
  15955.   *      the highest number in the range
  15956.   */
  15957.  
  15958.  static long
  15959.  save_range(n, s)
  15960.  int n;
  15961.  char *s;
  15962.  {
  15963.          int radix = 10;
  15964.          char *cp, num[NDIGITS + 1];
  15965.  
  15966.          /* get the first (and possibly only) number */
  15967.          cp = num;
  15968.          while (*s != '\0' && *s != '-')
  15969.                  *cp++ = *s++;
  15970.          *cp = '\0';
  15971.          Slist[n].s_min = atol(num);
  15972.          if (*s == '\0')
  15973.                  /* pretty narrow range, huh? */
  15974.                  return (Slist[n].s_max = Slist[n].s_min);
  15975.  
  15976.          /* get the second number */
  15977.          if (*++s == '\0')
  15978.                  /* unspecified top end of range */
  15979.                  Slist[n].s_max = BIGGEST;
  15980.          else {
  15981.                  cp = num;
  15982.                  while (*s != '\0')
  15983.                          *cp++ = *s++;
  15984.                  *cp = '\0';
  15985.                  Slist[n].s_max = atol(num);
  15986.          }
  15987.          return (Slist[n].s_max);
  15988.  } /* end save_range() */
  15989.  
  15990.  
  15991.  
  15992.  tstsel
  15993.  ───────────────────────────────────────────────────────────────────────────
  15994.  
  15995.  /*
  15996.   *      tstsel -- test driver for the "select" functions
  15997.   */
  15998.  
  15999.  #include <stdio.h>
  16000.  #include <local\std.h>
  16001.  
  16002.  #define MAXTEST 20
  16003.  #define NRANGES 10
  16004.  #define NDIGITS 5
  16005.  
  16006.  extern struct slist_st {
  16007.          long int s_min;
  16008.          long int s_max;
  16009.  } Slist[NRANGES + 1];
  16010.  
  16011.  main (argc, argv)
  16012.  int argc;
  16013.  char **argv;
  16014.  {
  16015.          int i;
  16016.          extern int mkslist(char *);
  16017.          extern int selected(unsigned int);
  16018.          static void showlist();
  16019.  
  16020.          if (argc != 2) {
  16021.                  fprintf(stderr, "Usage: tstsel list\n");
  16022.                  exit(1);
  16023.          }
  16024.          printf("argv[1] = %s\n", argv[1]);
  16025.          mkslist(argv[1]);
  16026.          showlist();
  16027.          for (i = 0; i < MAXTEST; ++i)
  16028.                  printf("%2d -> %s\n", i, selected(i) ? "YES" : "NO");
  16029.          exit(0);
  16030.  }
  16031.  
  16032.  /*
  16033.   *      showlist -- display the contents of the select list
  16034.   */
  16035.  
  16036.  static void
  16037.  showlist()
  16038.  {
  16039.          int i;
  16040.  
  16041.          /* scan the selection list and display values */
  16042.          for (i = 0; i <= NRANGES; ++i)
  16043.                  printf("%2d %5ld %5ld\n", i, Slist[i].s_min,
  16044.                          Slist[i].s_max);
  16045.  }
  16046.  
  16047.  
  16048.  
  16049.  Sample output from tstsel
  16050.  ───────────────────────────────────────────────────────────────────────────
  16051.  
  16052.  argv[1] = 1,2,3,5-10
  16053.   0     1     1
  16054.   1     2     2
  16055.   2     3     3
  16056.   3     5    10
  16057.   4    -1     0
  16058.   5     0     0
  16059.   6     0     0
  16060.   7     0     0
  16061.   8     0     0
  16062.   9     0     0
  16063.  10     0     0
  16064.   0 -> NO
  16065.   1 -> YES
  16066.   2 -> YES
  16067.   3 -> YES
  16068.   4 -> NO
  16069.   5 -> YES
  16070.   6 -> YES
  16071.   7 -> YES
  16072.   8 -> YES
  16073.   9 -> YES
  16074.  10 -> YES
  16075.  11 -> NO
  16076.  12 -> NO
  16077.  13 -> NO
  16078.  14 -> NO
  16079.  15 -> NO
  16080.  16 -> NO
  16081.  17 -> NO
  16082.  18 -> NO
  16083.  19 -> NO
  16084.  
  16085.  
  16086.  
  16087.  tabs
  16088.  ───────────────────────────────────────────────────────────────────────────
  16089.  
  16090.  /*
  16091.   *      tabs -- a group of cooperating functions that set
  16092.   *      and report the settings of "tabstops"
  16093.   */
  16094.  
  16095.  #include <local\std.h>
  16096.  
  16097.  static char Tabstops[MAXLINE];
  16098.  
  16099.  /*
  16100.   *      fixtabs -- set up fixed-interval tabstops
  16101.   */
  16102.  void
  16103.  fixtabs(interval)
  16104.  register int interval;
  16105.  {
  16106.          register int i;
  16107.  
  16108.          for (i = 0; i < MAXLINE; i++)
  16109.                  Tabstops[i] = (i % interval == 0) ? 1 : 0;
  16110.  } /* end fixtabs() */
  16111.  
  16112.  /*
  16113.   *      vartabs -- set up variable tabstops from an array
  16114.   *      integers terminated by a -1 entry
  16115.   */
  16116.  void
  16117.  vartabs(list)
  16118.  int *list;
  16119.  {
  16120.          register int i;
  16121.  
  16122.          /* initialize the tabstop array */
  16123.          for (i = 0; i < MAXLINE; ++i)
  16124.                  Tabstops[i] = 0;
  16125.  
  16126.          /* set user-specified tabstops */
  16127.          while (*list != -1)
  16128.                  Tabstops[*++list] = 1;
  16129.  } /* end vartabs() */
  16130.  
  16131.  /*
  16132.   *      tabstop -- return non-zero if col is a tabstop
  16133.   */
  16134.  int
  16135.  tabstop(col)
  16136.  register int col;
  16137.  {
  16138.          return (col >= MAXLINE ? 1 : Tabstops[col]);
  16139.  } /* end tabstop() */
  16140.  
  16141.  
  16142.  
  16143.  showtabs
  16144.  ───────────────────────────────────────────────────────────────────────────
  16145.  
  16146.  /*
  16147.   *      showtabs -- graphically display tabstop settings
  16148.   */
  16149.  
  16150.  #include <stdio.h>
  16151.  #include <stdlib.h>
  16152.  #include <local\std.h>
  16153.  
  16154.  #define MAXCOL    80
  16155.  #define TABWIDTH  8
  16156.  
  16157.  extern long Highest;
  16158.  
  16159.  main(argc, argv)
  16160.  int argc;
  16161.  char *argv[];
  16162.  {
  16163.          int ch, i;
  16164.          int interval, tablist[MAXLINE + 1], *p;
  16165.          char *tabstr;
  16166.          BOOLEAN errflag, fflag, vflag;
  16167.          static char pgm[MAXNAME + 1] = { "showtabs" };
  16168.  
  16169.          extern char *getpname(char *, char *);
  16170.          extern int getopt(int, char **, char *);
  16171.          extern char *optarg;
  16172.          extern int optind, opterr;
  16173.          extern int mkslist(char *);
  16174.          extern int selected(long);
  16175.          extern void fixtabs(int);
  16176.          extern void vartabs(int *);
  16177.          extern int tabstop(int);
  16178.  
  16179.          if (_osmajor >= 3)
  16180.                  getpname(*argv, pgm);
  16181.  
  16182.          /* process command-line options */
  16183.          errflag = fflag = vflag = FALSE;
  16184.          interval = 0;
  16185.          while ((ch = getopt(argc, argv, "f:v:")) != EOF) {
  16186.                  switch (ch) {
  16187.                  case 'f':
  16188.                          /* used fixed tabbing interval */
  16189.                          if (vflag == FALSE) {
  16190.                                  fflag = TRUE;
  16191.                                  interval = atoi(optarg);
  16192.                          }
  16193.                          break;
  16194.                  case 'v':
  16195.                          /* use list of tabs */
  16196.                          if (fflag == FALSE) {
  16197.                                  vflag = TRUE;
  16198.                                  strcpy(tabstr, optarg);
  16199.                          }
  16200.                          break;
  16201.                  case '?':
  16202.                          errflag = TRUE;
  16203.                          break;
  16204.                  }
  16205.          }
  16206.          if (errflag == TRUE) {
  16207.                  fprintf(stderr, "Usage: %s [-f interval |
  16208.                                   -v tablist]\n", pgm);
  16209.                  exit(2);
  16210.          }
  16211.  
  16212.          /* set the tabstops */
  16213.          if (vflag == TRUE) {
  16214.                  /* user-supplied variable tab list */
  16215.                  mkslist(tabstr);
  16216.                  p = tablist;
  16217.                  for (i = 0; i < MAXLINE && i < Highest; ++i)
  16218.                          *p++ = selected((long)i + 1) ? i : 0;
  16219.                  *p = -1;        /* terminate the list */
  16220.                  vartabs(tablist);
  16221.          }
  16222.          else if (fflag == TRUE)
  16223.                  /* user-supplied fixed tabbing interval */
  16224.                  fixtabs(interval);
  16225.          else
  16226.                  /* hardware default tabbing interval */
  16227.                  fixtabs(TABWIDTH);
  16228.  
  16229.          /* display current tabs settings */
  16230.          for (i = 0; i < MAXCOL; ++i)
  16231.                  if (tabstop(i))
  16232.                          fputc('T', stdout);
  16233.                  else if ((i + 1) % 10 == 0)
  16234.                          fputc('+', stdout);
  16235.                  else
  16236.                          fputc('-', stdout);
  16237.          fputc('\n', stdout);
  16238.  
  16239.          exit(0);
  16240.  }
  16241.  
  16242.  
  16243.  
  16244.  touch
  16245.  ───────────────────────────────────────────────────────────────────────────
  16246.  
  16247.  /*
  16248.   *      touch -- update modification time of file(s)
  16249.   */
  16250.  
  16251.  #include <stdio.h>
  16252.  #include <stdlib.h>
  16253.  #include <ctype.h>
  16254.  #include <sys\types.h>
  16255.  #include <sys\stat.h>
  16256.  #include <sys\utime.h>
  16257.  #include <io.h>
  16258.  #include <errno.h>
  16259.  #include <local\std.h>
  16260.  
  16261.  /* error return -- big enough not to be mistaken for a bad file count */
  16262.  #define ERR     0x7FFF
  16263.  
  16264.  main(argc, argv)
  16265.  int argc;
  16266.  char *argv[];
  16267.  {
  16268.          int ch;
  16269.          int i;
  16270.          int badcount;           /* # of files that can't be updated */
  16271.          struct stat statbuf;    /* buffer for stat results */
  16272.          BOOLEAN errflag,        /* error flag */
  16273.                  cflag,          /* creation flag */
  16274.                  vflag;          /* verbose flag */
  16275.          FILE *fp;
  16276.  
  16277.          static char pgm[MAXNAME + 1] = { "touch" };
  16278.          extern int getopt(int, char **, char *);
  16279.          extern int optind, opterr;
  16280.          extern char *optarg;
  16281.          extern void getpname(char *, char *);
  16282.          static void usage(char *);
  16283.  
  16284.          /* get program name from DOS (version 3.00 and later) */
  16285.          if (_osmajor >= 3)
  16286.                  getpname(argv[0], pgm);
  16287.  
  16288.          /* process optional arguments first */
  16289.          errflag = cflag = vflag = FALSE;
  16290.          badcount = 0;
  16291.          while ((ch = getopt(argc, argv, "cv")) != EOF)
  16292.                  switch (ch) {
  16293.                  case 'c':
  16294.                          /* don't create files */
  16295.                          cflag = TRUE;
  16296.                          break;
  16297.                  case 'v':
  16298.                          /* verbose -- report activity */
  16299.                          vflag = TRUE;
  16300.                          break;
  16301.                  case '?':
  16302.                          errflag = TRUE;
  16303.                          break;
  16304.                  }
  16305.          argc -= optind;
  16306.          argv += optind;
  16307.  
  16308.          /* check for errors including no file names */
  16309.          if (errflag == TRUE || argc <= 0) {
  16310.                  usage(pgm);
  16311.                  exit(ERR);
  16312.          }
  16313.  
  16314.          /* update modification times of files */
  16315.          for (; argc-- > 0; ++argv) {
  16316.                  if (stat(*argv, &statbuf) == -1) {
  16317.                          /* file doesn't exist */
  16318.                          if (cflag == TRUE) {
  16319.                                  /* don't create it */
  16320.                                  ++badcount;
  16321.                                  continue;
  16322.                          }
  16323.                          else if ((fp = fopen(*argv, "w")) == NULL) {
  16324.                                  fprintf(stderr, "%s: Cannot create %s\n",
  16325.                                          pgm, *argv);
  16326.                                  ++badcount;
  16327.                                  continue;
  16328.                          }
  16329.                          else {
  16330.                                  if (fclose(fp) == EOF) {
  16331.                                          perror("Error closing file");
  16332.                                          exit(ERR);
  16333.                                  }
  16334.                                  if (stat(*argv, &statbuf) == -1) {
  16335.                                          fprintf(stderr,
  16336.                                                  "%s: Cannot stat %s\n",
  16337.                                                  pgm, *argv);
  16338.                                          ++badcount;
  16339.                                          continue;
  16340.                                  }
  16341.                          }
  16342.                  }
  16343.                  if (utime(*argv, NULL) == -1) {
  16344.                          ++badcount;
  16345.                          perror("Error updating date/time stamp");
  16346.                          continue;
  16347.                  }
  16348.                  if (vflag == TRUE)
  16349.                          fprintf(stderr, "Touched file %s\n", *argv);
  16350.          }
  16351.  
  16352.          exit(badcount);
  16353.  } /* end main() */
  16354.  
  16355.  /*
  16356.   *      usage -- display an informative usage message
  16357.   */
  16358.  
  16359.  static void
  16360.  usage(pname)
  16361.  char *pname;
  16362.  {
  16363.          fprintf(stderr, "Usage: %s [-cv] file ...\n", pname);
  16364.          fprintf(stderr, "\t-c  Do not create any files\n");
  16365.          fprintf(stderr, "\t-v  Verbose mode -- report activities\n");
  16366.  } /* end usage() */
  16367.  
  16368.  /*
  16369.   *      dummy functions to show how to save a little space
  16370.   */
  16371.  
  16372.  _setenvp()
  16373.  {
  16374.  }
  16375.  
  16376.  #ifndef DEBUG
  16377.  _nullcheck()
  16378.  {
  16379.  }
  16380.  #endif
  16381.  
  16382.  
  16383.  
  16384.  tee
  16385.  ───────────────────────────────────────────────────────────────────────────
  16386.  
  16387.  /*
  16388.   *      tee -- a "pipe fitter" for DOS
  16389.   */
  16390.  
  16391.  #include <stdio.h>
  16392.  #include <stdlib.h>
  16393.  #include <string.h>
  16394.  #include <local\std.h>
  16395.  
  16396.  main(argc, argv)
  16397.  int argc;
  16398.  char *argv[];
  16399.  {
  16400.          register int ch, n;
  16401.          static char openmode[] = { "w" };
  16402.          static char pgm[MAXPATH + 1] = { "tee" };
  16403.          FILE *fp[_NFILE];       /* array of file pointers */
  16404.  
  16405.          extern int getopt(int, char **, char *);
  16406.          extern int optind, opterr;
  16407.          extern char *optarg;
  16408.          extern void getpname(char *, char *);
  16409.  
  16410.          /* check for an alias */
  16411.          if (_osmajor >= 3)
  16412.                  getpname(argv[0], pgm);
  16413.  
  16414.          /* process command-line options, if any */
  16415.          while ((ch = getopt(argc, argv, "a")) != EOF)
  16416.                  switch (ch) {
  16417.                  case 'a':
  16418.                          strcpy(openmode, "a");
  16419.                          break;
  16420.                  case '?':
  16421.                          break;
  16422.                  }
  16423.          n = argc -= optind;
  16424.          argv += optind;
  16425.  
  16426.          /* check for errors */
  16427.          if (argc > _NFILE) {
  16428.                  fprintf(stderr, "Too many files (max = %d)\n", _NFILE);
  16429.                  exit(1);
  16430.          }
  16431.  
  16432.          /* open the output file(s) */
  16433.          for (n = 0; n < argc; ++n) {
  16434.                  if ((fp[n] = fopen(argv[n], openmode)) == NULL) {
  16435.                          fprintf(stderr, "Cannot open %s\n", argv[n]);
  16436.                          continue;
  16437.                  }
  16438.          }
  16439.  
  16440.          /* copy input to stdout plus opened file(s) */
  16441.          while ((ch = getchar()) != EOF) {
  16442.                  putchar(ch);
  16443.                  for (n = 0; n < argc; ++n)
  16444.                          if (fp[n] != NULL)
  16445.                                  fputc(ch, fp[n]);
  16446.          }
  16447.  
  16448.          /* close file(s) */
  16449.          if (fcloseall() == -1) {
  16450.                  fprintf(stderr, "Error closing a file\n");
  16451.                  exit(2);
  16452.          }
  16453.  
  16454.          exit(0);
  16455.  }
  16456.  
  16457.  
  16458.  
  16459.  pwd
  16460.  ───────────────────────────────────────────────────────────────────────────
  16461.  
  16462.  /*
  16463.   *      pwd -- print (display actually) the current directory pathname
  16464.   */
  16465.  
  16466.  #include <stdio.h>
  16467.  #include <direct.h>
  16468.  #include <local\std.h>
  16469.  
  16470.  main()
  16471.  {
  16472.          char *path;
  16473.  
  16474.          if ((path = getcwd(NULL, MAXPATH)) == NULL) {
  16475.                  perror("Error getting current directory");
  16476.                  exit(1);
  16477.          }
  16478.          printf("%s\n", path);
  16479.          exit(0);
  16480.  }
  16481.  
  16482.  _setargv()
  16483.  {
  16484.  }
  16485.  
  16486.  _setenvp()
  16487.  {
  16488.  }
  16489.  
  16490.  _nullcheck()
  16491.  {
  16492.  }
  16493.  
  16494.  
  16495.  
  16496.  rm
  16497.  ───────────────────────────────────────────────────────────────────────────
  16498.  
  16499.  /*
  16500.   *      rm -- remove file(s)
  16501.   */
  16502.  
  16503.  #include <stdio.h>
  16504.  #include <stdlib.h>
  16505.  #include <sys\types.h>
  16506.  #include <sys\stat.h>
  16507.  #include <ctype.h>
  16508.  #include <io.h>
  16509.  #include <local\std.h>
  16510.  
  16511.  main(argc, argv)
  16512.  int argc;
  16513.  char *argv[];
  16514.  {
  16515.          int ch;
  16516.          BOOLEAN errflag,
  16517.                  iflag;
  16518.  
  16519.          static char pgm[MAXNAME + 1] = { "rm" };
  16520.          extern void getpname(char *, char *);
  16521.          static void do_rm(char *, char *, BOOLEAN);
  16522.          extern int getopt(int, char **, char *);
  16523.          extern int optind, opterr;
  16524.          extern char *optarg;
  16525.  
  16526.          /* get program name from DOS (version 3.00 and later) */
  16527.          if (_osmajor >= 3)
  16528.                  getpname(*argv, pgm);
  16529.  
  16530.          /* process optional arguments first */
  16531.          errflag = iflag = FALSE;
  16532.          while ((ch = getopt(argc, argv, "i")) != EOF)
  16533.                  switch (ch) {
  16534.                  case 'i':
  16535.                          /* interactive -- requires confirmation */
  16536.                          iflag = TRUE;
  16537.                          break;
  16538.                  case '?':
  16539.                          /* say what? */
  16540.                          errflag = TRUE;
  16541.                          break;
  16542.                  }
  16543.          argc -= optind;
  16544.          argv += optind;
  16545.  
  16546.          if (argc <= 0 || errflag == TRUE) {
  16547.                  fprintf(stderr, "%s [-i] file(s)\n", pgm);
  16548.                  exit(1);
  16549.          }
  16550.  
  16551.          /* process remaining arguments */
  16552.          for (; argc-- > 0; ++argv)
  16553.                  do_rm(pgm, *argv, iflag);
  16554.  
  16555.          exit(0);
  16556.  } /* end main() */
  16557.  
  16558.  /*
  16559.   *      do_rm -- remove a file
  16560.   */
  16561.  
  16562.  static void
  16563.  do_rm(pname, fname, iflag)
  16564.  char *pname, *fname;
  16565.  BOOLEAN iflag;
  16566.  {
  16567.          int result = 0;
  16568.          struct stat statbuf;
  16569.          static BOOLEAN affirm();
  16570.  
  16571.          if (iflag == TRUE) {
  16572.                  fprintf(stderr, "%s (y/n): ", fname);
  16573.                  if (affirm() == FALSE)
  16574.                          return;
  16575.          }
  16576.          if ((result = unlink(fname)) == -1) {
  16577.                  fprintf(stderr, "%s: ", pname);
  16578.                  perror(fname);
  16579.          }
  16580.          return;
  16581.  }
  16582.  
  16583.  /*
  16584.   *      affirm -- return TRUE if the first character of the
  16585.   *      user's response is 'y' or FALSE otherwise
  16586.   */
  16587.  
  16588.  #define MAXSTR  64
  16589.  
  16590.  static BOOLEAN
  16591.  affirm()
  16592.  {
  16593.          char line[MAXSTR + 1];
  16594.          char *response;
  16595.  
  16596.          response = fgets(line, MAXSTR, stdin);
  16597.          return (tolower(*response) == 'y' ? TRUE : FALSE);
  16598.  }
  16599.  
  16600.  
  16601.  
  16602.  ls.h
  16603.  ───────────────────────────────────────────────────────────────────────────
  16604.  
  16605.  /*
  16606.   *      ls.h -- header file for ls program
  16607.   */
  16608.  
  16609.  /* structure definition for output buffer elements */
  16610.  struct OUTBUF {
  16611.          unsigned short o_mode;  /* file mode (attributes) */
  16612.          long o_size;            /* file size in bytes */
  16613.          unsigned int o_date;    /* file modification date */
  16614.          unsigned int o_time;    /* file modification time */
  16615.          char *o_name;           /* DOS filespec */
  16616.  };
  16617.  
  16618.  /* constants for DOS file-matching routines */
  16619.  #define FILESPEC        13      /* maximum filespec + NUL */
  16620.  #define RNBYTES         21      /* bytes reserved for next_fm() calls */
  16621.  
  16622.  /* file modes (attributes) */
  16623.  #define READONLY        0x0001
  16624.  #define HIDDEN          0x0002
  16625.  #define SYSTEM          0x0004
  16626.  #define VOLUME          0x0008
  16627.  #define SUBDIR          0x0010
  16628.  #define ARCHIVE         0x0020
  16629.  
  16630.  /* structure definition for DOS file-matching routines */
  16631.  struct DTA {
  16632.          unsigned char d_reserved[RNBYTES];     /* buffer for next_fm */
  16633.          unsigned char d_attr;          /* file attribute (type) byte */
  16634.          unsigned d_mtime;              /* time of last modification */
  16635.          unsigned d_mdate;              /* date of last modification */
  16636.          long d_fsize;                  /* file size in bytes */
  16637.          char d_fname[FILESPEC];        /* file spec (filename.ext + NUL) */
  16638.  };
  16639.  
  16640.  
  16641.  
  16642.  ls
  16643.  ───────────────────────────────────────────────────────────────────────────
  16644.  
  16645.  /*
  16646.   *      ls -- display a directory listing
  16647.   */
  16648.  
  16649.  #include <stdio.h>
  16650.  #include <stdlib.h>
  16651.  #include <string.h>
  16652.  #include <memory.h>
  16653.  #include <dos.h>
  16654.  #include <direct.h>
  16655.  #include <signal.h>
  16656.  #include <search.h>
  16657.  #include <local\std.h>
  16658.  #include <local\doslib.h>
  16659.  #include "ls.h"
  16660.  
  16661.  /* allocation quantities */
  16662.  #define N_FILES 256
  16663.  #define N_DIRS  16
  16664.  
  16665.  /* global data */
  16666.  int Multicol = 0;
  16667.  int Filetype = 0;
  16668.  int Hidden = 0;
  16669.  int Longlist = 0;
  16670.  int Reverse = 0;
  16671.  int Modtime = 0;
  16672.  
  16673.  main(argc, argv)
  16674.  int argc;
  16675.  char *argv[];
  16676.  {
  16677.          int ch, i;
  16678.          int errflag;            /* error flag */
  16679.          char *ep;               /* environment pointer */
  16680.          int status = 0;         /* return status value */
  16681.          int fileattr;           /* file attribute number */
  16682.          struct DTA buf;         /* private disk buffer */
  16683.          char path[MAXPATH + 1]; /* working pathname */
  16684.          struct OUTBUF *fp, *fq; /* pointers to file array */
  16685.          char **dp, **dq;        /* pointer to directory pointer array */
  16686.          int fbc = 1;            /* file memory block allocation count */
  16687.          int dbc = 1;            /* directory memory block allocation */
  16688.                                  /* count */
  16689.          int nfiles;             /* number of file elements */
  16690.          int ndirs;              /* number of directory elements */
  16691.  
  16692.          static char pgm[MAXNAME + 1] = { "ls" };
  16693.  
  16694.          /* function prototypes */
  16695.          void getpname(char *, char *);
  16696.          extern int getopt(int, char **, char *);
  16697.          extern int optind, opterr;
  16698.          extern char *optarg;
  16699.          extern char *drvpath(char *);
  16700.          extern void fatal(char *, char *, int);
  16701.          extern void setdta(char *);
  16702.          extern int first_fm(char *, int);
  16703.          extern int next_fm();
  16704.          extern int ls_fcomp(struct OUTBUF *, struct OUTBUF *);
  16705.          extern int ls_dcomp(char *, char *);
  16706.          extern int ls_single(struct OUTBUF *, int);
  16707.          extern int ls_multi(struct OUTBUF *, int);
  16708.          extern int ls_dirx(char *, char *);
  16709.          int bailout();
  16710.  
  16711.          /* guarantee that needed DOS services are available */
  16712.          if (_osmajor < 2)
  16713.                  fatal(pgm, "ls requires DOS 2.00 or later", 1);
  16714.  
  16715.          /* get program name from DOS (version 3.00 and later) */
  16716.          if (_osmajor >= 3)
  16717.                  getpname(*argv, pgm);
  16718.  
  16719.          /* useful aliases (DOS version 3.00 and later) */
  16720.          if (strcmp(pgm, "lc") == 0)
  16721.                  ++Multicol;
  16722.          if (strcmp(pgm, "lf") == 0) {
  16723.                  ++Multicol;
  16724.                  ++Filetype;
  16725.          }
  16726.  
  16727.          /* prepare for emergencies */
  16728.          if (signal(SIGINT, bailout) == (int(*)())-1) {
  16729.                  perror("Can't set SIGINT");
  16730.                  exit(2);
  16731.          }
  16732.  
  16733.          /* process optional arguments first */
  16734.          errflag = 0;
  16735.          while ((ch = getopt(argc, argv, "aCFlrt")) != EOF)
  16736.                  switch (ch) {
  16737.                  case 'a':
  16738.                          /* all files (hidden, system, etc.) */
  16739.                          ++Hidden;
  16740.                          break;
  16741.                  case 'C':
  16742.                          ++Multicol;
  16743.                          break;
  16744.                  case 'F':
  16745.                          /* show file types (/=directory, *=executable) */
  16746.                          ++Filetype;
  16747.                          break;
  16748.                  case 'l':
  16749.                          /* long list (overrides multicolumn) */
  16750.                          ++Longlist;
  16751.                          break;
  16752.                  case 'r':
  16753.                          /* reverse sort */
  16754.                          ++Reverse;
  16755.                          break;
  16756.                  case 't':
  16757.                          /* sort by file modification time */
  16758.                          ++Modtime;
  16759.                          break;
  16760.                  case '?':
  16761.                          errflag = TRUE;
  16762.                          break;
  16763.                  }
  16764.          argc -= optind;
  16765.          argv += optind;
  16766.  
  16767.          /* check for command-line errors */
  16768.          if (argc < 0 || errflag) {
  16769.                  fprintf(stderr, "Usage: %s [-aCFlrt] [pathname ...]", pgm);
  16770.                  exit(3);
  16771.          }
  16772.  
  16773.          /* allocate initial file and directory storage areas */
  16774.          dp = dq = (char **)malloc(N_DIRS * sizeof (char *));
  16775.          if (dp == NULL)
  16776.                  fatal(pgm, "Out of memory", 4);
  16777.          fp = fq = (struct OUTBUF *)malloc(N_FILES *
  16778.                     sizeof (struct OUTBUF));
  16779.          if (fp == NULL)
  16780.                  fatal(pgm, "Out of memory", 4);
  16781.          nfiles = ndirs = 0;
  16782.  
  16783.          /* use current directory if no args */
  16784.          if (argc == 0) {
  16785.                  if (getcwd(path, MAXPATH) == NULL)
  16786.                          fatal(pgm, "Cannot get current directory", 5);
  16787.                  *dq = path;
  16788.                  ndirs = 1;
  16789.          }
  16790.          else {
  16791.                  /* use arguments as file and directory names */
  16792.                  for ( ; argc-- > 0; ++argv) {
  16793.                          strcpy(path, *argv);
  16794.                          if (path[0] == '\\') {
  16795.                                  /* prepend default drive name */
  16796.                                  memcpy(path + 2, path, strlen(path) + 1);
  16797.                                  path[0] = 'a' + getdrive();
  16798.                                  path[1] = ':';
  16799.                          }
  16800.                          if (path[1] == ':' && path[2] ==
  16801.                              '\0' && drvpath(path) == NULL) {
  16802.                                  fprintf(stderr,
  16803.                                          "%s: Cannot get drive path", pgm);
  16804.                                  continue;
  16805.                          }
  16806.  
  16807.                          /* establish private disk transfer area */
  16808.                          setdta((char *)&buf);
  16809.  
  16810.                          /* set file attribute for search */
  16811.                          if (Hidden)
  16812.                                  fileattr = SUBDIR | HIDDEN | SYSTEM |
  16813.                                             READONLY;
  16814.                          else
  16815.                                  fileattr = SUBDIR;
  16816.                          if (first_fm(path, fileattr) != 0 && path[3] !=
  16817.                              '\0') {
  16818.                                  fprintf(stderr,
  16819.                                          "%s -- No such file or
  16820.                                          directory\n", path);
  16821.                                  continue;
  16822.                          }
  16823.                          if ((buf.d_attr & SUBDIR) == SUBDIR || path[3] ==
  16824.                              '\0') {
  16825.                                  /* path is a (sub)directory */
  16826.                                  *dq = strdup(path);
  16827.                                  if (++ndirs == dbc * N_DIRS) {
  16828.                                          ++dbc; /* increase space */
  16829.                                                 /* requirement */
  16830.                                          dp = (char **)realloc(dp, dbc *
  16831.                                                N_DIRS
  16832.                                                  * sizeof (char *));
  16833.                                          if (dp == NULL)
  16834.                                                  fatal(pgm,
  16835.                                                        "Out of memory", 4);
  16836.                                          dq = dp + dbc * N_DIRS;
  16837.                                  }
  16838.                                  else
  16839.                                          ++dq;
  16840.                          }
  16841.                          else {
  16842.                                  fq->o_name = strdup(path);
  16843.                                  fq->o_mode = buf.d_attr;
  16844.                                  fq->o_date = buf.d_mdate;
  16845.                                  fq->o_time = buf.d_mtime;
  16846.                                  fq->o_size = buf.d_fsize;
  16847.                                  if (++nfiles == fbc * N_FILES) {
  16848.                                          ++fbc;
  16849.                                          fp = (struct OUTBUF *)realloc(fp,
  16850.                                                  fbc * N_FILES *
  16851.                                                  sizeof (struct OUTBUF));
  16852.                                          if (fp == NULL)
  16853.                                                  fatal(pgm, "Out of memory",
  16854.                                                        4);
  16855.                                          fq = fp + fbc * N_FILES;
  16856.                                  }
  16857.                                  else
  16858.                                          ++fq;
  16859.                          }
  16860.                  }
  16861.          }
  16862.  
  16863.          /* output file list, if any */
  16864.          if (nfiles > 0) {
  16865.                  qsort(fp, nfiles, sizeof(struct OUTBUF), ls_fcomp);
  16866.                  if (Longlist)
  16867.                          ls_long(fp, nfiles);
  16868.                  else if (Multicol)
  16869.                          ls_multi(fp, nfiles);
  16870.                  else
  16871.                          ls_single(fp, nfiles);
  16872.                  putchar('\n');
  16873.          }
  16874.          free(fp);
  16875.  
  16876.          /* output directory lists, if any */
  16877.          if (ndirs == 1 && nfiles == 0) {
  16878.                  /* expand directory and output without header */
  16879.                  if (ls_dirx(pgm, *dp))
  16880.                          fprintf(stderr, "%s -- empty directory\n",
  16881.                                  strlwr(*dp));
  16882.          }
  16883.          else if (ndirs > 0) {
  16884.                  /* expand each directory and output with headers */
  16885.                  dq = dp;
  16886.                  qsort(dp, ndirs, sizeof(char *), ls_dcomp);
  16887.                  while (ndirs-- > 0) {
  16888.                          fprintf(stdout, "%s:\n", strlwr(*dq));
  16889.                          if (ls_dirx(pgm, *dq++))
  16890.                                  fprintf(stderr, "%s -- empty directory\n",
  16891.                                          strlwr(*dq));
  16892.                          putchar('\n');
  16893.                  }
  16894.          }
  16895.  
  16896.          exit(0);
  16897.  }
  16898.  
  16899.  /*
  16900.   *      bailout -- optionally terminate upon interrupt
  16901.   */
  16902.  int
  16903.  bailout()
  16904.  {
  16905.          char ch;
  16906.  
  16907.          signal(SIGINT, bailout);
  16908.          printf("\nTerminate directory listing? ");
  16909.          scanf("%1s", &ch);
  16910.          if (ch == 'y' || ch == 'Y')
  16911.                  exit(1);
  16912.  }
  16913.  
  16914.  
  16915.  
  16916.  drvpath
  16917.  ───────────────────────────────────────────────────────────────────────────
  16918.  
  16919.  /*
  16920.   *      drvpath -- convert a drive name to a full pathname
  16921.   */
  16922.  
  16923.  #include <stdio.h>
  16924.  #include <dos.h>
  16925.  #include <string.h>
  16926.  #include <ctype.h>
  16927.  #include <local\doslib.h>
  16928.  
  16929.  char *
  16930.  drvpath(path)
  16931.  char path[];    /* path string */
  16932.                  /* must be large enough to hold a full DOS path + NUL */
  16933.  {
  16934.          union REGS inregs, outregs;
  16935.          static int drive(char);
  16936.  
  16937.          /* patch root directory onto drive name */
  16938.          strcat(path, "\\");
  16939.  
  16940.          /* set current directory path for drive from DOS */
  16941.          inregs.h.ah = GET_CUR_DIR;
  16942.          inregs.h.dl = drive(path[0]);         /* convert to drive number */
  16943.          inregs.x.si = (unsigned)&path[3];     /* start of return string */
  16944.          intdos(&inregs, &outregs);
  16945.  
  16946.          return (outregs.x.cflag ? (char *)NULL : path);
  16947.  }
  16948.  
  16949.  static int
  16950.  drive(dltr)
  16951.  char dltr;      /* drive letter */
  16952.  {
  16953.          /* 'A' (or 'a') => 1, 'B' (or 'b') => 2, etc. */
  16954.          return (tolower(dltr) - 'a' + 1);
  16955.  }
  16956.  
  16957.  
  16958.  
  16959.  ls_fcomp
  16960.  ───────────────────────────────────────────────────────────────────────────
  16961.  
  16962.  /*
  16963.   *      ls_fcomp -- file and directory comparison functions
  16964.   */
  16965.  
  16966.  #include <string.h>
  16967.  #include "ls.h"
  16968.  
  16969.  extern int Modtime;
  16970.  extern int Reverse;
  16971.  
  16972.  
  16973.  /*
  16974.   *      ls_fcomp -- compare two "file" items
  16975.   */
  16976.  
  16977.  int
  16978.  ls_fcomp(s1, s2)
  16979.  struct OUTBUF *s1, *s2;
  16980.  {
  16981.          int result;
  16982.  
  16983.          if (Modtime) {
  16984.                  if ((result = s1->o_date - s2->o_date) == 0)
  16985.                          result = s1->o_time - s2->o_time;
  16986.          }
  16987.          else
  16988.                  result = strcmp(s1->o_name, s2->o_name);
  16989.  
  16990.          return (Reverse ? -result : result);
  16991.  } /* end_fcomp() */
  16992.  
  16993.  
  16994.  /*
  16995.   *      dcomp -- compare two "directory" items
  16996.   */
  16997.  
  16998.  int
  16999.  ls_dcomp(s1, s2)
  17000.  char *s1, *s2;
  17001.  {
  17002.          int result;
  17003.  
  17004.          result = strcmp(s1, s2);
  17005.  
  17006.          return (Reverse ? -result : result);
  17007.  } /* end ls_dcomp() */
  17008.  
  17009.  
  17010.  
  17011.  ls_list
  17012.  ───────────────────────────────────────────────────────────────────────────
  17013.  
  17014.  /*
  17015.   *      ls_list -- list functions (long, single, multi) for ls
  17016.   */
  17017.  
  17018.  #include <stdio.h>
  17019.  #include <string.h>
  17020.  #include "ls.h"
  17021.  
  17022.  #define MAXCOL          80
  17023.  #define MONTH_SHIFT     5
  17024.  #define MONTH_MASK      0x0F
  17025.  #define DAY_MASK        0x1F
  17026.  #define YEAR_SHIFT      9
  17027.  #define DOS_EPOCH       80
  17028.  #define HOUR_SHIFT      11
  17029.  #define HOUR_MASK       0x1F
  17030.  #define MINUTE_SHIFT    5
  17031.  #define MINUTE_MASK     0x3F
  17032.  
  17033.  extern int Filetype;
  17034.  
  17035.  /*
  17036.   *      ls_long -- list items in "long" format (mode time size name)
  17037.   */
  17038.  
  17039.  int
  17040.  ls_long(buf, nelem)
  17041.  struct OUTBUF *buf;
  17042.  int nelem;
  17043.  {
  17044.         int n = 0;
  17045.         char modebuf[5];
  17046.         static void modestr(unsigned short, char *);
  17047.  
  17048.         while (nelem-- > 0) {
  17049.                 /* convert mode number to a string */
  17050.                 modestr(buf->o_mode, modebuf);
  17051.                 printf("%s ", modebuf);
  17052.  
  17053.                 /* display file size in bytes */
  17054.                 printf("%7ld ", buf->o_size);
  17055.  
  17056.                 /* convert date and time values to formatted presentation */
  17057.                 printf("%02d-%02d-%02d ", (buf->o_date >> MONTH_SHIFT) &
  17058.                        MONTH_MASK, buf->o_date & DAY_MASK, (buf->o_date >>
  17059.                        YEAR_SHIFT) + DOS_EPOCH);
  17060.                 printf("%02d:%02d ", (buf->o_time >> HOUR_SHIFT) &
  17061.                        HOUR_MASK, (buf->o_time >> MINUTE_SHIFT) &
  17062.                        MINUTE_MASK);
  17063.  
  17064.                 /* display filenames as lowercase strings */
  17065.                 printf("%s\n", strlwr(buf->o_name));
  17066.  
  17067.                 ++buf;
  17068.                 ++n;
  17069.         }
  17070.  
  17071.         /* tell caller how many entries were printed */
  17072.         return (n);
  17073.  } /* end ls_long() */
  17074.  
  17075.  
  17076.  /*
  17077.   *      ls_single -- list items in a single column
  17078.   */
  17079.  
  17080.  int
  17081.  ls_single(buf, nelem)
  17082.  struct OUTBUF *buf;
  17083.  int nelem;
  17084.  {
  17085.          int n = 0;
  17086.  
  17087.          while (nelem-- > 0) {
  17088.                  printf("%s", strlwr(buf->o_name));
  17089.                  if (Filetype && (buf->o_mode & SUBDIR) == SUBDIR)
  17090.                          putchar('\\');
  17091.                  putchar('\n');
  17092.                  ++buf;
  17093.                  ++n;
  17094.          }
  17095.  
  17096.          /* tell caller how many entries were printed */
  17097.          return (n);
  17098.  } /* end ls_single() */
  17099.  
  17100.  
  17101.  /*
  17102.   *      ls_multi -- list items in multiple columns that
  17103.   *      vary in width and number based on longest item size
  17104.   */
  17105.  
  17106.  int
  17107.  ls_multi(buf, nelem)
  17108.  struct OUTBUF *buf;
  17109.  int nelem;
  17110.  {
  17111.          int i, j;
  17112.          int errcount = 0;
  17113.          struct OUTBUF *tmp;     /* temporary buffer pointer */
  17114.          struct OUTBUF *base;    /* buffer pointer for multi-col output */
  17115.          int n;                  /* number of items in list */
  17116.          int len, maxlen;        /* pathname lengths */
  17117.          int ncols;              /* number of columns to output */
  17118.          int nlines;             /* number of lines to output */
  17119.  
  17120.          /*
  17121.           *  get length of longest pathname and calculate number
  17122.           *  of columns and lines (col width = maxlen + 1)
  17123.           */
  17124.          tmp = buf;
  17125.          n = 0;
  17126.          maxlen = 0;
  17127.          for (tmp = buf, n = 0; n < nelem; ++tmp, ++n)
  17128.                  if ((len = strlen(tmp->o_name)) > maxlen)
  17129.                          maxlen = len;
  17130.          /*
  17131.           *  use width of screen - 1 to allow for newline at end of
  17132.           *  line and leave two spaces between entries (one for optional
  17133.           *  file type flag)
  17134.           */
  17135.          ncols = (MAXCOL - 1) / (maxlen + 2);
  17136.          nlines = n / ncols;
  17137.          if (n % ncols)
  17138.                  ++nlines;
  17139.  
  17140.          /* output multi-column list */
  17141.          base = buf;
  17142.          for (i = 0; i < nlines; ++i) {
  17143.                  tmp = base;
  17144.                  for (j = 0; j < ncols; ++j) {
  17145.                          len = maxlen + 2;
  17146.                          len -= printf("%s", strlwr(tmp->o_name));
  17147.                          if (Filetype && (tmp->o_mode & SUBDIR) == SUBDIR) {
  17148.                                  putchar('\\');
  17149.                                  --len;
  17150.                          }
  17151.                          while (len-- > 0)
  17152.                                  putchar(' ');
  17153.                          tmp += nlines;
  17154.                          if (tmp - buf >= nelem)
  17155.                                  break;
  17156.                  }
  17157.                  putchar('\n');
  17158.                  ++base;
  17159.          }
  17160.  
  17161.          return (errcount);
  17162.  } /* end ls_multi() */
  17163.  
  17164.  
  17165.  static void
  17166.  modestr(mode, s)
  17167.  unsigned short mode;    /* file mode number */
  17168.  char s[];               /* mode string buffer */
  17169.  {
  17170.  
  17171.          /* fill in the mode string to show what's set */
  17172.          s[0] = (mode & SUBDIR) == SUBDIR ? 'd' : '-';
  17173.          s[1] = (mode & HIDDEN) == HIDDEN ? 'h' : '-';
  17174.          s[2] = (mode & SYSTEM) == SYSTEM ? 's' : '-';
  17175.          s[3] = (mode & READONLY) == READONLY ? 'r' : '-';
  17176.          s[4] = '\0';
  17177.  } /* end modestr() */
  17178.  
  17179.  
  17180.  
  17181.  ls_dirx
  17182.  ───────────────────────────────────────────────────────────────────────────
  17183.  
  17184.  /*
  17185.   *      ls_dirx -- expand the contents of a directory using
  17186.   *      the DOS first/next matching file functions
  17187.   */
  17188.  
  17189.  #include <stdio.h>
  17190.  #include <stdlib.h>
  17191.  #include <string.h>
  17192.  #include <malloc.h>
  17193.  #include <dos.h>
  17194.  #include <direct.h>
  17195.  #include <signal.h>
  17196.  #include <search.h>
  17197.  #include <local\std.h>
  17198.  #include <local\doslib.h>
  17199.  #include "ls.h"
  17200.  
  17201.  #define NFILES  1024
  17202.  
  17203.  extern int Recursive;
  17204.  extern int Longlist;
  17205.  extern int Multicol;
  17206.  extern int Hidden;
  17207.  
  17208.  int
  17209.  ls_dirx(pname, namep)
  17210.  char *pname;
  17211.  char *namep;
  17212.  {
  17213.          int status = 0;                 /* function return value */
  17214.          int n;                          /* number of items found */
  17215.          int fileattr;                   /* attributes of file-matching */
  17216.          struct DTA buf;                 /* disk transfer area */
  17217.          struct OUTBUF *bp, *bq;         /* output buffer pointers */
  17218.          char path[MAXPATH + 1];         /* working path string */
  17219.  
  17220.          extern void setdta(char *);
  17221.          extern int first_fm(char *, int);
  17222.          extern int next_fm();
  17223.          extern int ls_fcomp(struct OUTBUF *, struct OUTBUF *);
  17224.          extern char last_ch(char *);
  17225.  
  17226.          /* allocate a buffer */
  17227.          bp = bq = (struct OUTBUF *)malloc(NFILES * sizeof(struct OUTBUF));
  17228.          if (bp == NULL)
  17229.                  fatal(pname, "Out of memory");
  17230.  
  17231.          /* form name for directory search */
  17232.          strcpy(path, namep);
  17233.          if (last_ch(path) != '\\')
  17234.                  strcat(path, "\\");
  17235.          strcat(path, "*.*");
  17236.  
  17237.          /* list the files found */
  17238.          n = 0;
  17239.          /* establish a private DTA */
  17240.          setdta((char *)&buf);
  17241.          /* select file attributes */
  17242.          if (Hidden)
  17243.                  fileattr = SUBDIR | HIDDEN | SYSTEM | READONLY;
  17244.          else
  17245.                  fileattr = SUBDIR;
  17246.          if (first_fm(path, fileattr) == 0) {
  17247.                  /* add file or directory to the buffer */
  17248.                  do {
  17249.                          if (!Hidden && buf.d_fname[0] == '.')
  17250.                                  continue;
  17251.                          bq->o_name = strdup(buf.d_fname);
  17252.                          bq->o_mode = buf.d_attr;
  17253.                          bq->o_size = buf.d_fsize;
  17254.                          bq->o_date = buf.d_mdate;
  17255.                          bq->o_time = buf.d_mtime;
  17256.                          ++bq;
  17257.                          ++n;
  17258.                          setdta((char *)&buf);   /* reset to our DTA */
  17259.                  } while (next_fm() == 0);
  17260.  
  17261.                  if (n > 0) {
  17262.                          /* got some -- sort and list them */
  17263.                          qsort(bp, n, sizeof(struct OUTBUF), ls_fcomp);
  17264.                          if (Longlist)
  17265.                                  ls_long(bp, n);
  17266.                          else if (Multicol)
  17267.                                  ls_multi(bp, n);
  17268.                          else
  17269.                                  ls_single(bp, n);
  17270.                  }
  17271.          }
  17272.          else
  17273.                  ++status;
  17274.          free(bp);
  17275.  
  17276.          return (status);
  17277.  }
  17278.  
  17279.  
  17280.  
  17281.  first_fm
  17282.  ───────────────────────────────────────────────────────────────────────────
  17283.  
  17284.  /*
  17285.   *      first_fm - find first file match in work directory
  17286.   */
  17287.  
  17288.  #include <dos.h>
  17289.  #include <local\doslib.h>
  17290.  
  17291.  int
  17292.  first_fm(path, fa)
  17293.  char *path;     /* pathname of directory */
  17294.  int fa;         /* attribute(s) of file to match */
  17295.  {
  17296.          union REGS inregs, outregs;
  17297.  
  17298.          /* find first matching file */
  17299.          inregs.h.ah = FIND_FIRST;
  17300.          inregs.x.cx = fa;
  17301.          inregs.x.dx = (unsigned int)path;
  17302.          (void)intdos(&inregs, &outregs);
  17303.  
  17304.          return (outregs.x.cflag);
  17305.  }
  17306.  
  17307.  /*
  17308.   *      next_fm - find next file match in work directory
  17309.   */
  17310.  
  17311.  #include <dos.h>
  17312.  #include <local\doslib.h>
  17313.  
  17314.  int
  17315.  next_fm()
  17316.  {
  17317.          union REGS inregs, outregs;
  17318.  
  17319.          /* find next matching file */
  17320.          inregs.h.ah = FIND_NEXT;
  17321.          (void)intdos(&inregs, &outregs);
  17322.  
  17323.          return (outregs.x.cflag);
  17324.  }
  17325.  
  17326.  
  17327.  
  17328.  makefile for the LS program
  17329.  ───────────────────────────────────────────────────────────────────────────
  17330.  
  17331.  # makefile for the LS program
  17332.  
  17333.  LIB=c:\lib
  17334.  LLIB=c:\lib\local
  17335.  
  17336.  first_fm.obj:   first_fm.c
  17337.          msc $*;
  17338.          lib $(LLIB)\dos -+ $*;
  17339.  
  17340.  next_fm.obj:    next_fm.c
  17341.          msc $*;
  17342.          lib $(LLIB)\dos -+ $*;
  17343.  
  17344.  setdta.obj:     setdta.c
  17345.          msc $*;
  17346.          lib $(LLIB)\dos -+ $*;
  17347.  
  17348.  getdrive.obj:   getdrive.c
  17349.          msc $*;
  17350.          lib $(LLIB)\dos -+ $*;
  17351.  
  17352.  drvpath.obj:    drvpath.c
  17353.          msc $*;
  17354.          lib $(LLIB)\dos -+ $*;
  17355.  
  17356.  ls_fcomp.obj:   ls_fcomp.c ls.h
  17357.          msc $*;
  17358.  
  17359.  ls_dirx.obj:    ls_dirx.c ls.h
  17360.          msc $*;
  17361.  
  17362.  ls_list.obj:    ls_list.c ls.h
  17363.          msc $*;
  17364.  
  17365.  ls.obj:         ls.c ls.h
  17366.          msc $*;
  17367.  
  17368.  ls.exe:         ls.obj ls_dirx.obj ls_fcomp.obj ls_list.obj \
  17369.                  $(LLIB)\util.lib $(LLIB)\dos.lib
  17370.          link $* ls_dirx ls_fcomp ls_list $(LIB)\ssetargv, $*,, $(LLIB)\util
  17371.           $(LLIB)\dos;
  17372.  
  17373.  
  17374.  
  17375.  pr_help
  17376.  ───────────────────────────────────────────────────────────────────────────
  17377.  
  17378.  /*
  17379.   *      pr_help -- display an abbreviated manual page
  17380.   */
  17381.  
  17382.  #include <stdio.h>
  17383.  #include <local\std.h>
  17384.  
  17385.  void
  17386.  pr_help(pname)
  17387.  char *pname;
  17388.  {
  17389.        static char *m_str[] = {
  17390.              "The following options may be used singly or in "combination:",
  17391.              "-e\t set Epson-compatible mode",
  17392.              "-f\t use formfeed to eject a page (default is newlines)",
  17393.              "-g\t use generic printer mode",
  17394.              "-h hdr\t use specified header instead of filename",
  17395.              "-l len\t set page length in lines (default = 66)",
  17396.              "-n\t enable line-numbering (default = off)",
  17397.              "-o cols\t offset from left edge in columns (default = 5)",
  17398.              "-p\t preview output on screen (may be redirected)",
  17399.              "-s list\t print only selected pages",
  17400.              "-w cols\t line width in columns (default = 80)"
  17401.        };
  17402.        int i, n = sizeof (m_str)/ sizeof (char *);
  17403.  
  17404.        fprintf(stderr, "Usage: %s [options] file...\n\n", pname);
  17405.        for (i = 0; i < n; ++i)
  17406.              fprintf(stderr, "%s\n", m_str[i]);
  17407.  
  17408.        return;
  17409.  }
  17410.  
  17411.  
  17412.  
  17413.  print.h
  17414.  ───────────────────────────────────────────────────────────────────────────
  17415.  
  17416.  /*
  17417.   *      print.h -- header information for print programs
  17418.   */
  17419.  
  17420.  /* default printing format information */
  17421.  #define BOTTOM  3       /* blank lines at bottom of page */
  17422.  #define MARGIN  5       /* default margin width in columns */
  17423.  #define MAXPCOL 80      /* maximum number of printed columns per line */
  17424.  #define MAXPSTR 64      /* maximum characters in a string variable */
  17425.  #define PAGELEN 66      /* default page length (at 6 lines per inch) */
  17426.  #define LPI     6       /* default lines per inch */
  17427.  #define TABSPEC 8       /* default tab separation */
  17428.  #define TOP1    2       /* blank lines above header line */
  17429.  #define TOP2    2       /* blank lines below header line */
  17430.  
  17431.  /* primary data structure for printer programs */
  17432.  typedef struct pr_st {
  17433.          /* numeric variables */
  17434.          int p_top1;     /* lines above header */
  17435.          int p_top2;     /* lines below header */
  17436.          int p_btm;      /* lines in footer */
  17437.          int p_wid;      /* width in columns */
  17438.          int p_lmarg;    /* left margin */
  17439.          int p_rmarg;    /* right margin */
  17440.          int p_len;      /* lines per page */
  17441.          int p_lpi;      /* lines per inch */
  17442.          int p_lnum;     /* nonzero turns line-numbering on */
  17443.          int p_mode;     /* zero for generic printer */
  17444.          int p_font;     /* font number when in nongeneric mode */
  17445.          int p_ff;       /* nonzero uses formfeed to eject page */
  17446.          int p_tabint;   /* tab interval */
  17447.  
  17448.          /* string variables */
  17449.          char p_hdr[MAXPSTR];
  17450.          char p_dest[MAXPSTR];
  17451.  } PRINT;
  17452.  
  17453.  
  17454.  
  17455.  pr
  17456.  ───────────────────────────────────────────────────────────────────────────
  17457.  
  17458.  /*
  17459.   *      pr -- file printer
  17460.   */
  17461.  
  17462.  #include <stdio.h>
  17463.  #include <stdlib.h>
  17464.  #include <dos.h>
  17465.  #include <local\std.h>
  17466.  #include "print.h"
  17467.  
  17468.  char Pagelist[MAXLINE];
  17469.  
  17470.  main(argc, argv)
  17471.  int argc;
  17472.  char **argv;
  17473.  {
  17474.          int ch;
  17475.          BOOLEAN errflag;
  17476.          extern PRINT pcnf;
  17477.          static char pgm[MAXNAME + 1] = { "pr" };
  17478.  
  17479.          extern char getpname(char *, char *);
  17480.          extern int getopt(int, char **, char *);
  17481.          extern char *optarg;
  17482.          extern int optind, opterr;
  17483.          extern int pr_gcnf(char *);
  17484.          extern pr_file(char *, int, char **);
  17485.          extern void pr_help(char *);
  17486.          extern void fixtabs(int);
  17487.          extern int setprnt();
  17488.  
  17489.          if (_osmajor >= 3)
  17490.                  getpname(*argv, pgm);
  17491.  
  17492.          /* do configuration */
  17493.          if (pr_gcnf(pgm) != 0) {
  17494.                  fprintf(stderr, "%s: Configuration error", pgm);
  17495.                  exit(2);
  17496.          }
  17497.          if (setprnt() == -1) {
  17498.                  fprintf(stderr, "%s: Bad printer configuration\n", pgm);
  17499.                  exit(1);
  17500.          }
  17501.          fixtabs(pcnf.p_tabint);
  17502.  
  17503.          /* process command-line arguments */
  17504.          while ((ch = getopt(argc, argv, "efgh:l:no:ps:w:")) != EOF) {
  17505.                  switch (ch) {
  17506.                  case 'e':
  17507.                          /* force "Epson-compatible " printer mode */
  17508.                          pcnf.p_mode = 1;
  17509.                          break;
  17510.                  case 'f':
  17511.                          /* use formfeed to eject a page */
  17512.                          pcnf.p_ff = 1;
  17513.                          break;
  17514.                  case 'g':
  17515.                          /* force "generic" printer mode */
  17516.                          pcnf.p_mode = 0;
  17517.                          break;
  17518.                  case 'h':
  17519.                          /* use specified header */
  17520.                          strcpy(pcnf.p_hdr, optarg);
  17521.                          break;
  17522.                  case 'l':
  17523.                          /* set lines per page */
  17524.                          pcnf.p_len = atoi(optarg);
  17525.                          break;
  17526.                  case 'n':
  17527.                          /* enable line-numbering */
  17528.                          pcnf.p_lnum = 1;
  17529.                          break;
  17530.                  case 'o':
  17531.                          /* set left margin */
  17532.                          pcnf.p_lmarg = atoi(optarg);
  17533.                          break;
  17534.                  case 'p':
  17535.                          /* preview output on screen */
  17536.                          strcpy(pcnf.p_dest, "");
  17537.                          break;
  17538.                  case 's':
  17539.                          /* output selected pages */
  17540.                          strcpy(Pagelist, optarg);
  17541.                          break;
  17542.                  case 'w':
  17543.                          /* set page width in columns */
  17544.                          pcnf.p_wid = atoi(optarg);
  17545.                          break;
  17546.                  case '?':
  17547.                          /* unknown option */
  17548.                          errflag = TRUE;
  17549.                          break;
  17550.                  }
  17551.          }
  17552.          if (errflag == TRUE) {
  17553.                  pr_help(pgm);
  17554.                  exit(3);
  17555.          }
  17556.  
  17557.          /* print the files */
  17558.          pr_file(pgm, argc - optind, argv += optind);
  17559.  
  17560.          exit(0);
  17561.  }
  17562.  
  17563.  
  17564.  
  17565.  pr_gcnf
  17566.  ───────────────────────────────────────────────────────────────────────────
  17567.  
  17568.  /*
  17569.   *      pr_gcnf -- get configuration for pr program
  17570.   */
  17571.  
  17572.  #include <stdio.h>
  17573.  #include <string.h>
  17574.  #include <local\std.h>
  17575.  #include <local\printer.h>
  17576.  #include "print.h"
  17577.  
  17578.  /* expected number of configuration items */
  17579.  #define N_NBR   12
  17580.  
  17581.  PRINT pcnf;
  17582.  
  17583.  int
  17584.  pr_gcnf(pname)
  17585.  char *pname;
  17586.  {
  17587.         char line[MAXLINE];
  17588.         char *s;
  17589.         int cnf[N_NBR];
  17590.         int n, errcount, good;
  17591.         FILE *fp, *fconfig(char *, char *);
  17592.  
  17593.         /* get configuration file values, if any */
  17594.         n = good = errcount = 0;
  17595.         if ((fp = fconfig("CONFIG", "pr.cnf")) != NULL) {
  17596.                while (n < N_NBR && (s = fgets(line, MAXLINE, fp)) != NULL) {
  17597.                        cnf[n] = atoi(s);
  17598.                        ++n;
  17599.                }
  17600.                if ((s = fgets(line, MAXLINE, fp)) == NULL)
  17601.                        ++errcount;
  17602.                else
  17603.                        strcpy(pcnf.p_dest, strtok(line, " \t\n"));
  17604.                if (n != N_NBR)
  17605.                        ++errcount;
  17606.                if (errcount == 0)
  17607.                        good = 1;
  17608.                if (fclose(fp) == -1)
  17609.                        fatal(pname, "cannot close config file", 1);
  17610.         }
  17611.  
  17612.         /* use config data if good; use defaults otherwise */
  17613.         pcnf.p_top1 = good ? cnf[0]: TOP1;
  17614.         pcnf.p_top2 = good ? cnf[1] : TOP2;
  17615.         pcnf.p_btm = good ? cnf[2] : BOTTOM;
  17616.         pcnf.p_wid = good ? cnf[3] : MAXPCOL;
  17617.         pcnf.p_lmarg = good ? cnf[4] : MARGIN;
  17618.         pcnf.p_rmarg = good ? cnf[5] : MARGIN;
  17619.         pcnf.p_len = good ? cnf[6] : PAGELEN;
  17620.         pcnf.p_lpi = good ? cnf[7] : LPI;
  17621.         pcnf.p_mode = good ? cnf[8] : 0;
  17622.         pcnf.p_lnum = good ? cnf[9] : 0;
  17623.         pcnf.p_ff = good ? cnf[10] : 0;
  17624.         pcnf.p_tabint = good ? cnf[11] : TABSPEC;
  17625.         if (!good)
  17626.                strcpy(pcnf.p_dest, "PRN");
  17627.         if (pcnf.p_mode == 1)
  17628.                pcnf.p_font = CONDENSED;
  17629.         strcpy(pcnf.p_hdr, "");
  17630.  
  17631.         return (errcount);
  17632.  }
  17633.  
  17634.  
  17635.  
  17636.  pr_file
  17637.  ───────────────────────────────────────────────────────────────────────────
  17638.  
  17639.  /*
  17640.   *      pr_file -- process each filename or standard input
  17641.   */
  17642.  
  17643.  #include <stdio.h>
  17644.  #include <stdlib.h>
  17645.  #include <string.h>
  17646.  #include <local\std.h>
  17647.  #include "print.h"
  17648.  
  17649.  int
  17650.  pr_file(pname, ac, av)
  17651.  char *pname;
  17652.  int ac;
  17653.  char **av;
  17654.  {
  17655.          int ch, errcount = 0;
  17656.          FILE *fin, *fout;
  17657.          extern PRINT pcnf;
  17658.  
  17659.          extern void fatal(char*, char*, int);
  17660.          extern int pr_cpy(FILE *, FILE *, char *);
  17661.  
  17662.          /* open output stream only if not already open */
  17663.          if (*pcnf.p_dest == '\0' || strcmp(pcnf.p_dest, "CON") == 0)
  17664.                  fout = stdout;
  17665.          else if (strcmp(pcnf.p_dest, "PRN") == 0)
  17666.                  fout = stdprn;
  17667.          else if (strcmp(pcnf.p_dest, "AUX") == 0)
  17668.                  fout = stdaux;
  17669.          else
  17670.                  if ((fout = fopen(pcnf.p_dest, "w")) == NULL)
  17671.                          fatal(pname, "Error open destination stream", 1);
  17672.  
  17673.          /* prepare input stream */
  17674.          if (ac == 0)
  17675.                  pr_cpy(stdin, fout, "");
  17676.          else {
  17677.                  for (; ac > 0; --ac, ++av) {
  17678.                          if ((fin = fopen(*av, "r")) == NULL) {
  17679.                                 fprintf(stderr, "%s: Error opening %s\n",
  17680.                                         pname, *av);
  17681.                                 continue;
  17682.                          }
  17683.                          if (pr_cpy(fin, fout, *av) == -1) {
  17684.                                 fprintf(stderr, "%s: Cannot stat %s",
  17685.                                         pname, *av);
  17686.                                 continue;
  17687.                          }
  17688.                          if (fclose(fin) == EOF)
  17689.                                 fatal(pname, "Error closing input file", 1);
  17690.                  }
  17691.          }
  17692.  
  17693.          return (errcount);
  17694.  }
  17695.  
  17696.  
  17697.  
  17698.  pr_cpy
  17699.  ───────────────────────────────────────────────────────────────────────────
  17700.  
  17701.  /*
  17702.   *      pr_cpy -- copy input stream to output stream
  17703.   */
  17704.  
  17705.  #include <stdio.h>
  17706.  #include <string.h>
  17707.  #include <local\std.h>
  17708.  #include <local\printer.h>
  17709.  #include <sys\types.h>
  17710.  #include <sys\stat.h>
  17711.  #include <time.h>
  17712.  #include "print.h"
  17713.  
  17714.  extern PRINT pcnf;
  17715.  extern char Pagelist[MAXLINE];
  17716.  extern long Highest;
  17717.  
  17718.  int
  17719.  pr_cpy(fin, fout, fname)
  17720.  FILE *fin;
  17721.  FILE *fout;
  17722.  char *fname;
  17723.  {
  17724.          int errcount = 0;
  17725.          unsigned int p_line;    /* page-relative line number */
  17726.          long f_line;            /* file-relative line number */
  17727.          long f_page;            /* file-relative page number */
  17728.          int lnlen;              /* line length */
  17729.          char line[MAXLINE];     /* input line buffer */
  17730.          struct stat tbuf;       /* file information */
  17731.          long ltime;             /* date and time */
  17732.          FILE *fnull, *fx;       /* additional output file pointers */
  17733.  
  17734.          extern void mkslist(char *);    /* make a selection list */
  17735.          extern int selected(long);      /* is item in the list? */
  17736.          extern int spaces(int, FILE *); /* emit string of spaces */
  17737.          extern int setfont(int, FILE *);/* set printer font type */
  17738.          extern int clrprnt(FILE *);     /* clear special fonts */
  17739.          extern int lines(int, FILE *);  /* emit string of blank lines */
  17740.          static int fit(int, int);       /* will line fit on page? */
  17741.          extern int pr_line(char *, FILE *, unsigned int);
  17742.  
  17743.          /* install page selection list, if any */
  17744.          if (Pagelist[0] != '\0') {
  17745.                  /* open the NUL device for dumping output */
  17746.                  if ((fnull = fopen("NUL", "w")) == NULL) {
  17747.                          perror("Error opening NUL device");
  17748.                          exit(1);
  17749.                  }
  17750.                  mkslist(Pagelist);
  17751.          }
  17752.          else
  17753.                  Highest = BIGGEST;
  17754.          /* get date and time stamp */
  17755.          if (*fname == '\0')
  17756.                  /* using stdin -- use today's date and time */
  17757.                  ltime = time(NULL);
  17758.          else {
  17759.                  if (stat(fname, &tbuf) == -1)
  17760.                          return (-1);
  17761.                  /* use file's modification time */
  17762.                  ltime = tbuf.st_mtime;
  17763.          }
  17764.          p_line = 0;
  17765.          f_line = 1;
  17766.          f_page = 1;
  17767.          while ((lnlen = pr_getln(line, MAXLINE, fin)) > 0 ) {
  17768.                  /* if formfeed or no room for line, eject page */
  17769.                  if (line[0] == '\f' || !fit(lnlen, p_line)) {
  17770.                          /* to top of next page */
  17771.                          if (pcnf.p_ff == 0)
  17772.                                  lines(pcnf.p_len - p_line, fx);
  17773.                          else
  17774.                                  fputc('\f', fx);
  17775.                          p_line = 0;
  17776.                  }
  17777.  
  17778.                  /* if at top of page, print the header */
  17779.                  if (p_line == 0) {
  17780.                          if (f_page > Highest)
  17781.                                  break;
  17782.                          fx = selected(f_page) ? fout : fnull;
  17783.                          p_line += lines(pcnf.p_top1, fx);
  17784.                          if (pcnf.p_mode != 0)
  17785.                                  setfont(EMPHASIZED, fx);
  17786.                          spaces(pcnf.p_lmarg, fx);
  17787.                          if (*pcnf.p_hdr != '\0')
  17788.                                  fprintf(fx, "%s  ", pcnf.p_hdr);
  17789.                          else if (*fname != '\0')
  17790.                                  fprintf(fx, "%s  ", strupr(fname));
  17791.                          fprintf(fx, "Page %u  ", f_page++);
  17792.                          fputs(ctime(<ime), fx);
  17793.                          ++p_line;
  17794.                          if (pcnf.p_mode != 0)
  17795.                                  setfont(pcnf.p_font, fx);
  17796.                          p_line += lines(pcnf.p_top2, fx);
  17797.                  }
  17798.  
  17799.                  /* OK to output the line */
  17800.                  if (line[0] != '\f')
  17801.                          p_line += pr_line(line, fx, f_line++);
  17802.          }
  17803.          if (ferror(fin) != 0)
  17804.                  ++errcount;
  17805.          if (p_line > 0 && p_line < pcnf.p_len)
  17806.                  if (pcnf.p_ff == 0)
  17807.                          lines(pcnf.p_len - p_line, fx);
  17808.                  else
  17809.                          fputc('\f', fx);
  17810.  
  17811.          if (pcnf.p_mode != 0)
  17812.                  clrprnt(fx);
  17813.          return (errcount);
  17814.  }
  17815.  
  17816.  /*
  17817.   *      fit -- return nonzero value if enough physical
  17818.   *      lines are available on the current page to take
  17819.   *      the current logical line of text
  17820.   */
  17821.  
  17822.  #define NFLDWIDTH  8    /* width of number field */
  17823.  
  17824.  static int
  17825.  fit(len, ln)
  17826.  int len, ln;
  17827.  {
  17828.          int need, left; /* physical lines */
  17829.          int cols;       /* columns of actual output */
  17830.          int lw;         /* displayable line width */
  17831.  
  17832.          /* total need (columns -> physical lines) */
  17833.          cols = len + (pcnf.p_lnum > 0 ? NFLDWIDTH : 0);
  17834.          lw = pcnf.p_wid - pcnf.p_lmarg - pcnf.p_rmarg;
  17835.          need = 1 + cols / lw;
  17836.  
  17837.          /* lines remaining on page */
  17838.          left = pcnf.p_len - ln - pcnf.p_btm;
  17839.  
  17840.          return (need <= left ? 1 : 0);
  17841.  }
  17842.  
  17843.  
  17844.  
  17845.  pr_getln
  17846.  ───────────────────────────────────────────────────────────────────────────
  17847.  
  17848.  /*
  17849.   *      pr_getln -- get a line of text while expanding tabs;
  17850.   *      put text into an array and return the length of the line
  17851.   *      including termination to the calling function.
  17852.   */
  17853.  
  17854.  #include <stdio.h>
  17855.  #include <stdlib.h>
  17856.  #include <local\std.h>
  17857.  
  17858.  int
  17859.  pr_getln(s, lim, fin)
  17860.  char *s;
  17861.  int lim;
  17862.  FILE *fin;
  17863.  {
  17864.          int ch;
  17865.          register char *cp;
  17866.  
  17867.          extern int tabstop();   /* query tabstop array */
  17868.  
  17869.          cp = s;
  17870.          while (--lim > 0 && (ch = fgetc(fin)) != EOF && ch != '\n' && ch !=
  17871.                 '\f') {
  17872.                  if (ch == '\t')
  17873.                          /* loop and store spaces until next tabstop */
  17874.                          do
  17875.                                  *cp++ = ' ';
  17876.                          while (--lim > 0 && tabstop(cp - s) == 0);
  17877.                  else
  17878.                          *cp++ = ch;
  17879.          }
  17880.          if (ch == EOF && cp - s == 0)
  17881.                  ;
  17882.          else if (ch == EOF || ch == '\n')
  17883.                  *cp++ = '\n';   /* assure correct line termination */
  17884.          else if (ch == '\f' && cp - s == 0) {
  17885.                  *cp++ = '\f';
  17886.                  fgetc(fin);     /* toss the trailing newline */
  17887.          }
  17888.          *cp = '\0';
  17889.  
  17890.          return (cp - s);
  17891.  }
  17892.  
  17893.  
  17894.  
  17895.  pr_line
  17896.  ───────────────────────────────────────────────────────────────────────────
  17897.  
  17898.  /*
  17899.   *      pr_line -- ouput a buffered logical line and
  17900.   *      return a count of physical lines produced
  17901.   */
  17902.  
  17903.  #include <stdio.h>
  17904.  #include <stdlib.h>
  17905.  #include <local\std.h>
  17906.  #include "print.h"
  17907.  
  17908.  int
  17909.  pr_line(s, fout, rline)
  17910.  char *s;                /* buffered line of text */
  17911.  FILE *fout;             /* output stream */
  17912.  unsigned int rline;     /* file-relative line number */
  17913.  {
  17914.          int c_cnt;      /* character position in output line */
  17915.          int nlines;     /* number of lines output */
  17916.          extern PRINT pcnf;
  17917.  
  17918.          extern int spaces(int, FILE *); /* emit string of spaces */
  17919.  
  17920.          nlines = 1;
  17921.          c_cnt = 0;
  17922.  
  17923.          /* output the left indentation, if any */
  17924.          c_cnt += spaces(pcnf.p_lmarg, fout);
  17925.  
  17926.          /* output the line number if numbering enabled */
  17927.          if (pcnf.p_lnum != 0)
  17928.                  c_cnt += fprintf(fout, "%6u  ", rline);
  17929.  
  17930.          /* output the text of the line */
  17931.          while (*s != '\0') {
  17932.                  if (c_cnt > (pcnf.p_wid - pcnf.p_rmarg)) {
  17933.                          fputc('\n', fout);
  17934.                          ++nlines;
  17935.                          c_cnt = 0;
  17936.                          c_cnt = spaces(pcnf.p_lmarg, fout);
  17937.                  }
  17938.                  fputc(*s, fout);
  17939.                  ++s;
  17940.                  ++c_cnt;
  17941.          }
  17942.  
  17943.          return (nlines);
  17944.  }
  17945.  
  17946.  
  17947.  
  17948.  makefile for the pr program
  17949.  ───────────────────────────────────────────────────────────────────────────
  17950.  
  17951.  # makefile for the pr program
  17952.  
  17953.  LINC=c:\include\local
  17954.  LIB=c:\lib
  17955.  LLIB=c:\lib\local
  17956.  
  17957.  pr_cpy.obj:     pr_cpy.c print.h $(LINC)\printer.h
  17958.          msc  $*;
  17959.          lib prlib -+$*;
  17960.  
  17961.  pr_file.obj:    pr_file.c print.h
  17962.          msc  $*;
  17963.          lib prlib -+$*;
  17964.  
  17965.  pr_getln.obj:   pr_getln.c
  17966.          msc  $*;
  17967.          lib prlib -+$*;
  17968.  
  17969.  pr_help.obj:    pr_help.c
  17970.          msc  $*;
  17971.          lib prlib -+$*;
  17972.  
  17973.  pr_gcnf.obj:    pr_gcnf.c print.h $(LINC)\printer.h
  17974.          msc  $*;
  17975.          lib prlib -+$*;
  17976.  
  17977.  pr_line.obj:    pr_line.c print.h
  17978.          msc  $*;
  17979.          lib prlib -+$*;
  17980.  
  17981.  pr.obj:         pr.c print.h
  17982.          msc  $*;
  17983.  
  17984.  pr.exe:         pr.obj prlib.lib $(LLIB)\util.lib
  17985.          link $* $(LIB)\ssetargv, $*,, prlib $(LLIB)\util;
  17986.  
  17987.  
  17988.  
  17989.  hex.c
  17990.  ───────────────────────────────────────────────────────────────────────────
  17991.  
  17992.  /*
  17993.   *      hex.c -- hex conversions routines
  17994.   */
  17995.  
  17996.  #define NIBBLE  0x000F
  17997.  #define BYTE    0x00FF
  17998.  #define WORD    0xFFFF
  17999.  
  18000.  char hextab[] = {
  18001.          '0', '1', '2', '3', '4', '5', '6', '7',
  18002.          '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
  18003.  };
  18004.  
  18005.  /*
  18006.   *      byte2hex -- convert a byte to a string
  18007.   *      representation of its hexadecimal value
  18008.   */
  18009.  
  18010.  char *
  18011.  byte2hex(data, buf)
  18012.  unsigned char data;
  18013.  char *buf;
  18014.  {
  18015.          char *cp;
  18016.          unsigned int d;
  18017.  
  18018.          d = data & BYTE;
  18019.          cp = buf;
  18020.          *cp++ = hextab[(d >> 4) & NIBBLE];
  18021.          *cp++ = hextab[d & NIBBLE];
  18022.          *cp = '\0';
  18023.  
  18024.          return (buf);
  18025.  }
  18026.  
  18027.  /*
  18028.   *      word2hex -- convert a word to a string
  18029.   *      representation of its hexadecimal value
  18030.   */
  18031.  
  18032.  char *
  18033.  word2hex(data, buf)
  18034.  unsigned int data;
  18035.  char *buf;
  18036.  {
  18037.          char *cp;
  18038.          unsigned int d;
  18039.  
  18040.          d = data & WORD;
  18041.          cp = buf;
  18042.          *cp++ = hextab[(d >> 12) & NIBBLE];
  18043.          *cp++ = hextab[(d >> 8) & NIBBLE];
  18044.          *cp++ = hextab[(d >> 4) & NIBBLE];
  18045.          *cp++ = hextab[d & NIBBLE];
  18046.          *cp = '\0';
  18047.  
  18048.          return (buf);
  18049.  }
  18050.  
  18051.  
  18052.  
  18053.  dump
  18054.  ───────────────────────────────────────────────────────────────────────────
  18055.  
  18056.  /*
  18057.   *      dump -- display contents of non-ASCII files in hex byte and
  18058.   *      ASCII character forms (like the DOS debug dump option)
  18059.   */
  18060.  
  18061.  #include <stdio.h>
  18062.  #include <stdlib.h>
  18063.  #include <fcntl.h>
  18064.  #include <sys\types.h>
  18065.  #include <sys\stat.h>
  18066.  #include <io.h>
  18067.  #include <local\std.h>
  18068.  
  18069.  #define STDINPUT        0
  18070.  #define LINEWIDTH       80
  18071.  
  18072.  main(argc,argv)
  18073.  int argc;
  18074.  char *argv[];
  18075.  {
  18076.          int ch;
  18077.          BOOLEAN sflag = FALSE,
  18078.                  vflag = FALSE,
  18079.                  errflag = FALSE;
  18080.          int fd;
  18081.          static char pgm[MAXNAME + 1] = { "dump" };
  18082.  
  18083.          extern int getopt(int, char **, char *);
  18084.          extern char *optarg;
  18085.          extern int optind, opterr;
  18086.          extern void getpname(char *, char *);
  18087.          extern int hexdump(int, BOOLEAN);
  18088.          extern void fatal(char *, char *, int);
  18089.  
  18090.          if (_osmajor >= 3)
  18091.                  getpname(*argv, pgm);
  18092.  
  18093.          while ((ch = getopt(argc, argv, "sv")) != EOF)
  18094.                  switch (ch) {
  18095.                  case 's': /* strip -- convert all non-ASCII to '.' */
  18096.                          sflag = TRUE;
  18097.                          break;
  18098.                  case 'v': /* verbose -- tell user what's happening */
  18099.                          vflag = TRUE;
  18100.                          break;
  18101.                  case '?': /* bad option */
  18102.                          errflag = TRUE;
  18103.                          break;
  18104.                  }
  18105.  
  18106.          if (errflag == TRUE) {
  18107.                  fprintf(stderr, "Usage: %s [-sv] [file...]\n", pgm);
  18108.                  exit(1);
  18109.          }
  18110.  
  18111.          if (optind == argc) {
  18112.                  if (setmode(STDINPUT, O_BINARY) == -1)
  18113.                          fatal(pgm, "Cannot set binary mode", 2);
  18114.                  hexdump(STDINPUT, sflag);
  18115.                  exit(0);
  18116.          }
  18117.  
  18118.          for ( ; optind < argc; ++optind) {
  18119.                  if ((fd = open(argv[optind], O_BINARY | O_RDONLY)) == -1) {
  18120.                          fprintf(stderr,
  18121.                                  "%s: Error opening %s -- ", pgm,
  18122.                                  argv[optind]);
  18123.                          perror("");
  18124.                          continue;
  18125.                  }
  18126.                  if (vflag == TRUE)
  18127.                          fprintf(stdout, "\n%s:\n", argv[optind]);
  18128.                  if (hexdump(fd, sflag) == FAILURE) {
  18129.                          fprintf(stderr,
  18130.                                  "%s: Error reading %s -- ", pgm,
  18131.                                   argv[optind]);
  18132.                          perror("");
  18133.                  }
  18134.                  if (close(fd) == -1)
  18135.                          fatal(pgm, "Error closing input file", 3);
  18136.          }
  18137.  
  18138.          exit(0);
  18139.  }
  18140.  
  18141.  
  18142.  
  18143.  hexdump
  18144.  ───────────────────────────────────────────────────────────────────────────
  18145.  
  18146.  /*
  18147.   *      hexdump -- read data from an open file and "dump"
  18148.   *      it in side-by-side hex and ASCII to standard output
  18149.   */
  18150.  
  18151.  #include <stdio.h>
  18152.  #include <stdlib.h>
  18153.  #include <ctype.h>
  18154.  #include <local\std.h>
  18155.  
  18156.  #define LINEWIDTH       80
  18157.  #define NBYTES          16
  18158.  #define WORD            0xFFFF
  18159.  #define RGHTMARK        179
  18160.  #define LEFTMARK        179
  18161.  #define DEL             0x7F
  18162.  
  18163.  int hexdump(fd, strip)
  18164.  int fd;
  18165.  BOOLEAN strip;
  18166.  {
  18167.          unsigned char i;
  18168.          int n;                  /* bytes per read operation */
  18169.          unsigned long offset;   /* bytes from start of file */
  18170.          char inbuf[BUFSIZ + 1], outbuf[LINEWIDTH + 1];
  18171.          char hexbuf[5];
  18172.          register char *inp, *outp;
  18173.  
  18174.          extern char *byte2hex(unsigned char, char *);
  18175.          extern char *word2hex(unsigned int, char *);
  18176.  
  18177.          offset = 0;
  18178.          while ((n = read(fd, inbuf, BUFSIZ)) != 0) {
  18179.                  if (n == -1)
  18180.                          return FAILURE;
  18181.                  inp = inbuf;
  18182.                  while (inp < inbuf + n) {
  18183.                          outp = outbuf;
  18184.  
  18185.                          /* offset in hex */
  18186.                          outp += sprintf(outp, "%08lX",
  18187.                                  offset + (unsigned long)(inp - inbuf));
  18188.                          *outp++ = ' ';
  18189.  
  18190.                          /* block of bytes in hex */
  18191.                          for (i = 0; i < NBYTES; ++i) {
  18192.                                  *outp++ = ' ';
  18193.                                  strcpy(outp, byte2hex(*inp++, hexbuf));
  18194.                                  outp += 2;
  18195.                          }
  18196.                          *outp++ = ' ';
  18197.                          *outp++ = ' ';
  18198.                          *outp++ = (strip == TRUE) ? '|' : LEFTMARK;
  18199.  
  18200.                          /* same block of bytes in ASCII */
  18201.                          inp -= NBYTES;
  18202.                          for (i = 0; i < NBYTES; ++i) {
  18203.                                  if (strip == TRUE && (*inp < ' ' || *inp >=
  18204.                                      DEL))
  18205.                                          *outp = '.';
  18206.                                  else if (iscntrl(*inp))
  18207.                                          *outp = '.';
  18208.                                  else
  18209.                                          *outp = *inp;
  18210.                                  ++inp;
  18211.                                  ++outp;
  18212.                          }
  18213.                          *outp++ = (strip == TRUE) ? '|' : RGHTMARK;
  18214.                          *outp++ = '\n';
  18215.                          *outp = '\0';
  18216.                          fputs(outbuf, stdout);
  18217.                  }
  18218.                  offset += n;
  18219.          }
  18220.          return SUCCESS;
  18221.  }
  18222.  
  18223.  
  18224.  
  18225.  show
  18226.  ───────────────────────────────────────────────────────────────────────────
  18227.  
  18228.  /*
  18229.   *      show -- a filter that displays the contents of a file
  18230.   *      in a way that is guaranteed to be displayable
  18231.   */
  18232.  
  18233.  #include <stdio.h>
  18234.  #include <stdlib.h>
  18235.  #include <fcntl.h>
  18236.  #include <io.h>
  18237.  #include <local\std.h>
  18238.  
  18239.  main(argc, argv)
  18240.  int argc;
  18241.  char **argv;
  18242.  {
  18243.          int ch;
  18244.          FILE *fp;
  18245.          BOOLEAN sflag = FALSE;  /* strip non-ASCII characters */
  18246.          BOOLEAN vflag = FALSE;  /* verbose mode */
  18247.          BOOLEAN wflag = FALSE;  /* filter typical word-processing codes */
  18248.          BOOLEAN errflag = FALSE;
  18249.          static char pgm[] = { "show" };
  18250.  
  18251.          extern int getopt(int, char **, char *);
  18252.          extern char *optarg;
  18253.          extern int optind, opterr;
  18254.          extern int showit(FILE *, BOOLEAN, BOOLEAN);
  18255.          extern void fatal(char *, char *, int);
  18256.  
  18257.          if (_osmajor >= 3)
  18258.                  getpname(*argv, pgm);
  18259.  
  18260.          while ((ch = getopt(argc, argv, "svw")) != EOF) {
  18261.                  switch (ch) {
  18262.                  case 's': /* strip non-ASCII characters */
  18263.                          sflag = TRUE;
  18264.                          break;
  18265.                  case 'v': /* verbose */
  18266.                          vflag = TRUE;
  18267.                          break;
  18268.                  case 'w': /* use word-processing conventions */
  18269.                          wflag = sflag = TRUE;
  18270.                          break;
  18271.                  case '?':
  18272.                          errflag = TRUE;
  18273.                          break;
  18274.                  }
  18275.          }
  18276.  
  18277.          /* check for errors */
  18278.          if (errflag == TRUE) {
  18279.                  fprintf(stderr, "Usage: %s [-sv] [file...]\n", pgm);
  18280.                  exit(1);
  18281.          }
  18282.  
  18283.          /* if no file names, use standard input */
  18284.          if (optind == argc) {
  18285.                  if (setmode(fileno(stdin), O_BINARY) == -1)
  18286.                          fatal(pgm, "Cannot set binary mode on stdin", 2);
  18287.                  showit(stdin, sflag, wflag);
  18288.                  exit(0);
  18289.          }
  18290.  
  18291.          /* otherwise, process remainder of command line */
  18292.          for ( ; optind < argc; ++optind) {
  18293.                  if ((fp = fopen(argv[optind], "rb")) == NULL) {
  18294.                          fprintf(stderr,
  18295.                                  "%s: Error opening %s\n", pgm,
  18296.                                  argv[optind]);
  18297.                          perror("");
  18298.                          continue;
  18299.                  }
  18300.                  if (vflag == TRUE)
  18301.                          fprintf(stdout, "\n%s:\n", argv[optind]);
  18302.                  if (showit(fp, sflag, wflag) != 0) {
  18303.                          fprintf(stderr,
  18304.                                  "%s: Error reading %s\n", pgm,
  18305.                                  argv[optind]);
  18306.                          perror("");
  18307.                  }
  18308.                  if (fclose(fp) == EOF)
  18309.                          fatal(pgm, "Error closing input file", 2);
  18310.          }
  18311.  
  18312.          exit(0);
  18313.  }
  18314.  
  18315.  
  18316.  
  18317.  showit
  18318.  ───────────────────────────────────────────────────────────────────────────
  18319.  
  18320.  /*
  18321.   *      showit -- make non-printable characters in
  18322.   *      the stream fp visible (or optionally strip them)
  18323.   */
  18324.  
  18325.  #include <stdio.h>
  18326.  #include <ctype.h>
  18327.  #include <local\std.h>
  18328.  
  18329.  int
  18330.  showit(fp, strip, wp)
  18331.  FILE *fp;
  18332.  BOOLEAN strip;  /* strip non-ASCII codes */
  18333.  BOOLEAN wp;     /* filter typical word-processing codes */
  18334.  {
  18335.          int ch;
  18336.  
  18337.          clearerr(fp);
  18338.          while ((ch = getc(fp)) != EOF)
  18339.                  if (isascii(ch) && (isprint(ch) || isspace(ch)))
  18340.                          switch (ch) {
  18341.                          case '\r':
  18342.                                  if (wp == TRUE) {
  18343.                                          if ((ch = getc(fp)) != '\n')
  18344.                                                  ungetc(ch, fp);
  18345.                                          putchar('\r');
  18346.                                          putchar('\n');
  18347.                                  }
  18348.                                  else
  18349.                                          putchar(ch);
  18350.                                  break;
  18351.                          default:
  18352.                                  putchar(ch);
  18353.                                  break;
  18354.                          }
  18355.                  else if (strip == FALSE)
  18356.                          printf("\\%02X", ch);
  18357.                  else if (wp == TRUE && isprint(ch & ASCII))
  18358.                          putchar(ch & ASCII);
  18359.          return (ferror(fp));
  18360.  }
  18361.  
  18362.  
  18363.  
  18364.  dspytype
  18365.  ───────────────────────────────────────────────────────────────────────────
  18366.  
  18367.  /*
  18368.   *      dspytype -- determine display adapter type
  18369.   */
  18370.  
  18371.  #include <stdio.h>
  18372.  #include <dos.h>
  18373.  #include <local\bioslib.h>
  18374.  #include <local\video.h>
  18375.  
  18376.  #define MDA_SEG 0xB000
  18377.  #define CGA_SEG 0xB800
  18378.  
  18379.  main()
  18380.  {
  18381.          extern int memchk(unsigned int, unsigned int);
  18382.          int mdaflag, egaflag, cgaflag;
  18383.          int ega_mem, ega_mode;
  18384.          unsigned int features, switches;
  18385.          static int memtab[] = {
  18386.                  64, 128, 192, 256
  18387.          };
  18388.  
  18389.          mdaflag = egaflag = cgaflag = 0;
  18390.  
  18391.          /* look for display adapters */
  18392.          if (ega_info(&ega_mem, &ega_mode, &features, &switches))
  18393.                  ++egaflag;
  18394.          fputs("Enhanced graphics adapter ", stdout);
  18395.          if (egaflag) {
  18396.                  fputs("installed\n", stdout);
  18397.                  fprintf(stdout, "EGA memory size = %d-KB\n",
  18398.                          memtab[ega_mem]);
  18399.                  fprintf(stdout, "EGA is in %s mode\n",
  18400.                          ega_mode ? "monochrome" : "color");
  18401.          }
  18402.          else
  18403.                  fputs("not installed\n", stdout);
  18404.  
  18405.          /* look for IBM monochrome memory */
  18406.          if (!egaflag || (egaflag && ega_mode == 0))
  18407.                  if (memchk(MDA_SEG, 0))
  18408.                          ++mdaflag;
  18409.  
  18410.          /* look for CGA memory */
  18411.          if (!egaflag || (ega && ega_mode == 1))
  18412.                  if (memchk(CGA_SEG, 0))
  18413.                          ++cgaflag;
  18414.          }
  18415.          fputs("Monochrome adapter ", stdout);
  18416.          if (mdaflag)
  18417.                  fputs("installed\n", stdout);
  18418.          else
  18419.                  fputs("not installed\n", stdout);
  18420.          fputs("Color/graphics adapter ", stdout);
  18421.          if (cgaflag)
  18422.                  fputs("installed\n", stdout);
  18423.          else
  18424.                  fputs("not installed\n", stdout);
  18425.  
  18426.          /* report video settings */
  18427.          getstate();
  18428.          fprintf(stdout, "mode=%d width=%d page=%d\n", Vmode, Vwidth,
  18429.                  Vpage);
  18430.  
  18431.          exit(0);
  18432.  }
  18433.  
  18434.  
  18435.  
  18436.  memchk
  18437.  ───────────────────────────────────────────────────────────────────────────
  18438.  
  18439.  /*
  18440.   *      memchk -- look for random-access memory at
  18441.   *      a specified location; return nonzero if found
  18442.   */
  18443.  
  18444.  #include <dos.h>
  18445.  #include <memory.h>
  18446.  
  18447.  int
  18448.  memchk(seg, os)
  18449.  unsigned int seg;
  18450.  unsigned int os;
  18451.  {
  18452.          unsigned char tstval, oldval, newval;
  18453.          unsigned int ds;
  18454.          struct SREGS segregs;
  18455.  
  18456.          /* get value of current data segment */
  18457.          segread(&segregs);
  18458.          ds = segregs.ds;
  18459.  
  18460.          /* save current contents of test location */
  18461.          movedata(seg, os, ds, &oldval, 1);
  18462.  
  18463.          /* copy a known value into test location */
  18464.          tstval = 0xFC;
  18465.          movedata(ds, &tstval, seg, os, 1);
  18466.  
  18467.          /* read test value back and compare to value written */
  18468.          movedata(seg, os, ds, &newval, 1);
  18469.          if (newval != tstval)
  18470.                  return (0);
  18471.  
  18472.          /* restore original contents of test location */
  18473.          movedata(ds, &oldval, seg, os, 1);
  18474.  
  18475.          return (1);
  18476.  }
  18477.  
  18478.  
  18479.  
  18480.  ega_info
  18481.  ───────────────────────────────────────────────────────────────────────────
  18482.  
  18483.  /*
  18484.   *      ega_info -- gather information about an EGA;
  18485.   *      return a nonzero value if one is found
  18486.   */
  18487.  
  18488.  #include <dos.h>
  18489.  #include <local\bioslib.h>
  18490.  
  18491.  #define EGA_INFO 0x10
  18492.  #define NMODES  2
  18493.  #define NMEMSIZ 4
  18494.  
  18495.  int
  18496.  ega_info(memsize, mode, features, switches)
  18497.  int *memsize;           /* EGA memory size indicator: 0 = 64K */
  18498.                          /* 1 = 128K; 2 = 192K; 3 = 256K */
  18499.  int *mode;              /* 0 = color mode; 1 = mono mode */
  18500.                          /* use getstate function to find out which mode */
  18501.  unsigned int
  18502.          *features,      /* feature bit settings */
  18503.          *switches;      /* EGA switch settings */
  18504.  {
  18505.          int result = 0;
  18506.          union REGS inregs, outregs;
  18507.  
  18508.          /* request EGA information */
  18509.          inregs.h.ah = ALT_FUNCTION;
  18510.          inregs.h.bl = EGA_INFO;
  18511.          int86(VIDEO_IO, &inregs, &outregs);
  18512.  
  18513.          *memsize = outregs.h.bl;
  18514.          *mode = outregs.h.bh;
  18515.          *features = outregs.h.ch;
  18516.          *switches = outregs.h.cl;
  18517.  
  18518.          /* return nonzero if EGA installed */
  18519.          if (*memsize >= 0 && *memsize < NMEMSIZ &&
  18520.                  *mode >= 0 && *mode < NMODES)
  18521.                  result = 1;
  18522.          return (result);
  18523.  }
  18524.  
  18525.  
  18526.  
  18527.  cpblk
  18528.  ───────────────────────────────────────────────────────────────────────────
  18529.  
  18530.  /*
  18531.   *      cpblk -- copy a block of characters and attributes
  18532.   *      while eliminating "snow" on a standard CGA display
  18533.   */
  18534.  
  18535.  #include <conio.h>
  18536.  #include <memory.h>
  18537.  
  18538.  #define BLKCNT  10
  18539.  #define VSTAT   0x3DA
  18540.  #define VRBIT   8
  18541.  #define WRDCNT  200
  18542.  #define NBYTES  (2 * WRDCNT)
  18543.  
  18544.  /* macro to synchronize with vertical retrace period */
  18545.  #define VSYNC   while ((inp(VSTAT) & VRBIT) == VRBIT); \
  18546.                  while ((inp(VSTAT) & VRBIT) != VRBIT)
  18547.  
  18548.  int
  18549.  cpblk(src_os, src_seg, dest_os, dest_seg)
  18550.  unsigned int src_os, src_seg, dest_os, dest_seg;
  18551.  {
  18552.          register int i;
  18553.          int n;
  18554.          register int delta;
  18555.  
  18556.          n = 0;
  18557.          delta = 0;
  18558.          for (i = 0; i < BLKCNT ; ++i) {
  18559.                  /* copy a block of words during vertical retrace */
  18560.                  VSYNC;
  18561.                  movedata(src_seg, src_os + delta,
  18562.                          dest_seg, dest_os + delta, NBYTES);
  18563.                  n += WRDCNT;
  18564.  
  18565.                  /* adjust buffer offset */
  18566.                  delta += NBYTES;
  18567.          }
  18568.  
  18569.          return (n);
  18570.  }
  18571.  
  18572.  
  18573.  
  18574.  st
  18575.  ───────────────────────────────────────────────────────────────────────────
  18576.  
  18577.  /*
  18578.   *      st -- screen test using cpblk function
  18579.   */
  18580.  
  18581.  #include <stdio.h>
  18582.  #include <stdlib.h>
  18583.  #include <conio.h>
  18584.  #include <ctype.h>
  18585.  #include <dos.h>
  18586.  #include <local\std.h>
  18587.  #include <local\video.h>
  18588.  
  18589.  #define CG_SEG          0xB800
  18590.  #define MONO_SEG        0xB000
  18591.  #define NBYTES          0x1000
  18592.  #define PAGESIZ         (NBYTES / 2)
  18593.  #define PG0_OS          0
  18594.  #define PG1_OS          PG0_OS + NBYTES
  18595.  #define ESC             27
  18596.  #define MAXSCAN         14
  18597.  
  18598.  main(argc, argv)
  18599.  int argc;
  18600.  char *argv[];
  18601.  {
  18602.          int i;
  18603.          int k;                          /* user command character */
  18604.          int ca;                         /* character/attribute word */
  18605.          int ch;                         /* character value read */
  18606.          int row, col;                   /* cursor position upon entry */
  18607.          int c_start, c_end;             /* cursor scan lines */
  18608.          unsigned char attr;             /* saved video attribute */
  18609.          unsigned dseg;                  /* destination buffer segment */
  18610.          unsigned os;                    /* page offset in bytes */
  18611.          static unsigned sbuf[NBYTES];   /* screen buffer */
  18612.          unsigned *bp;                   /* screen element pointer */
  18613.          unsigned sseg;                  /* source segment */
  18614.          int special;                    /* use special copy routine */
  18615.          int apg, vpg;                   /* active and visual display
  18616.                                          /* pages */
  18617.  
  18618.          /* segment register values */
  18619.          struct SREGS segregs;
  18620.  
  18621.          extern void swap_int(int *, int *);
  18622.  
  18623.          static char pgm[] = { "st" };   /* program name */
  18624.  
  18625.          /* save user's current video state */
  18626.          getstate();
  18627.          readcur(&row, &col, Vpage);
  18628.          putcur(row - 1, 0, Vpage);
  18629.          readca(&ch, &attr, Vpage);
  18630.          getctype(&c_start, &c_end, Vpage);
  18631.          setctype(MAXSCAN, c_end);
  18632.          clrscrn(attr);
  18633.          putcur(1, 1, Vpage);
  18634.  
  18635.          /* initialize destination segment */
  18636.          special = 1;
  18637.          fputs("ScreenTest (ST): ", stderr);
  18638.          if (Vmode == CGA_C80 || Vmode == CGA_M80) {
  18639.                  dseg = CG_SEG;
  18640.                  fprintf(stderr, "Using CGA mode %d", Vmode);
  18641.          }
  18642.          else if (Vmode == MDA_M80) {
  18643.                  dseg = MONO_SEG;
  18644.                  fprintf(stderr, "Using MDA (mode %d)", Vmode);
  18645.                  special = 0;
  18646.          } else
  18647.                  fprintf(stderr, "%s: Requires 80-column text mode\n", pgm);
  18648.  
  18649.          /* process command-line arguments */
  18650.          if (argc > 2) {
  18651.                  fprintf(stderr, "Usage: %s [x]\n", pgm);
  18652.                  exit(2);
  18653.          }
  18654.          else if (argc == 2)
  18655.                  special = 0;    /* bypass special block move */
  18656.  
  18657.          /* get data segment value */
  18658.          segread(&segregs);
  18659.          sseg = segregs.ds;
  18660.  
  18661.          /* set up "active" and "visual" display pages */
  18662.          apg = 1;        /* page being written to */
  18663.          vpg = 0;        /* page being viewed */
  18664.  
  18665.          /* display buffers on the user's command */
  18666.          fprintf(stderr, " -- Type printable text; Esc=exit");
  18667.          while ((k = getkey()) != ESC) {
  18668.                  if (isascii(k) && isprint(k)) {
  18669.                          /* fill the buffer */
  18670.                          ca = ((k % 0xEF) << 8) | k;
  18671.                          for (bp = sbuf; bp - sbuf < PAGESIZ; ++bp)
  18672.                                  *bp = ca;
  18673.                          if (Vmode == MDA_M80)
  18674.                                  os = 0;
  18675.                          else
  18676.                                  os = (apg == 0) ? PG0_OS : PG1_OS;
  18677.                          if (special)
  18678.                                  cpblk(sbuf, sseg, os, dseg);
  18679.                          else
  18680.                                  movedata(sseg, sbuf, dseg, os, NBYTES);
  18681.                          if (Vmode != MDA_M80) {
  18682.                                  swap_int(&apg, &vpg);
  18683.                                  setpage(vpg);
  18684.                          }
  18685.                  }
  18686.                  else {
  18687.                          clrscrn(attr);
  18688.                          putcur(0, 0, Vpage);
  18689.                          writestr(" Type printable text; Esc = exit ", vpg);
  18690.                  }
  18691.          }
  18692.  
  18693.          /* restore user's video conditions and return to DOS */
  18694.          setpage(Vpage);
  18695.          clrscrn(attr);
  18696.          putcur(0, 0, Vpage);
  18697.          setctype(c_start, c_end);
  18698.          exit(0);
  18699.  }
  18700.  
  18701.  
  18702.  
  18703.  sbuf.h
  18704.  ───────────────────────────────────────────────────────────────────────────
  18705.  
  18706.  /*
  18707.   *      sbuf.h -- header file for buffered screen interface
  18708.   */
  18709.  
  18710.  #define SB_OK   0
  18711.  #define SB_ERR  (-1)
  18712.  
  18713.  /* screen-buffer constants */
  18714.  #define SB_ROWS 25
  18715.  #define SB_COLS 80
  18716.  #define SB_SIZ  SB_ROWS * SB_COLS
  18717.  
  18718.  /* screen character/attribute buffer element definition */
  18719.  struct BYTEBUF {
  18720.          unsigned char ch;       /* character */
  18721.          unsigned char attr;     /* attribute */
  18722.  };
  18723.  
  18724.  union CELL {
  18725.          struct BYTEBUF b;
  18726.          unsigned short cap;     /* character/attribute pair */
  18727.  };
  18728.  
  18729.  /* screen-buffer control structure */
  18730.  struct BUFFER {
  18731.          /* current position */
  18732.          short row, col;
  18733.  
  18734.          /* pointer to screen-buffer array */
  18735.          union CELL *bp;
  18736.  
  18737.          /* changed region per screen-buffer row */
  18738.          short lcol[SB_ROWS];    /* left end of changed region */
  18739.          short rcol[SB_ROWS];    /* right end of changed region */
  18740.  
  18741.          /* buffer status */
  18742.          unsigned short flags;
  18743.  };
  18744.  
  18745.  /* buffer flags values */
  18746.  #define SB_DELTA        0x01
  18747.  #define SB_RAW          0x02
  18748.  #define SB_DIRECT       0x04
  18749.  #define SB_SCROLL       0x08
  18750.  
  18751.  /* coordinates of a window (rectangular region) on the screen buffer */
  18752.  struct REGION {
  18753.          /* current position */
  18754.          short row, col;
  18755.  
  18756.          /* window boundaries */
  18757.          short r0, c0;   /* upper left corner */
  18758.          short r1, c1;   /* lower right corner */
  18759.  
  18760.          /* scrolling region boundaries */
  18761.          short sr0, sc0; /* upper left corner */
  18762.          short sr1, sc1; /* lower right corner */
  18763.  
  18764.          /* window buffer flags */
  18765.          unsigned short wflags;
  18766.  };
  18767.  
  18768.  
  18769.  
  18770.  sb_init
  18771.  ───────────────────────────────────────────────────────────────────────────
  18772.  
  18773.  /*
  18774.   *      sb_init -- initialize the buffered screen interface
  18775.   */
  18776.  
  18777.  #include <stdio.h>
  18778.  #include <stdlib.h>
  18779.  #include <string.h>
  18780.  #include <local\sbuf.h>
  18781.  
  18782.  /* global data declarations */
  18783.  struct BUFFER Sbuf;                     /* control information */
  18784.  union CELL Scrnbuf[SB_ROWS][SB_COLS];   /* screen-buffer array */
  18785.  
  18786.  int
  18787.  sb_init()
  18788.  {
  18789.          int i;
  18790.          char *um;       /* update mode */
  18791.  
  18792.          /* set initial parameter values */
  18793.          Sbuf.bp = &Scrnbuf[0][0];
  18794.          Sbuf.row = Sbuf.col = 0;
  18795.          for (i = 0; i < SB_ROWS; ++i) {
  18796.                  Sbuf.lcol[i] = SB_COLS;
  18797.                  Sbuf.rcol[i] = 0;
  18798.          }
  18799.          Sbuf.flags = 0;
  18800.  
  18801.          /* set screen update mode */
  18802.          um = strupr(getenv("UPDATEMODE"));
  18803.          if (um == NULL || strcmp(um, "BIOS") == 0)
  18804.                  Sbuf.flags &= ~SB_DIRECT;
  18805.          else if (strcmp(um, "DIRECT") == 0)
  18806.                  Sbuf.flags |= SB_DIRECT;
  18807.          else
  18808.                  return SB_ERR;
  18809.          return SB_OK;
  18810.  }
  18811.  
  18812.  
  18813.  
  18814.  sb_show
  18815.  ───────────────────────────────────────────────────────────────────────────
  18816.  
  18817.  /*
  18818.   *      sb_show -- copy the screen buffer to display memory
  18819.   */
  18820.  
  18821.  #include <stdio.h>
  18822.  #include <dos.h>
  18823.  #include <memory.h>
  18824.  #include <local\sbuf.h>
  18825.  #include <local\video.h>
  18826.  
  18827.  #define MDA_SEG 0xB000
  18828.  #define CGA_SEG 0xB800
  18829.  #define NBYTES  (2 * SB_COLS)
  18830.  
  18831.  /* macro to synchronize with vertical retrace period */
  18832.  #define VSTAT   0x3DA
  18833.  #define VRBIT   8
  18834.  #define VSYNC   while ((inp(VSTAT) & VRBIT) == VRBIT); \
  18835.                  while ((inp(VSTAT) & VRBIT) != VRBIT)
  18836.  
  18837.  extern struct BUFFER Sbuf;
  18838.  extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  18839.  
  18840.  int
  18841.  sb_show(pg)
  18842.  short pg;
  18843.  {
  18844.          register short r, c;
  18845.          short n;
  18846.          short count, ncols;
  18847.          unsigned int src_os, dest_os;
  18848.          struct SREGS segregs;
  18849.  
  18850.          if ((Sbuf.flags & SB_DIRECT) == SB_DIRECT) {
  18851.                  /* use the direct-screen interface */
  18852.                  segread(&segregs);
  18853.  
  18854.                  /* determine extent of changes */
  18855.                  n = 0;
  18856.                  for (r = 0; r < SB_ROWS; ++r)
  18857.                          if (Sbuf.lcol[r] <= Sbuf.rcol[r])
  18858.                                  ++n;
  18859.                  src_os = dest_os = 0;
  18860.                  if (n <= 2)
  18861.                          /* copy only rows that contain changes */
  18862.                          for (r = 0; r < SB_ROWS; ++r) {
  18863.                                  if (Sbuf.lcol[r] <= Sbuf.rcol[r]) {
  18864.                                          /* copy blocks during vertical
  18865.                                           * retrace */
  18866.                                          VSYNC;
  18867.                                          movedata(segregs.ds, &Scrnbuf[0][0]
  18868.                                                   + src_os,
  18869.                                                  CGA_SEG, dest_os, NBYTES);
  18870.                                          Sbuf.lcol[r] = SB_COLS;
  18871.                                          Sbuf.rcol[r] = 0;
  18872.                                  }
  18873.                                  src_os += NBYTES;
  18874.                                  dest_os += NBYTES;
  18875.                          }
  18876.                  else {
  18877.                          /* copy the entire buffer */
  18878.                          count = 3 * NBYTES;
  18879.                          ncols = 3 * NBYTES;
  18880.                          for (r = 0; r < SB_ROWS - 1; r += 3) {
  18881.                                  VSYNC;
  18882.                                  movedata(segregs.ds, &Scrnbuf[0][0]
  18883.                                           +src_os,
  18884.                                          CGA_SEG, dest_os, count);
  18885.                                  src_os += ncols;
  18886.                                  dest_os += count;
  18887.                          }
  18888.                          VSYNC;
  18889.                          movedata(segregs.ds, &Scrnbuf[0][0] + src_os,
  18890.                                  CGA_SEG, dest_os, NBYTES);
  18891.                          for (r = 0; r < SB_ROWS; ++r) {
  18892.                                  Sbuf.lcol[r] = SB_COLS;
  18893.                                  Sbuf.rcol[r] = 0;
  18894.                          }
  18895.                  }
  18896.          }
  18897.          else
  18898.                  /* use the BIOS video interface */
  18899.                  for (r = 0; r < SB_ROWS; ++r)
  18900.                          /* copy only changed portions of lines */
  18901.                          if (Sbuf.lcol[r] < SB_COLS && Sbuf.rcol[r] > 0) {
  18902.                                  for (c = Sbuf.lcol[r]; c <= Sbuf.rcol[r];
  18903.                                       ++c) {
  18904.                                          putcur(r, c, pg);
  18905.                                          writeca(Scrnbuf[r][c].b.ch,
  18906.                                                  Scrnbuf[r][c].b.attr, 1,
  18907.                                                   pg);
  18908.                                  }
  18909.                                  Sbuf.lcol[r] = SB_COLS;
  18910.                                  Sbuf.rcol[r] = 0;
  18911.                          }
  18912.  
  18913.          /* the display now matches the buffer -- clear flag bit */
  18914.          Sbuf.flags &= ~SB_DELTA;
  18915.  
  18916.          return SB_OK;
  18917.  }
  18918.  
  18919.  
  18920.  
  18921.  sb_new
  18922.  ───────────────────────────────────────────────────────────────────────────
  18923.  
  18924.  /*
  18925.   *      sb_new -- prepare a new window (rectangular region)
  18926.   *      and return a pointer to it
  18927.   */
  18928.  
  18929.  #include <stdio.h>
  18930.  #include <malloc.h>
  18931.  #include <local\sbuf.h>
  18932.  
  18933.  struct REGION *
  18934.  sb_new(top, left, height, width)
  18935.  int top;        /* top row */
  18936.  int left;       /* left column */
  18937.  int height;     /* total rows */
  18938.  int width;      /* total columns */
  18939.  {
  18940.          struct REGION *new;
  18941.  
  18942.          /* allocate the data-control structure */
  18943.          new = (struct REGION *)malloc(sizeof (struct REGION));
  18944.          if (new != NULL) {
  18945.                  new->r0 = new->sr0 = top;
  18946.                  new->r1 = new->sr1 = top + height - 1;
  18947.                  new->c0 = new->sc0 = left;
  18948.                  new->c1 = new->sc1 = left + width - 1;
  18949.                  new->row = new->col = 0;
  18950.                  new->wflags = 0;
  18951.          }
  18952.          return (new);
  18953.  }
  18954.  
  18955.  
  18956.  
  18957.  sb_move
  18958.  ───────────────────────────────────────────────────────────────────────────
  18959.  
  18960.  /*
  18961.   *      sb_move -- move the screen-buffer "cursor"
  18962.   */
  18963.  
  18964.  #include <local\sbuf.h>
  18965.  
  18966.  extern struct BUFFER Sbuf;
  18967.  extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  18968.  
  18969.  int
  18970.  sb_move(win, r, c)
  18971.  struct REGION *win;     /* window pointer */
  18972.  register short r, c;    /* buffer row and column */
  18973.  {
  18974.          /* don't change anything if request is out of range */
  18975.          if (r < 0 || r > win->r1 - win->r0 || c < 0 || c > win->c1 -
  18976.              win->c0)
  18977.                  return SB_ERR;
  18978.          win->row = r;
  18979.          win->col = c;
  18980.          Sbuf.row = r + win->r0;
  18981.          Sbuf.col = c + win->c0;
  18982.          return SB_OK;
  18983.  }
  18984.  
  18985.  
  18986.  
  18987.  sb_fill
  18988.  ───────────────────────────────────────────────────────────────────────────
  18989.  
  18990.  /*
  18991.   *      sb_fill -- fill region routines
  18992.   */
  18993.  
  18994.  #include <local\sbuf.h>
  18995.  
  18996.  extern struct BUFFER Sbuf;
  18997.  extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  18998.  
  18999.  /*
  19000.   *      sb_fill -- set all cells in a specified region
  19001.   *      to the same character/attribute value
  19002.   */
  19003.  
  19004.  int
  19005.  sb_fill(win, ch, attr)
  19006.  struct REGION *win;
  19007.  unsigned char ch;       /* fill character */
  19008.  unsigned char attr;     /* fill attribute */
  19009.  {
  19010.          register int i, j;
  19011.          unsigned short ca;
  19012.  
  19013.          ca = (attr << 8) | ch;
  19014.          for (i = win->sr0; i <= win->sr1; ++i) {
  19015.                  for (j = win->sc0; j <= win->sc1; ++j)
  19016.                          Scrnbuf[i][j].cap = ca;
  19017.                  if (win->sc0 < Sbuf.lcol[i])
  19018.                          Sbuf.lcol[i] = win->sc0;
  19019.                  if (win->sc1 > Sbuf.rcol[i])
  19020.                          Sbuf.rcol[i] = win->sc1;
  19021.          }
  19022.          Sbuf.flags |= SB_DELTA;
  19023.  
  19024.          return SB_OK;
  19025.  }
  19026.  
  19027.  
  19028.  /*
  19029.   *      sb_fillc -- set all cells in a specified region
  19030.   *      to the same character value; leave attributes undisturbed
  19031.   */
  19032.  
  19033.  int
  19034.  sb_fillc(win, ch)
  19035.  struct REGION *win;
  19036.  unsigned char ch;       /* fill character */
  19037.  {
  19038.          register int i, j;
  19039.  
  19040.          for (i = win->sr0; i <= win->sr1; ++i) {
  19041.                  for (j = win->sc0; j <= win->sc1; ++j)
  19042.                          Scrnbuf[i][j].b.ch = ch;
  19043.                  if (win->sc0 < Sbuf.lcol[i])
  19044.                          Sbuf.lcol[i] = win->sc0;
  19045.                  if (win->sc1 > Sbuf.rcol[i])
  19046.                          Sbuf.rcol[i] = win->sc1;
  19047.          }
  19048.          Sbuf.flags |= SB_DELTA;
  19049.  
  19050.          return SB_OK;
  19051.  }
  19052.  
  19053.  
  19054.  /*
  19055.   *      sb_filla -- set all cells in a specified region
  19056.   *      to the same attribute value; leave characters undisturbed
  19057.   */
  19058.  
  19059.  int
  19060.  sb_filla(win, attr)
  19061.  struct REGION *win;
  19062.  unsigned char attr;     /* fill attribute */
  19063.  {
  19064.          register int i, j;
  19065.  
  19066.          for (i = win->sr0; i <= win->sr1; ++i) {
  19067.                  for (j = win->sc0; j <= win->sc1; ++j)
  19068.                          Scrnbuf[i][j].b.attr = attr;
  19069.                  if (win->sc0 < Sbuf.lcol[i])
  19070.                          Sbuf.lcol[i] = win->sc0;
  19071.                  if (win->sc1 > Sbuf.rcol[i])
  19072.                          Sbuf.rcol[i] = win->sc1;
  19073.          }
  19074.          Sbuf.flags |= SB_DELTA;
  19075.  
  19076.          return SB_OK;
  19077.  }
  19078.  
  19079.  
  19080.  
  19081.  sb_put
  19082.  ───────────────────────────────────────────────────────────────────────────
  19083.  
  19084.   *      sb_put -- routines to put characters and strings into the
  19085.   *      screen buffer; the cursor location is altered
  19086.   */
  19087.  
  19088.  #include <local\sbuf.h>
  19089.  #include <ctype.h>
  19090.  
  19091.  extern struct BUFFER Sbuf;
  19092.  extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  19093.  
  19094.  /*
  19095.   *      sb_putc -- put a character into a screen-buffer window
  19096.   */
  19097.  
  19098.  int
  19099.  sb_putc(win, ch)
  19100.  struct REGION *win;
  19101.  unsigned char ch;
  19102.  {
  19103.          short cmax, rmax;
  19104.          short lim;
  19105.          short noscroll = 0, puterr = 0;
  19106.  
  19107.          /* calculate screen-buffer position and limits */
  19108.          cmax = win->c1 - win->c0;
  19109.          rmax = win->r1 - win->r0;
  19110.          Sbuf.row = win->r0 + win->row;
  19111.          Sbuf.col = win->c0 + win->col;
  19112.  
  19113.          /* process the character */
  19114.          switch (ch) {
  19115.          case '\b':
  19116.                  /* non-destructive backspace */
  19117.                  if (win->col > 0) {
  19118.                          --win->col;
  19119.                          Sbuf.col = win->c0 + win->col;
  19120.                          return SB_OK;
  19121.                  }
  19122.                  else
  19123.                          return SB_ERR;
  19124.          case '\n':
  19125.                  /* clear trailing line segment */
  19126.                  while (win->col < cmax)
  19127.                          if (sb_putc(win, ' ') == SB_ERR)
  19128.                                  ++puterr;
  19129.                  break;
  19130.          case '\t':
  19131.                  /* convert tab to required number of spaces */
  19132.                  lim = win->col + 8 - (win->col & 0x7);
  19133.                  while (win->col < lim)
  19134.                          if (sb_putc(win, ' ') == SB_ERR)
  19135.                                  ++puterr;
  19136.                  break;
  19137.          default:
  19138.                  /* if printable ASCII, place the character in the buffer */
  19139.                  if (isascii(ch) && isprint(ch))
  19140.                          Scrnbuf[Sbuf.row][Sbuf.col].b.ch = ch;
  19141.                  if (Sbuf.col < Sbuf.lcol[Sbuf.row])
  19142.                          Sbuf.lcol[Sbuf.row] = Sbuf.col;
  19143.                  if (Sbuf.col > Sbuf.rcol[Sbuf.row])
  19144.                          Sbuf.rcol[Sbuf.row] = Sbuf.col;
  19145.                  break;
  19146.          }
  19147.  
  19148.          /* update the cursor position */
  19149.          if (win->col < cmax)
  19150.                  ++win->col;
  19151.          else if (win->row < rmax) {
  19152.                  win->col = 0;
  19153.                  ++win->row;
  19154.          }
  19155.          else if ((win->wflags & SB_SCROLL) == SB_SCROLL) {
  19156.                  sb_scrl(win, 1);
  19157.                  win->col = 0;
  19158.                  win->row = rmax;
  19159.          }
  19160.          else
  19161.                  ++noscroll;
  19162.  
  19163.          /* update screen-buffer position */
  19164.          Sbuf.row = win->r0 + win->row;
  19165.          Sbuf.col = win->c0 + win->col;
  19166.          Sbuf.flags |= SB_DELTA;
  19167.  
  19168.          return ((noscroll || puterr) ? SB_ERR : SB_OK);
  19169.  } /* end sb_putc() */
  19170.  
  19171.  
  19172.  /*
  19173.   *      sb_puts -- put a string into the screen buffer
  19174.   */
  19175.  
  19176.  int
  19177.  sb_puts(win, s)
  19178.  struct REGION *win;
  19179.  unsigned char *s;
  19180.  {
  19181.          while (*s)
  19182.                  if (sb_putc(win, *s++) == SB_ERR)
  19183.                          return SB_ERR;
  19184.          return SB_OK;
  19185.  } /* end sb_puts() */
  19186.  
  19187.  
  19188.  
  19189.  sb_read
  19190.  ───────────────────────────────────────────────────────────────────────────
  19191.  
  19192.  /*
  19193.   *      sb_read -- read character/attribute data
  19194.   */
  19195.  
  19196.  #include <local\sbuf.h>
  19197.  
  19198.  extern struct BUFFER Sbuf;
  19199.  extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  19200.  
  19201.  unsigned char
  19202.  sb_ra(win)
  19203.  struct REGION *win;     /* window pointer */
  19204.  {
  19205.          return (Scrnbuf[win->r0 + win->row][win->c0 + win->col].b.attr);
  19206.  } /* end sb_ra() */
  19207.  
  19208.  
  19209.  /*
  19210.   *      sb_rc -- read character from current location in screen buffer
  19211.   */
  19212.  
  19213.  unsigned char
  19214.  sb_rc(win)
  19215.  struct REGION *win;     /* window pointer */
  19216.  {
  19217.          return (Scrnbuf[win->r0 + win->row][win->c0 + win->col].b.ch);
  19218.  } /* end sb_rc() */
  19219.  
  19220.  
  19221.  /*
  19222.   *      sb_rca -- read character/attribute pair from current
  19223.   *      location in screen buffer
  19224.   */
  19225.  
  19226.  unsigned short
  19227.  sb_rca(win)
  19228.  struct REGION *win;     /* window pointer */
  19229.  {
  19230.          return (Scrnbuf[win->r0 + win->row][win->c0 + win->col].cap);
  19231.  } /* end sb_rca() */
  19232.  
  19233.  
  19234.  
  19235.  sb_scrl
  19236.  ───────────────────────────────────────────────────────────────────────────
  19237.  
  19238.  /*
  19239.   *      sb_scrl -- scrolling routines
  19240.   */
  19241.  
  19242.  #include <local\sbuf.h>
  19243.  
  19244.  extern struct BUFFER Sbuf;
  19245.  extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  19246.  
  19247.  
  19248.  /*
  19249.   *      sb_scrl -- scroll the specified window
  19250.   *      n lines (direction indicated by sign)
  19251.   */
  19252.  
  19253.  int
  19254.  sb_scrl(win, n)
  19255.  struct REGION *win;
  19256.  short n;                /* number of rows to scroll */
  19257.  {
  19258.          register short r, c;
  19259.  
  19260.          if (n == 0)
  19261.                  /* clear the entire region to spaces */
  19262.                  sb_fillc(win, ' ');
  19263.          else if (n > 0) {
  19264.                  /* scroll n rows up */
  19265.                  for (r = win->sr0; r <= win->sr1 - n; ++r) {
  19266.                          for (c = win->sc0; c <= win->sc1; ++c)
  19267.                                  Scrnbuf[r][c] = Scrnbuf[r + n][c];
  19268.                          if (win->sc0 < Sbuf.lcol[r])
  19269.                                  Sbuf.lcol[r] = win->sc0;
  19270.                          if (win->sc1 > Sbuf.rcol[r])
  19271.                                  Sbuf.rcol[r] = win->sc1;
  19272.                  }
  19273.                  for ( ; r <= win->sr1; ++r) {
  19274.                          for (c = win->sc0; c <= win->sc1; ++c)
  19275.                                  Scrnbuf[r][c].b.ch = ' ';
  19276.                          if (win->sc0 < Sbuf.lcol[r])
  19277.                                  Sbuf.lcol[r] = win->sc0;
  19278.                          if (win->sc1 > Sbuf.rcol[r])
  19279.                                  Sbuf.rcol[r] = win->sc1;
  19280.                  }
  19281.          }
  19282.          else {
  19283.                  /* scroll n rows down */
  19284.                  n = -n;
  19285.                  for (r = win->sr1; r >= win->sr0 + n; --r) {
  19286.                          for (c = win->sc0; c <= win->sc1; ++c)
  19287.                                  Scrnbuf[r][c] = Scrnbuf[r - n][c];
  19288.                          if (win->sc0 < Sbuf.lcol[r])
  19289.                                  Sbuf.lcol[r] = win->sc0;
  19290.                          if (win->sc1 > Sbuf.rcol[r])
  19291.                                  Sbuf.rcol[r] = win->sc1;
  19292.                  }
  19293.                  for ( ; r >= win->sr0; --r) {
  19294.                          for (c = win->sc0; c <= win->sc1; ++c)
  19295.                                  Scrnbuf[r][c].b.ch = ' ';
  19296.                          if (win->sc0 < Sbuf.lcol[r])
  19297.                                  Sbuf.lcol[r] = win->sc0;
  19298.                          if (win->sc1 > Sbuf.rcol[r])
  19299.                                  Sbuf.rcol[r] = win->sc1;
  19300.                  }
  19301.          }
  19302.          Sbuf.flags |= SB_DELTA;
  19303.          return SB_OK;
  19304.  } /* end sb_scrl() */
  19305.  
  19306.  
  19307.  /*
  19308.   *      sb_set_scrl -- set the scroll region boundaries
  19309.   */
  19310.  
  19311.  int
  19312.  sb_set_scrl(win, top, left, bottom, right)
  19313.  struct REGION *win;     /* window pointer */
  19314.  short top, left;        /* upper left corner */
  19315.  short bottom, right;    /* lower left corner */
  19316.  {
  19317.          if (top < 0 || left < 0 ||
  19318.                  bottom > win->r1 - win->r0 || right > win->c1 - win->c0)
  19319.                  return SB_ERR;
  19320.          win->sr0 = win->r0 + top;
  19321.          win->sc0 = win->c0 + left;
  19322.          win->sr1 = win->r0 + bottom - 1;
  19323.          win->sc1 = win->c0 + right - 1;
  19324.          return SB_OK;
  19325.  } /* end sb_set_scrl() */
  19326.  
  19327.  
  19328.  
  19329.  sb_write
  19330.  ───────────────────────────────────────────────────────────────────────────
  19331.  
  19332.  /*
  19333.   *      sb_write -- screen-buffer write routines
  19334.   */
  19335.  
  19336.  #include <local\sbuf.h>
  19337.  
  19338.  extern struct BUFFER Sbuf;
  19339.  extern union CELL Scrnbuf[SB_ROWS][SB_COLS];
  19340.  
  19341.  /*
  19342.   *      sb_wa -- write an attribute to a region of the screen buffer
  19343.   */
  19344.  
  19345.  int
  19346.  sb_wa(win, attr, n)
  19347.  struct REGION *win;     /* window pointer */
  19348.  unsigned char attr;     /* attribute */
  19349.  short n;                /* repetition count */
  19350.  {
  19351.          short i;
  19352.          short row;
  19353.          short col;
  19354.  
  19355.          i = n;
  19356.          row = win->r0 + win->row;
  19357.          col = win->c0 + win->col;
  19358.          while (i--)
  19359.                  Scrnbuf[row][col + i].b.attr = attr;
  19360.  
  19361.          /* marked the changed region */
  19362.          if (col < Sbuf.lcol[row])
  19363.                  Sbuf.lcol[row] = col;
  19364.          if (col + n > Sbuf.rcol[row])
  19365.                  Sbuf.rcol[row] = col + n;
  19366.          Sbuf.flags |= SB_DELTA;
  19367.  
  19368.          return (i == 0) ? SB_OK : SB_ERR;
  19369.  } /* end sb_wa() */
  19370.  
  19371.  
  19372.  /*
  19373.   *      sb_wc -- write a character to a region of the screen buffer
  19374.   */
  19375.  
  19376.  int
  19377.  sb_wc(win, ch, n)
  19378.  struct REGION *win;     /* window pointer */
  19379.  unsigned char ch;       /* character */
  19380.  short n;                /* repetition count */
  19381.  {
  19382.          short i;
  19383.          short row;
  19384.          short col;
  19385.          i = n;
  19386.          row = win->r0 + win->row;
  19387.          col = win->c0 + win->col;
  19388.          while (i--)
  19389.                  Scrnbuf[row][col + i].b.ch = ch;
  19390.  
  19391.          /* marked the changed region */
  19392.          if (col < Sbuf.lcol[row])
  19393.                  Sbuf.lcol[row] = col;
  19394.          if (col + n > Sbuf.rcol[row])
  19395.                  Sbuf.rcol[row] = col + n;
  19396.          Sbuf.flags |= SB_DELTA;
  19397.  
  19398.          return (i == 0 ? SB_OK : SB_ERR);
  19399.  } /* end sb_wc() */
  19400.  
  19401.  
  19402.  /*
  19403.   *      sb_wca -- write a character/attribute pair to a region
  19404.   *      of the screen buffer
  19405.   */
  19406.  
  19407.  int
  19408.  sb_wca(win, ch, attr, n)
  19409.  struct REGION *win;     /* window pointer */
  19410.  unsigned char ch;       /* character */
  19411.  unsigned char attr;     /* attribute */
  19412.  short n;                /* repetition count */
  19413.  {
  19414.          int i;
  19415.          short row;
  19416.          short col;
  19417.  
  19418.          i = n;
  19419.          row = win->r0 + win->row;
  19420.          col = win->c0 + win->col;
  19421.          while (i--)
  19422.                  Scrnbuf[row][col + i].cap = (attr << 8) | ch;
  19423.  
  19424.          /* marked the changed region */
  19425.          if (col < Sbuf.lcol[row])
  19426.                  Sbuf.lcol[row] = col;
  19427.          if (col + n > Sbuf.rcol[row])
  19428.                  Sbuf.rcol[row] = col + n;
  19429.          Sbuf.flags |= SB_DELTA;
  19430.  
  19431.          return (i == 0 ? SB_OK : SB_ERR);
  19432.  } /* end sb_wca() */
  19433.  
  19434.  
  19435.  
  19436.  sb_box
  19437.  ───────────────────────────────────────────────────────────────────────────
  19438.  
  19439.  /*
  19440.   *      sb_box -- draw a box around the perimeter of a window
  19441.   *      using the appropriate IBM graphics characters
  19442.   */
  19443.  
  19444.  #include <local\sbuf.h>
  19445.  #include <local\video.h>
  19446.  #include <local\box.h>
  19447.  
  19448.  int
  19449.  sb_box(win, type, attr)
  19450.  struct REGION *win;
  19451.  short type;
  19452.  unsigned char attr;
  19453.  {
  19454.          register short r;     /* row index */
  19455.          short x;              /* interior horizontal line length */
  19456.          short maxr, maxc;     /* maximum row and col values */
  19457.          BOXTYPE *boxp;        /* pointer to box-drawing character struct */
  19458.          static BOXTYPE box[] = {
  19459.                  '+',   '+',   '+',   '+',   '-',   '-',   '|',   '|',
  19460.                  ULC11, URC11, LLC11, LRC11, HBAR1, HBAR1, VBAR1, VBAR1,
  19461.                  ULC22, URC22, LLC22, LRC22, HBAR2, HBAR2, VBAR2, VBAR2,
  19462.                  ULC12, URC12, LLC12, LRC12, HBAR1, HBAR1, VBAR2, VBAR2,
  19463.                  ULC21, URC21, LLC21, LRC21, HBAR2, HBAR2, VBAR1, VBAR1,
  19464.                  BLOCK, BLOCK, BLOCK, BLOCK, HBART, HBARB, BLOCK, BLOCK
  19465.          };
  19466.  
  19467.          boxp = &box[type];
  19468.          maxc = win->c1 - win->c0;
  19469.          maxr = win->r1 - win->r0;
  19470.          x = maxc - 1;
  19471.  
  19472.          /* draw top row */
  19473.          sb_move(win, 0, 0);
  19474.          sb_wca(win, boxp->ul, attr, 1);
  19475.          sb_move(win, 0, 1);
  19476.          sb_wca(win, boxp->tbar, attr, x);
  19477.          sb_move(win, 0, maxc);
  19478.          sb_wca(win, boxp->ur, attr, 1);
  19479.  
  19480.          /* draw left and right sides */
  19481.          for (r = 1; r < maxr; ++r) {
  19482.                  sb_move(win, r, 0);
  19483.                  sb_wca(win, boxp->lbar, attr, 1);
  19484.                  sb_move(win, r, maxc);
  19485.                  sb_wca(win, boxp->rbar, attr, 1);
  19486.          }
  19487.  
  19488.          /* draw bottom row */
  19489.          sb_move(win, maxr, 0);
  19490.          sb_wca(win, boxp->ll, attr, 1);
  19491.          sb_move(win, maxr, 1);
  19492.          sb_wca(win, boxp->bbar, attr, x);
  19493.          sb_move(win, maxr, maxc);
  19494.          sb_wca(win, boxp->lr, attr, 1);
  19495.  
  19496.          return SB_OK;
  19497.  }
  19498.  
  19499.  
  19500.  
  19501.  makefile for SBUF.LIB
  19502.  ───────────────────────────────────────────────────────────────────────────
  19503.  
  19504.  # makefile for SBUF.LIB (screen-buffer library)
  19505.  
  19506.  LLIB = c:\lib\local
  19507.  LINC = c:\include\local
  19508.  
  19509.  OBJS = sb_box.obj sb_fill.obj sb_init.obj sb_move.obj sb_new.obj \
  19510.  sb_put.obj sb_read.obj sb_scrl.obj sb_show.obj sb_write.obj
  19511.  
  19512.  MODS = sb_box sb_fill sb_init sb_move sb_new.obj sb_put sb_read \
  19513.  sb_scrl sb_show sb_write
  19514.  
  19515.  $(LINC)\sbuf.h: sbuf.h
  19516.          copy sbuf.h $(LINC)\sbuf.h
  19517.  
  19518.  $(LINC)\box.h:  box.h
  19519.          copy box.h $(LINC)\box.h
  19520.  
  19521.  sb_box.obj:     sb_box.c $(LINC)\sbuf.h $(LINC)\video.h $(LINC)\box.h
  19522.  
  19523.  sb_fill.obj:    sb_fill.c $(LINC)\sbuf.h
  19524.  
  19525.  sb_init.obj:    sb_init.c $(LINC)\sbuf.h
  19526.  
  19527.  sb_move.obj:    sb_move.c $(LINC)\sbuf.h
  19528.  
  19529.  sb_new.obj:     sb_new.c $(LINC)\sbuf.h
  19530.  
  19531.  sb_put.obj:     sb_put.c $(LINC)\sbuf.h
  19532.  
  19533.  sb_read.obj:    sb_read.c $(LINC)\sbuf.h
  19534.  
  19535.  sb_scrl.obj:    sb_scrl.c $(LINC)\sbuf.h
  19536.  
  19537.  sb_show.obj:    sb_show.c $(LINC)\sbuf.h
  19538.  
  19539.  sb_write.obj:   sb_write.c $(LINC)\sbuf.h
  19540.  
  19541.  sbuf.lib:       $(OBJS)
  19542.          del sbuf.lib
  19543.          lib sbuf +$(MODS);
  19544.          copy sbuf.lib $(LLIB)
  19545.  
  19546.  
  19547.  
  19548.  sb_test
  19549.  ───────────────────────────────────────────────────────────────────────────
  19550.  
  19551.  /*
  19552.   *      sb_test -- driver for screen-buffer interface functions
  19553.   */
  19554.  
  19555.  #include <stdio.h>
  19556.  #include <stdlib.h>
  19557.  #include <conio.h>
  19558.  #include <local\std.h>
  19559.  #include <local\keydefs.h>
  19560.  #include <local\sbuf.h>
  19561.  #include <local\video.h>
  19562.  #include <local\box.h>
  19563.  #include "sb_test.h"
  19564.  
  19565.  #define BEL 7
  19566.  
  19567.  extern struct BUFFER Sbuf;
  19568.  
  19569.  main(argc, argv)
  19570.  int argc;
  19571.  char *argv[];
  19572.  {
  19573.          char *s, line[MAXLINE];
  19574.          int k;
  19575.          short i;
  19576.          FILE *fp;
  19577.          char fname[MAXPATH];
  19578.          struct REGION *cmnd, *stat, *text, *help, *curwin;
  19579.          unsigned char cmndattr, statattr, textattr, helpattr, curattr;
  19580.          unsigned char ch, userattr;
  19581.  
  19582.          /* function prototypes */
  19583.          int sb_init();
  19584.          int sb_move(struct REGION *, short, short);
  19585.          struct REGION *sb_new(short, short, short, short);
  19586.          int sb_putc(struct REGION *, unsigned char);
  19587.          int sb_puts(struct REGION *, char *);
  19588.          int sb_show(short);
  19589.          int sb_fill(struct REGION *, unsigned char, unsigned char);
  19590.          char *get_fname(struct REGION *, char *, short);
  19591.  
  19592.          getstate();
  19593.          readca(&ch, &userattr, Vpage);
  19594.  
  19595.          /* set up the screen buffer */
  19596.          if (sb_init() == SB_ERR) {
  19597.                  fprintf(stderr, "Bad UPDATEMODE value in environment\n");
  19598.                  exit(1);
  19599.          }
  19600.  
  19601.          /* set up windows and scrolling regions */
  19602.          cmnd = sb_new(CMND_ROW, CMND_COL, CMND_HT, CMND_WID);
  19603.          stat = sb_new(STAT_ROW, STAT_COL, STAT_HT, STAT_WID);
  19604.          text = sb_new(TEXT_ROW, TEXT_COL, TEXT_HT, TEXT_WID);
  19605.          help = sb_new(HELP_ROW, HELP_COL, HELP_HT, HELP_WID);
  19606.          text->wflags |= SB_SCROLL;
  19607.          sb_set_scrl(help, 1, 1, HELP_HT - 1, HELP_WID - 1);
  19608.  
  19609.          /* display each primary window in its own attribute */
  19610.          cmndattr = GRN;
  19611.          statattr = (WHT << 4) | BLK;
  19612.          textattr = (BLU << 4) | CYAN;
  19613.          helpattr = (GRN << 4) | YEL;
  19614.          sb_fill(cmnd, ' ', cmndattr);
  19615.          if (sb_move(cmnd, 0, 0) == SB_OK)
  19616.                  sb_puts(cmnd, "SB_TEST (Version 1.0)");
  19617.          sb_fill(stat, ' ', statattr);
  19618.          if (sb_move(stat, 0, 0) == SB_OK)
  19619.                  sb_puts(stat, "*** STATUS AREA ***");
  19620.          for (i = 0; i <= text->r1 - text->r0; ++i) {
  19621.                  sb_move(text, i, 0);
  19622.                  sb_wca(text, i + 'a', textattr,
  19623.                          text->c1 - text->c0 + 1);
  19624.          }
  19625.          if (sb_move(text, 10, 25) == SB_OK)
  19626.                  sb_puts(text, " *** TEXT DISPLAY AREA *** ");
  19627.          sb_show(Vpage);
  19628.          curwin = text;
  19629.          curattr = textattr;
  19630.  
  19631.          /* respond to user commands */
  19632.          while ((k = getkey()) != K_ESC) {
  19633.                  switch (k) {
  19634.                  case K_UP:
  19635.                          sb_scrl(curwin, 1);
  19636.                          break;
  19637.                  case K_DOWN:
  19638.                          sb_scrl(curwin, -1);
  19639.                          break;
  19640.                  case K_PGUP:
  19641.                          sb_scrl(curwin, curwin->sr1 - curwin->sr0);
  19642.                          break;
  19643.                  case K_PGDN:
  19644.                          sb_scrl(curwin, -(curwin->sr1 - curwin->sr0));
  19645.                          break;
  19646.                  case K_ALTC:
  19647.                          /* clear the current window */
  19648.                          sb_fill(curwin, ' ', curattr);
  19649.                          break;
  19650.                  case K_ALTH:
  19651.                          /* display help */
  19652.                          curwin = help;
  19653.                          curattr = helpattr;
  19654.                          for (i = 0; i < help->r1 - help->r0; ++i) {
  19655.                                  sb_move(help, i, 0);
  19656.                                  sb_wca(help, i + 'a', helpattr,
  19657.                                          help->c1 - help->c0 + 1);
  19658.                          }
  19659.                          sb_box(help, BOXBLK, helpattr);
  19660.                          break;
  19661.                  case K_ALTS:
  19662.                          /* fill the command area with letters */
  19663.                          curwin = stat;
  19664.                          curattr = statattr;
  19665.                          sb_fill(stat, 's', statattr);
  19666.                          break;
  19667.                  case K_ALTT:
  19668.                          /* fill the text area */
  19669.                          curwin = text;
  19670.                          curattr = textattr;
  19671.                          for (i = 0; i <= text->r1 - text->r0; ++i) {
  19672.                                  sb_move(text, i, 0);
  19673.                                  sb_wca(text, i + 'a', textattr,
  19674.                                          text->c1 - text->c0 + 1);
  19675.                          }
  19676.                          break;
  19677.                  case K_ALTR:
  19678.                          /* read a file into the current window */
  19679.                          sb_fill(stat, ' ', statattr);
  19680.                          sb_move(stat, 0, 0);
  19681.                          sb_puts(stat, "File to read: ");
  19682.                          sb_show(Vpage);
  19683.                          (void)get_fname(stat, fname, MAXPATH);
  19684.                          if ((fp = fopen(fname, "r")) == NULL) {
  19685.                                  sb_fill(stat, ' ', statattr);
  19686.                                  sb_move(stat, 0, 0);
  19687.                                  sb_puts(stat, "Cannot open ");
  19688.                                  sb_puts(stat, fname);
  19689.                          }
  19690.                          else {
  19691.                                  sb_fill(stat, ' ', statattr);
  19692.                                  sb_move(stat, 0, 0);
  19693.                                  sb_puts(stat, "File: ");
  19694.                                  sb_puts(stat, fname);
  19695.                                  sb_show(Vpage);
  19696.                                  sb_fill(text, ' ', textattr);
  19697.                                  sb_move(text, 0, 0);
  19698.                                  putcur(text->r0, text->c0, Vpage);
  19699.                                  while ((s = fgets(line, MAXLINE,
  19700.                                         fp)) != NULL) {
  19701.                                          if (sb_puts(text, s) == SB_ERR) {
  19702.                                                  clrscrn(userattr);
  19703.                                                  putcur(0, 0, Vpage);
  19704.                                                  fprintf(stderr,
  19705.                                                         "puts error\n");
  19706.                                                  exit(1);
  19707.                                          }
  19708.                                          sb_show(Vpage);
  19709.                                  }
  19710.                                  if (ferror(fp)) {
  19711.                                          putcur(text->r0, text->c0, Vpage);
  19712.                                          fprintf(stderr,
  19713.                                                  "Error reading file\n");
  19714.                                  }
  19715.                                  fclose(fp);
  19716.                          }
  19717.                          break;
  19718.                  default:
  19719.                          /* say what? */
  19720.                          fputc(BEL, stderr);
  19721.                          continue;
  19722.                  }
  19723.                  if ((Sbuf.flags & SB_DELTA) == SB_DELTA)
  19724.                          sb_show(Vpage);
  19725.          }
  19726.  
  19727.          clrscrn(userattr);
  19728.          putcur(0, 0, Vpage);
  19729.          exit(0);
  19730.  }
  19731.  
  19732.  /*
  19733.   *      get_fname -- get a filename from the user
  19734.   */
  19735.  
  19736.  char *
  19737.  get_fname(win, path, lim)
  19738.  struct REGION *win;
  19739.  char *path;
  19740.  short lim;
  19741.  {
  19742.          int ch;
  19743.          char *s;
  19744.  
  19745.          s = path;
  19746.          sb_show(Vpage);
  19747.          while ((ch = getch()) != K_RETURN) {
  19748.                  if (ch == '\b')
  19749.                          --s;
  19750.                  else {
  19751.                          sb_putc(win, ch);
  19752.                          *s++ = ch;
  19753.                  }
  19754.                  sb_show(Vpage);
  19755.          }
  19756.          *s = '\0';
  19757.          return (path);
  19758.  }
  19759.  
  19760.  
  19761.  
  19762.  ansi.h
  19763.  ───────────────────────────────────────────────────────────────────────────
  19764.  
  19765.  /*
  19766.   *      ansi.h -- header file for ANSI driver information and
  19767.   *      macro versions of the ANSI control sequences
  19768.   */
  19769.  
  19770.  /*****************/
  19771.  /** ANSI macros **/
  19772.  /*****************/
  19773.  
  19774.  /* cursor position */
  19775.  #define ANSI_CUP(r, c)  printf("\x1B[%d;%dH", r, c)
  19776.  #define ANSI_HVP(r, c)  printf("\x1B[%d;%df", r, c)
  19777.  
  19778.  /* cursor up, down, forward, back */
  19779.  #define ANSI_CUU(n)     printf("\x1B[%dA", n)
  19780.  #define ANSI_CUD(n)     printf("\x1B[%dB", n)
  19781.  #define ANSI_CUF(n)     printf("\x1B[%dC", n)
  19782.  #define ANSI_CUB(n)     printf("\x1B[%dD", n)
  19783.  
  19784.  /* device status report (dumps position data into keyboard buffer) */
  19785.  #define ANSI_DSR        printf("\x1B[6n")
  19786.  
  19787.  /* save and restore cursor position */
  19788.  #define ANSI_SCP        fputs("\x1B[s", stdout)
  19789.  #define ANSI_RCP        fputs("\x1B[u", stdout)
  19790.  
  19791.  /* erase display and line */
  19792.  #define ANSI_ED         fputs("\x1B[2J", stdout);
  19793.  #define ANSI_EL         fputs("\x1B[K", stdout)
  19794.  
  19795.  /* set graphic rendition */
  19796.  #define ANSI_SGR(a)     printf("\x1B[%dm", a)
  19797.  
  19798.  /* set and reset modes */
  19799.  #define ANSI_SM(m)      printf("\x1B[=%dh", m)
  19800.  #define ANSI_RM(m)      printf("\x1B[=%dl", m)
  19801.  
  19802.  
  19803.  /**********************/
  19804.  /** ANSI color codes **/
  19805.  /**********************/
  19806.  
  19807.  /* special settings */
  19808.  #define ANSI_NORMAL     0
  19809.  #define ANSI_BOLD       1
  19810.  #define ANSI_BLINK      5
  19811.  #define ANSI_REVERSE    7
  19812.  #define ANSI_INVISIBLE  8
  19813.  
  19814.  /* shift values */
  19815.  #define ANSI_FOREGROUND 30
  19816.  #define ANSI_BACKGROUND 40
  19817.  
  19818.  /* basic colors */
  19819.  #define ANSI_BLACK      0
  19820.  #define ANSI_RED        1
  19821.  #define ANSI_GREEN      2
  19822.  #define ANSI_BROWN      3
  19823.  #define ANSI_BLUE       4
  19824.  #define ANSI_MAGENTA    5
  19825.  #define ANSI_CYAN       6
  19826.  #define ANSI_WHITE      7
  19827.  
  19828.  
  19829.  /*****************************/
  19830.  /** modes for set and reset **/
  19831.  /*****************************/
  19832.  
  19833.  #define ANSI_M40        0
  19834.  #define ANSI_C40        1
  19835.  #define ANSI_M80        2
  19836.  #define ANSI_C80        3
  19837.  #define ANSI_C320       4
  19838.  #define ANSI_M320       5
  19839.  #define ANSI_M640       6
  19840.  #define ANSI_WRAP       7
  19841.  
  19842.  /* attribute "position" type */
  19843.  typedef enum {
  19844.          FGND, BKGND, BDR
  19845.  } POSITION;
  19846.  
  19847.  
  19848.  
  19849.  cpr
  19850.  ───────────────────────────────────────────────────────────────────────────
  19851.  
  19852.  /*
  19853.   *      cpr -- report where the cursor is located
  19854.   *      The position information is placed in the keyboard buffer in the
  19855.   *      form ESC[rr;ccR, where ESC is the value of the ESCAPE character
  19856.   *      and r and c represent decimal values of row and column data.
  19857.   */
  19858.  
  19859.  #include <local\ansi.h>
  19860.  
  19861.  void ansi_cpr(row, col)
  19862.  int     *row,
  19863.          *col;
  19864.  {
  19865.          int i;
  19866.  
  19867.          /* request a cursor position report */
  19868.          ANSI_DSR;
  19869.  
  19870.          /* toss the ESC and '[' */
  19871.          (void) getkey();
  19872.          (void) getkey();
  19873.  
  19874.          /* read the row number */
  19875.          *row = 10 * (getkey() - '0');
  19876.          *row = *row + getkey() - '0';
  19877.  
  19878.          /* toss the ';' separator */
  19879.          (void) getkey();
  19880.  
  19881.          /* read the column number */
  19882.          *col = 10 * (getkey() - '0');
  19883.          *col = *col + getkey() - '0';
  19884.  
  19885.          /* toss the trailing ('R') and return */
  19886.          (void) getkey();
  19887.          (void) getkey();
  19888.          return;
  19889.  }
  19890.  
  19891.  
  19892.  
  19893.  ansi_tst
  19894.  ───────────────────────────────────────────────────────────────────────────
  19895.  
  19896.  /*
  19897.   *      ansi_tst -- verify that the ANSI.SYS driver is loaded
  19898.   *      (prints message and exits if ANSI driver not working)
  19899.   */
  19900.  
  19901.  #include <stdio.h>
  19902.  #include <local\ansi.h>
  19903.  #include <local\video.h>
  19904.  
  19905.  #define TST_ROW 2
  19906.  #define TST_COL 75
  19907.  
  19908.  void
  19909.  ansi_tst()
  19910.  {
  19911.          int row, col;
  19912.          static char *help[] = {
  19913.                  "\n",
  19914.                  "ANSI.SYS device driver not loaded:\n",
  19915.                  "  1. Copy ANSI.SYS to your system disk.\n",
  19916.                  "  2. Add the line device=ansi.sys to your\n",
  19917.                  "     CONFIG.SYS file and reboot your machine.\n",
  19918.                  NULL
  19919.          };
  19920.          char **msg;
  19921.  
  19922.          extern int getstate();
  19923.          extern int readcur(int *, int *, int);
  19924.  
  19925.          getstate();
  19926.          ANSI_CUP(TST_ROW, TST_COL);
  19927.          readcur(&row, &col, Vpage);
  19928.          if (row != TST_ROW - 1 || col != TST_COL - 1) {
  19929.                  for (msg = help; *msg != NULL; ++msg)
  19930.                          fputs(*msg, stderr);
  19931.                  exit(1);
  19932.          }
  19933.  
  19934.          return;
  19935.  }
  19936.  
  19937.  
  19938.  
  19939.  sc
  19940.  ───────────────────────────────────────────────────────────────────────────
  19941.  
  19942.  /*
  19943.   *  SetColor (sc) -- set foreground, background, and border attributes
  19944.   *              on systems equipped with color display systems
  19945.   *
  19946.   *  Usage:      sc [foreground [background [border]]]
  19947.   *              sc [attribute]
  19948.   */
  19949.  
  19950.  #include <stdio.h>
  19951.  #include <dos.h>
  19952.  #include <local\std.h>
  19953.  #include <local\ansi.h>
  19954.  #include <local\ibmcolor.h>
  19955.  
  19956.  main(argc, argv)
  19957.  int argc;
  19958.  char *argv[];
  19959.  {
  19960.          void ansi_tst();
  19961.          BOOLEAN iscolor();
  19962.          extern void setattr();
  19963.          extern void menumode();
  19964.  
  19965.          ansi_tst();
  19966.          if (iscolor() == FALSE) {
  19967.                  fprintf(stderr, "\n\nSystem not in a color text mode.\n");
  19968.                  fprintf(stderr, "Use the MODE command to set the mode.\n");
  19969.                  exit(2);
  19970.          }
  19971.  
  19972.          /* process either batch or interactive commands */
  19973.          if (argc > 1)
  19974.                  /* batch mode processing */
  19975.                  parse(argc, argv);
  19976.          else
  19977.                  /* no command-line args -- interactive mode */
  19978.                  menumode();
  19979.  
  19980.          ANSI_ED;
  19981.          exit (0);
  19982.  } /* end main() */
  19983.  
  19984.  /*
  19985.   *      iscolor -- return TRUE if a color display system is
  19986.   *      in use and is set to one of the text modes
  19987.   */
  19988.  
  19989.  #include <local\std.h>
  19990.  #include <local\video.h>
  19991.  
  19992.  BOOLEAN
  19993.  iscolor()
  19994.  {
  19995.          getstate();
  19996.          if (Vmode != CGA_C40 || Vmode != CGA_C80)
  19997.                  return TRUE;
  19998.          return FALSE;
  19999.  }
  20000.  
  20001.  
  20002.  
  20003.  parse
  20004.  ───────────────────────────────────────────────────────────────────────────
  20005.  
  20006.  /*
  20007.   *      parse -- process a list of attribute specifications
  20008.   */
  20009.  
  20010.  #include <stdio.h>
  20011.  #include <local\ansi.h>
  20012.  #include <local\std.h>
  20013.  #include <local\ibmcolor.h>
  20014.  
  20015.  /* buffer length for string comparisons */
  20016.  #define NCHARS  3
  20017.  #define C_MASK  0x7
  20018.  
  20019.  void parse(nargs, argvec)
  20020.  int nargs;      /* number of argument vectors */
  20021.  char *argvec[]; /* pointer to the argument vector array */
  20022.  {
  20023.          int i, intensity;
  20024.          int attribute;
  20025.          POSITION pos;
  20026.          char str[NCHARS + 1];
  20027.          extern int colornum();
  20028.          extern void setattr();
  20029.  
  20030.          /* clear all attributes */
  20031.          ANSI_SGR(ANSI_NORMAL);
  20032.  
  20033.          /* look for a single attribute specification */
  20034.          if (nargs == 2) {
  20035.                  attribute = colornum(argvec[1]);
  20036.                  switch (attribute) {
  20037.                  case IBM_NORMAL:
  20038.                          palette(0, IBM_BLACK);
  20039.                          return;
  20040.                  case IBM_REVERSE:
  20041.                          ANSI_SGR(ANSI_REVERSE);
  20042.                          palette(0, IBM_WHITE);
  20043.                          return;
  20044.                  case IBM_INVISIBLE:
  20045.                          ANSI_SGR(ANSI_INVISIBLE);
  20046.                          return;
  20047.                  case IBM_BLINK:
  20048.                          ANSI_SGR(ANSI_BLINK);
  20049.                          return;
  20050.                  }
  20051.          }
  20052.  
  20053.          /* must be separate attribute specifications */
  20054.          pos = FGND;
  20055.          intensity = 0;
  20056.          for (i = 1; i < nargs; ++i) {
  20057.                  attribute = colornum(argvec[i]);
  20058.                  if (attribute == -1) {
  20059.                          ANSI_ED;
  20060.                          fprintf(stderr, "\nIllegal parameter\n");
  20061.                          exit (2);
  20062.                  }
  20063.                  if (attribute == IBM_BRIGHT) {
  20064.                          intensity = IBM_BRIGHT;
  20065.                          continue;
  20066.                  }
  20067.                  setattr(pos, attribute | intensity);
  20068.                  if (pos == FGND)
  20069.                          pos = BKGND;
  20070.                  else if (pos == BKGND)
  20071.                          pos = BDR;
  20072.                  intensity = 0;
  20073.          }
  20074.          return;
  20075.  }
  20076.  
  20077.  
  20078.  
  20079.  ibmcolor.h
  20080.  ───────────────────────────────────────────────────────────────────────────
  20081.  
  20082.  /*
  20083.   *      ibmcolor.h
  20084.   */
  20085.  
  20086.  /* basic IBM color codes */
  20087.  #define IBM_BLACK       0
  20088.  #define IBM_BLUE        1
  20089.  #define IBM_GREEN       2
  20090.  #define IBM_CYAN        3
  20091.  #define IBM_RED         4
  20092.  #define IBM_MAGENTA     5
  20093.  #define IBM_BROWN       6
  20094.  #define IBM_WHITE       7
  20095.  
  20096.  /* color modifiers */
  20097.  #define IBM_BRIGHT      8
  20098.  #define IBM_BLINK       16
  20099.  
  20100.  /* special attribute codes */
  20101.  #define IBM_NORMAL      7
  20102.  #define IBM_REVERSE     112
  20103.  #define IBM_INVISIBLE   128
  20104.  
  20105.  
  20106.  
  20107.  colornum
  20108.  ───────────────────────────────────────────────────────────────────────────
  20109.  
  20110.  /*
  20111.   *      colornum -- return the IBM number for the color
  20112.   *      presented as a string; return -1 if no match.
  20113.   */
  20114.  
  20115.  #include <stdio.h>
  20116.  #include <string.h>
  20117.  #include <local\ibmcolor.h>
  20118.  
  20119.  #define NCHARS  3
  20120.  
  20121.  int colornum(name)
  20122.  char *name;
  20123.  {
  20124.          int n;
  20125.          static struct color_st {
  20126.                  char *c_name;
  20127.                  int c_num;
  20128.          } colortab[] = {
  20129.                  "black",        IBM_BLACK,
  20130.                  "blue",         IBM_BLUE,
  20131.                  "green",        IBM_GREEN,
  20132.                  "cyan",         IBM_CYAN,
  20133.                  "red",          IBM_RED,
  20134.                  "magenta",      IBM_MAGENTA,
  20135.                  "brown",        IBM_BROWN,
  20136.                  "white",        IBM_WHITE,
  20137.                  "normal",       IBM_NORMAL,
  20138.                  "bright",       IBM_BRIGHT,
  20139.                  "light",        IBM_BRIGHT,
  20140.                  "bold",         IBM_BRIGHT,
  20141.                  "yellow",       IBM_BROWN + IBM_BRIGHT,
  20142.                  "blink",        IBM_BLINK,
  20143.                  "reverse",      IBM_REVERSE,
  20144.                  "invisible",    IBM_INVISIBLE,
  20145.                  NULL,           (-1)
  20146.          };
  20147.  
  20148.          (void) strlwr(name);
  20149.          for (n = 0; colortab[n].c_name != NULL; ++n)
  20150.                  if ((strncmp(name, colortab[n].c_name, NCHARS)) == 0)
  20151.                          return (colortab[n].c_num);
  20152.          return (-1);
  20153.  }
  20154.  
  20155.  
  20156.  
  20157.  setattr
  20158.  ───────────────────────────────────────────────────────────────────────────
  20159.  
  20160.  /*
  20161.   *      setattr -- execute an attribute update
  20162.   */
  20163.  
  20164.  #include <stdio.h>
  20165.  #include <local\ansi.h>
  20166.  #include <local\ibmcolor.h>
  20167.  
  20168.  #define C_MASK  0x7
  20169.  
  20170.  void setattr(pos, attr)
  20171.  POSITION pos;   /* attribute position */
  20172.  int attr;       /* composite attribute number (base attr | intensity) */
  20173.  {
  20174.          static int ibm2ansi[] = {
  20175.                  ANSI_BLACK, ANSI_BLUE, ANSI_GREEN, ANSI_CYAN,
  20176.                  ANSI_RED, ANSI_MAGENTA, ANSI_BROWN, ANSI_WHITE
  20177.          };
  20178.  
  20179.          switch (pos) {
  20180.          case FGND:
  20181.                  if (attr & IBM_BRIGHT)
  20182.                          ANSI_SGR(ANSI_BOLD);
  20183.                  ANSI_SGR(ibm2ansi[attr & C_MASK] + ANSI_FOREGROUND);
  20184.                  break;
  20185.          case BKGND:
  20186.                  if (attr & IBM_BRIGHT)
  20187.                          ANSI_SGR(ANSI_BLINK);
  20188.                  ANSI_SGR(ibm2ansi[attr & C_MASK] + ANSI_BACKGROUND);
  20189.                  break;
  20190.          case BDR:
  20191.                  palette(0, attr);
  20192.                  break;
  20193.          }
  20194.          return;
  20195.  }
  20196.  
  20197.  
  20198.  
  20199.  menumode
  20200.  ───────────────────────────────────────────────────────────────────────────
  20201.  
  20202.  /*
  20203.   *  menumode -- process user commands interactively
  20204.   */
  20205.  
  20206.  #include <stdio.h>
  20207.  #include <local\ansi.h>
  20208.  #include <local\ibmcolor.h>
  20209.  #include <local\keydefs.h>
  20210.  
  20211.  /* maximum color number */
  20212.  #define MAX_CNUM        15
  20213.  
  20214.  void menumode()
  20215.  {
  20216.          int ch;
  20217.          int foreground, background, border;
  20218.          extern void setattr();
  20219.          extern void sc_cmds();
  20220.  
  20221.          /* default attributes */
  20222.          foreground = IBM_WHITE;
  20223.          background = IBM_BLACK;
  20224.          border = IBM_BLACK;
  20225.  
  20226.          ANSI_SGR(ANSI_NORMAL);
  20227.          setattr(FGND, foreground);
  20228.          setattr(BKGND, background);
  20229.          ANSI_ED;
  20230.          palette(0, border);
  20231.          sc_cmds(foreground, background, border);
  20232.          while ((ch = getkey()) != K_RETURN) {
  20233.                  switch (ch) {
  20234.                  case K_F1:
  20235.                          /* decrement foreground color */
  20236.                          if (--foreground < 0)
  20237.                                  foreground = MAX_CNUM;
  20238.                          break;
  20239.                  case K_F2:
  20240.                          /* increment foreground color */
  20241.                          if (++foreground > MAX_CNUM)
  20242.                                  foreground = 0;
  20243.                          break;
  20244.                  case K_F3:
  20245.                          /* decrement background color */
  20246.                          if (--background < 0)
  20247.                                  background = MAX_CNUM;
  20248.                          break;
  20249.                  case K_F4:
  20250.                          /* increment background color */
  20251.                          if (++background > MAX_CNUM)
  20252.                                  background = 0;
  20253.                          break;
  20254.                  case K_F5:
  20255.                          /* decrement border color */
  20256.                          if (--border < 0)
  20257.                                  border = MAX_CNUM;
  20258.                          break;
  20259.                  case K_F6:
  20260.                          /* increment border color number */
  20261.                          if (++border > MAX_CNUM)
  20262.                                  border = 0;
  20263.                          break;
  20264.                  default:
  20265.                          continue;
  20266.                  }
  20267.                  ANSI_SGR(ANSI_NORMAL);
  20268.                  setattr(FGND, foreground);
  20269.                  setattr(BKGND, background);
  20270.                  palette(0, border);
  20271.                  ANSI_ED;
  20272.                  sc_cmds(foreground, background, border);
  20273.          }
  20274.          return;
  20275.  }
  20276.  
  20277.  
  20278.  
  20279.  sc_cmds
  20280.  ───────────────────────────────────────────────────────────────────────────
  20281.  
  20282.  /*
  20283.   *      sc_cmds -- display command summary
  20284.   */
  20285.  
  20286.  #include <stdio.h>
  20287.  #include <local\ansi.h>
  20288.  
  20289.  void sc_cmds(fg, bkg, bdr)
  20290.  int fg, bkg, bdr;
  20291.  {
  20292.          static char *color_xlat[] = {
  20293.                  "Black (0)", "Blue (1)", "Green (2)", "Cyan (3)",
  20294.                  "Red (4)", "Magenta (5)", "Brown (6)", "White (7)",
  20295.                  "Grey (8)", "Light blue (9)", "Light green (10)",
  20296.                  "Light cyan (11)", "Light red (12)", "Light magenta (13)",
  20297.                  "Yellow (14)", "Bright white (15)"
  20298.          };
  20299.  
  20300.          ANSI_CUP(2, 29);
  20301.          fputs("*** SetColor (SC) ***", stdout);
  20302.          ANSI_CUP(4, 17);
  20303.          fputs("Attribute  Decrement  Increment  Current Value", stdout);
  20304.          ANSI_CUP(5, 17);
  20305.          fputs("---------  ---------  ---------  -------------", stdout);
  20306.          ANSI_CUP(6, 17);
  20307.          fputs("Foreground    F1         F2", stdout);
  20308.          ANSI_CUP(7, 17);
  20309.          fputs("Background    F3         F4", stdout);
  20310.          ANSI_CUP(8, 17);
  20311.          fputs("Border        F5         F6", stdout);
  20312.          ANSI_CUP(6, 50);
  20313.          fputs(color_xlat[fg], stdout);
  20314.          ANSI_CUP(7, 50);
  20315.          fputs(color_xlat[bkg], stdout);
  20316.          ANSI_CUP(8, 50);
  20317.          fputs(color_xlat[bdr], stdout);
  20318.          ANSI_CUP(10, 17);
  20319.          fputs("Type RETURN to exit. SetColor/Version 2.2", stdout);
  20320.          return;
  20321.  } /* end sc_cmds() */
  20322.  
  20323.  
  20324.  
  20325.  makefile for the SC program
  20326.  ───────────────────────────────────────────────────────────────────────────
  20327.  
  20328.  # makefile for the SC program
  20329.  
  20330.  LINC=c:\include\local
  20331.  LLIB=c:\lib\local
  20332.  
  20333.  iscolor.obj:    iscolor.c
  20334.          msc $*;
  20335.          lib $(LLIB)\util -+$*;
  20336.  
  20337.  colornum.obj:   colornum.c $(LINC)\ansi.h $(LINC)\ibmcolor.h
  20338.          msc $*;
  20339.          lib color -+$*;
  20340.  
  20341.  sc_cmds.obj:    sc_cmds.c $(LINC)\ansi.h
  20342.          msc $*;
  20343.          lib color -+$*;
  20344.  
  20345.  menumode.obj:   menumode.c $(LINC)\ansi.h $(LINC)\ibmcolor.h
  20346.                  $(LINC)\keydefs.h
  20347.          msc $*;
  20348.          lib color -+$*;
  20349.  
  20350.  parse.obj:      parse.c $(LINC)\ansi.h $(LINC)\ibmcolor.h
  20351.          msc $*;
  20352.          lib color -+$*;
  20353.  
  20354.  setattr.obj:    setattr.c $(LINC)\ansi.h $(LINC)\ibmcolor.h
  20355.          msc $*;
  20356.          lib color -+$*;
  20357.  
  20358.  sc.obj: sc.c $(LINC)\ansi.h $(LINC)\ibmcolor.h $(LINC)\keydefs.h
  20359.          msc $*;
  20360.  
  20361.  sc.exe: sc.obj color.lib $(LLIB)\ansi.lib $(LLIB)\util.lib
  20362.          link $*, $*, nul, color $(LLIB)\ansi $(LLIB)\util $(LLIB)\bios
  20363.                  $(LLIB)\dos;
  20364.  
  20365.  
  20366.  
  20367.  useattr
  20368.  ───────────────────────────────────────────────────────────────────────────
  20369.  
  20370.  /*
  20371.   *      userattr -- set video attributes to user-specified values
  20372.   *      (DOS environment parameters) or to reasonable defaults and
  20373.   *      return success or a failure indication for bad attributes
  20374.   */
  20375.  
  20376.  #include <stdio.h>
  20377.  #include <stdlib.h>
  20378.  #include <string.h>
  20379.  #include <local\std.h>
  20380.  #include <local\ansi.h>
  20381.  #include <local\ibmcolor.h>
  20382.  
  20383.  int userattr(foreground, background, border)
  20384.  char *foreground, *background, *border;
  20385.  {
  20386.          char *s;
  20387.          static int attrset();
  20388.  
  20389.          if ((s = getenv("FGND")) == NULL)
  20390.                  s = foreground;
  20391.          if (attrset(FGND, s) == -1)
  20392.                  return FAILURE;
  20393.  
  20394.          if ((s = getenv("BKGND")) == NULL)
  20395.                  s = background;
  20396.          if (attrset(BKGND, s) == -1)
  20397.                  return FAILURE;
  20398.  
  20399.          if ((s = getenv("BORDER")) == NULL)
  20400.                  s = border;
  20401.          if (attrset(BDR, s) == -1)
  20402.                  return FAILURE;
  20403.  
  20404.          return SUCCESS;
  20405.  }
  20406.  
  20407.  /*
  20408.   *      attrset -- parse the color spec and try to set it.
  20409.   *      return 0 if OK and -1 upon error (bad color number)
  20410.   */
  20411.  static int attrset(apos, str)
  20412.  POSITION apos;
  20413.  char *str;
  20414.  {
  20415.          int attr;
  20416.          extern int colornum();
  20417.          extern void setattr();
  20418.  
  20419.          if ((attr = colornum(strtok(str, " \t"))) == IBM_BRIGHT)
  20420.                  attr |= colornum(strtok(NULL, " \t"));
  20421.          if (attr >= 0)
  20422.                  setattr(apos, attr);
  20423.          else
  20424.                  return (-1);
  20425.          return (0);
  20426.  }
  20427.  
  20428.  
  20429.  
  20430.  makefile for the VF program
  20431.  ───────────────────────────────────────────────────────────────────────────
  20432.  
  20433.  # makefile for the ViewFile (VF) program
  20434.  # (compile using "compact" memory model)
  20435.  
  20436.  LIB = c:\lib
  20437.  LLIB = c:\lib\local
  20438.  OBJS = vf_list.obj vf_dspy.obj vf_srch.obj vf_util.obj vf_cmd.obj \
  20439.          message.obj getstr.obj
  20440.  
  20441.  vf_list.obj:    vf_list.c vf.h
  20442.  
  20443.  vf_dspy.obj:    vf_dspy.c vf.h
  20444.  
  20445.  vf_srch.obj:    vf_srch.c vf.h
  20446.  
  20447.  vf_util.obj:    vf_util.c vf.h
  20448.  
  20449.  vf_cmd.obj:     vf_cmd.c vf.h
  20450.  
  20451.  getstr.obj:     getstr.c
  20452.  
  20453.  message.obj:    message.c message.h
  20454.  
  20455.  cvf.lib:                $(OBJS)
  20456.          del cvf.lib
  20457.          lib cvf +$(OBJS);
  20458.  
  20459.  vf.obj: vf.c
  20460.  
  20461.  vf.exe: vf.obj cvf.lib $(LLIB)\cutil.lib $(LLIB)\cdos.lib $(LLIB)\cbios.lib
  20462.          link $* $(LIB)\csetargv, $*, NUL, cvf $(LLIB)\cutil $(LLIB)\cdos \
  20463.          $(LLIB)\cbios;
  20464.  
  20465.  
  20466.  
  20467.  vf
  20468.  ───────────────────────────────────────────────────────────────────────────
  20469.  
  20470.  /*
  20471.   *      vf -- view a file using a full-screen window onto
  20472.   *      an in-memory text file buffer
  20473.   */
  20474.  
  20475.  #include <stdio.h>
  20476.  #include <stdlib.h>
  20477.  #include <string.h>
  20478.  #include <local\std.h>
  20479.  #include <local\video.h>
  20480.  #include "vf.h"
  20481.  
  20482.  extern int setctype(int, int);
  20483.  int Startscan, Endscan; /* cursor scan line range */
  20484.  unsigned char Attr;     /* primary display attribute */
  20485.  unsigned char Revattr;  /* reverse video for highlighting */
  20486.  unsigned char Usrattr;  /* user's original attribute */
  20487.  
  20488.  main(argc, argv)
  20489.  int argc;
  20490.  char **argv;
  20491.  {
  20492.          int ch;
  20493.          static char pgm[MAXNAME + 1] = { "vf" };
  20494.          BOOLEAN errflag;
  20495.          BOOLEAN numbers;
  20496.          int errcode;
  20497.          FILE *fp;
  20498.          extern char *optarg;
  20499.          extern int optind;
  20500.  
  20501.          void clean();
  20502.  
  20503.          /* external function prototypes */
  20504.          extern void getpname(char *, char *);
  20505.          extern void fixtabs(int);
  20506.          extern void initmsg(int, int, int, unsigned char, int);
  20507.          extern int getopt(int, char **, char *);
  20508.          extern int vf_cmd(FILE *, char *, BOOLEAN);
  20509.          extern int getctype(int *, int *, int);
  20510.  
  20511.          errcode = 0;
  20512.          getstate();
  20513.          fixtabs(TABWIDTH);
  20514.  
  20515.          /* get program name from DOS (version 3.00 and later) */
  20516.          if (_osmajor >= 3)
  20517.                  getpname(*argv, pgm);
  20518.  
  20519.          /* be sure we have needed DOS support */
  20520.          if (_osmajor < 2) {
  20521.                  fprintf(stderr, "%s requires DOS 2.00 or later\n", pgm);
  20522.                  exit(1);
  20523.          }
  20524.  
  20525.          /* process optional arguments first */
  20526.          errflag = numbers = FALSE;
  20527.          while ((ch = getopt(argc, argv, "n")) != EOF)
  20528.                  switch (ch) {
  20529.                  case 'n':
  20530.                          /* turn on line numbering */
  20531.                          numbers = TRUE;
  20532.                          break;
  20533.                  case '?':
  20534.                          /* bad option */
  20535.                          errflag = TRUE;
  20536.                          break;
  20537.                  }
  20538.  
  20539.          /* check for command-line errors */
  20540.          argc -= optind;
  20541.          argv += optind;
  20542.          if (errflag == TRUE || argc == 0) {
  20543.                  fprintf(stderr, "Usage: %s [-n] file...\n", pgm);
  20544.                  exit(1);
  20545.          }
  20546.  
  20547.          /* get current video attribute and set VF attributes */
  20548.          getstate();
  20549.          readca(&ch, &Usrattr, Vpage);  /* save user's attribute settings */
  20550.          Attr = (BLU << 4) | CYAN;      /* basic text attributes */
  20551.          Revattr = (CYAN << 4) | BLK;   /* reverse video for highlighting */
  20552.          clrscrn(Attr);
  20553.  
  20554.          /* save user's cursor shape */
  20555.          getctype(&Startscan, &Endscan, Vpage);
  20556.          setctype(MAXSCAN, MAXSCAN);
  20557.  
  20558.          /* set up the message line manager */
  20559.          initmsg(MSGROW, MSGCOL, Maxcol[Vmode] - MSGCOL, Attr, Vpage);
  20560.  
  20561.          /* display first screen page */
  20562.          putcur(0, 0, Vpage);
  20563.          putstr("ViewFile/1.0  H=Help Q=Quit Esc=Next", Vpage);
  20564.          putcur(1, 0, Vpage);
  20565.          writea(Revattr, Maxcol[Vmode], Vpage);
  20566.  
  20567.          for (; argc-- > 0; ++argv) {
  20568.                  if ((fp = fopen(*argv, "r")) == NULL) {
  20569.                       fprintf(stderr, "%s: cannot open %s -- ", pgm, *argv);
  20570.                       perror("");
  20571.                       ++errcode;
  20572.                       continue;
  20573.                  }
  20574.                  if (vf_cmd(fp, *argv, numbers) != 0)
  20575.                          break;
  20576.          }
  20577.          clean();
  20578.          exit(errcode);
  20579.  }
  20580.  
  20581.  
  20582.  /*
  20583.   *      clean -- restore the user's original conditions
  20584.   */
  20585.  
  20586.  void
  20587.  clean()
  20588.  {
  20589.          /* set screen to user's attribute */
  20590.          clrscrn(Attr);
  20591.          putcur(0, 0, Vpage);
  20592.  
  20593.          /* restore user's cursor shape */
  20594.          setctype(Startscan, Endscan);
  20595.  }
  20596.  
  20597.  
  20598.  
  20599.  vf.h
  20600.  ───────────────────────────────────────────────────────────────────────────
  20601.  
  20602.  /*
  20603.   *      vf.h -- header for ViewFile program
  20604.   */
  20605.  
  20606.  #define OVERLAP         2
  20607.  #define MAXSTR          40
  20608.  #define MSGROW          0
  20609.  #define MSGCOL          40
  20610.  #define HEADROW         1
  20611.  #define TOPROW          2
  20612.  #define NROWS           23
  20613.  #define SHIFTWIDTH      20
  20614.  #define N_NODES         256
  20615.  #define TABWIDTH        8
  20616.  #define MAXSCAN         14
  20617.  
  20618.  typedef enum {
  20619.          FORWARD, BACKWARD
  20620.  } DIRECTION;
  20621.  
  20622.  /* doubly-linked list structure */
  20623.  typedef struct dnode_st {
  20624.          struct dnode_st *d_next;
  20625.          struct dnode_st *d_prev;
  20626.          unsigned int d_lnum;    /* file-relative line number */
  20627.          unsigned short d_flags; /* miscellaneous line flags */
  20628.          char *d_line;           /* pointer to text buffer */
  20629.  } DNODE;
  20630.  
  20631.  /* line flags */
  20632.  #define STANDOUT        0x1
  20633.  
  20634.  
  20635.  
  20636.  vf_cmd
  20637.  ───────────────────────────────────────────────────────────────────────────
  20638.  
  20639.  /*
  20640.   *      vf_cmd -- ViewFile command processor
  20641.   */
  20642.  
  20643.  #include <stdio.h>
  20644.  #include <stdlib.h>
  20645.  #include <string.h>
  20646.  #include <ctype.h>
  20647.  #include <local\std.h>
  20648.  #include <local\keydefs.h>
  20649.  #include <local\video.h>
  20650.  #include "vf.h"
  20651.  #include "message.h"
  20652.  
  20653.  extern unsigned char Attr;
  20654.  
  20655.  int
  20656.  vf_cmd(fp, fname, numbers)
  20657.  FILE *fp;
  20658.  char *fname;
  20659.  BOOLEAN numbers;
  20660.  {
  20661.          register int i;         /* general index */
  20662.          unsigned int offset;    /* horizontal scroll offset */
  20663.          unsigned int n;         /* relative line number */
  20664.          int memerr;             /* flag for memory allocation errors */
  20665.          char *s, lbuf[MAXLINE]; /* input line buffer and pointer */
  20666.          int k;                  /* key code (see keydefs.h) */
  20667.          int radix = 10;         /* base for number-to-character
  20668.                                   *  conversions */
  20669.          char number[17];        /* buffer for conversions */
  20670.          int errcount = 0;       /* error counter */
  20671.          DNODE *tmp;             /* pointer to buffer control nodes */
  20672.          DIRECTION srchdir;      /* search direction */
  20673.          char *ss;               /* pointer to search string */
  20674.          static char srchstr[MAXSTR] = { "" };   /* search string buffer */
  20675.          DNODE *head;            /* pointer to starting node of
  20676.                                   *  text buffer list */
  20677.          DNODE *current;         /* pointer to the current
  20678.                                   * node (text line) */
  20679.          static DNODE *freelist; /* pointer to starting node of
  20680.                                   *  "free" list */
  20681.                                  /* initialized to 0 at runtime; retains
  20682.                                   *   value */
  20683.  
  20684.          /* function prototypes */
  20685.          static void prtshift(int, int);
  20686.          extern DNODE *vf_mklst();
  20687.          extern DNODE *vf_alloc(int);
  20688.          extern DNODE *vf_ins(DNODE *, DNODE *);
  20689.          extern DNODE *vf_del(DNODE *, DNODE *);
  20690.          extern DNODE *search(DNODE *, DIRECTION, char *);
  20691.          extern DNODE *gotoln(DNODE *);
  20692.          extern char *getxline(char *, int, FILE *);
  20693.          extern char *getsstr(char *);
  20694.          extern int clrscrn(unsigned char);
  20695.          extern void showhelp(unsigned char);
  20696.          extern void clrmsg();
  20697.          extern void vf_dspy(DNODE *, DNODE *, int, BOOLEAN);
  20698.          extern int putstr(char *, int);
  20699.          extern int writec(char, int, int);
  20700.          extern char *nlerase(char *);
  20701.  
  20702.          /* display the file name */
  20703.          offset = 0;
  20704.          putcur(HEADROW, 0, Vpage);
  20705.          writec(' ', Maxcol[Vmode], Vpage);
  20706.          putstr("File: ", Vpage);
  20707.          putstr(fname, Vpage);
  20708.  
  20709.          /* establish the text buffer */
  20710.          memerr = 0;
  20711.          if ((head = vf_mklst()) == NULL)
  20712.                  ++memerr;
  20713.          if (freelist == NULL && (freelist = vf_alloc(N_NODES)) == NULL)
  20714.                  ++memerr;
  20715.          if (memerr) {
  20716.                  clean();
  20717.                  fprintf(stderr, "Memory allocation error\n");
  20718.                  exit(1);
  20719.          }
  20720.  
  20721.          /* read the file into the buffer */
  20722.          current = head;
  20723.          n = 0;
  20724.          while ((s = getxline(lbuf, MAXLINE, fp)) != NULL) {
  20725.                  /* add a node to the list */
  20726.                  if ((freelist = vf_ins(current, freelist)) == NULL)
  20727.                          ++memerr;
  20728.                  current = current->d_next;
  20729.  
  20730.                  /* save the received text in a line buffer */
  20731.                  if ((current->d_line = strdup(nlerase(s))) == NULL)
  20732.                          ++memerr;
  20733.                  if (memerr) {
  20734.                          clean();
  20735.                          fprintf(stderr, "File too big to load\n");
  20736.                          exit(1);
  20737.                  }
  20738.                  current->d_lnum = ++n;
  20739.                  current->d_flags = 0;
  20740.          }
  20741.  
  20742.          /* show the file size as a count of lines */
  20743.          putstr(" (", Vpage);
  20744.          putstr(itoa(current->d_lnum, number, radix), Vpage);
  20745.          putstr(" lines)", Vpage);
  20746.          prtshift(offset, Vpage);
  20747.          current = head->d_next;
  20748.          vf_dspy(head, current, offset, numbers);
  20749.  
  20750.          /* process user commands */
  20751.          while ((k = getkey()) != K_ESC) {
  20752.                  clrmsg();
  20753.                  switch (k) {
  20754.                  case 'b':
  20755.                  case 'B':
  20756.                  case K_HOME:
  20757.                          current = head->d_next;
  20758.                          break;
  20759.                  case 'e':
  20760.                  case 'E':
  20761.                  case K_END:
  20762.                          current = head->d_prev;
  20763.                          i = NROWS - 1;
  20764.                          while (i-- > 0)
  20765.                                  if (current->d_prev != head->d_next)
  20766.                                          current = current->d_prev;
  20767.                          break;
  20768.                  case K_PGUP:
  20769.                  case 'u':
  20770.                  case 'U':
  20771.                          i = NROWS - OVERLAP;
  20772.                          while (i-- > 0)
  20773.                                  if (current != head->d_next)
  20774.                                          current = current->d_prev;
  20775.                          break;
  20776.                  case K_PGDN:
  20777.                  case 'd':
  20778.                  case 'D':
  20779.                          i = NROWS - OVERLAP;
  20780.                          while (i-- > 0)
  20781.                                  if (current != head->d_prev)
  20782.                                          current = current->d_next;
  20783.                          break;
  20784.                  case K_UP:
  20785.                  case '-':
  20786.                          if (current == head->d_next)
  20787.                                  continue;
  20788.                          current = current->d_prev;
  20789.                          break;
  20790.                  case K_DOWN:
  20791.                  case '+':
  20792.                          if (current == head->d_prev)
  20793.                                  continue;
  20794.                          current = current->d_next;
  20795.                          break;
  20796.                  case K_RIGHT:
  20797.                  case '>':
  20798.                  case '.':
  20799.                          if (offset < MAXLINE - SHIFTWIDTH)
  20800.                                  offset += SHIFTWIDTH;
  20801.                          prtshift(offset, Vpage);
  20802.                          break;
  20803.                  case K_LEFT:
  20804.                  case '<':
  20805.                  case ',':
  20806.                          if ((offset -= SHIFTWIDTH) < 0)
  20807.                                  offset = 0;
  20808.                          prtshift(offset, Vpage);
  20809.                          break;
  20810.                  case K_ALTG:
  20811.                  case 'g':
  20812.                  case 'G':
  20813.                          if ((tmp = gotoln(head)) == NULL)
  20814.                                  continue;
  20815.                          current = tmp;
  20816.                          break;
  20817.                  case K_ALTH:
  20818.                  case 'h':
  20819.                  case 'H':
  20820.                  case '?':
  20821.                          showhelp(Attr);
  20822.                          break;
  20823.                  case K_ALTN:
  20824.                  case 'n':
  20825.                  case 'N':
  20826.                          numbers = (numbers == TRUE) ? FALSE : TRUE;
  20827.                          break;
  20828.                  case K_ALTQ:
  20829.                  case 'q':
  20830.                  case 'Q':
  20831.                          clrscrn(Attr);
  20832.                          putcur(0, 0, Vpage);
  20833.                          return (-1);
  20834.                  case 'r':
  20835.                  case 'R':
  20836.                  case '\\':
  20837.                          srchdir = BACKWARD;
  20838.                          ss = getsstr(srchstr);
  20839.                          if (ss == NULL)
  20840.                                  /* cancel search */
  20841.                                  break;
  20842.                          if (strlen(ss) > 0)
  20843.                                  strcpy(srchstr, ss);
  20844.                          if ((tmp = search(current, srchdir,
  20845.                                  srchstr)) == NULL)
  20846.                                  continue;
  20847.                          current = tmp;
  20848.                          break;
  20849.                  case 's':
  20850.                  case 'S':
  20851.                  case '/':
  20852.                          srchdir = FORWARD;
  20853.                          ss = getsstr(srchstr);
  20854.                          if (ss == NULL)
  20855.                                  /* cancel search */
  20856.                                  break;
  20857.                          if (strlen(ss) > 0)
  20858.                                  strcpy(srchstr, ss);
  20859.                          if ((tmp = search(current, srchdir,
  20860.                                  srchstr)) == NULL)
  20861.                                  continue;
  20862.                          current = tmp;
  20863.                          break;
  20864.                  default:
  20865.                          /* ignore all other keys */
  20866.                          continue;
  20867.                  }
  20868.                  vf_dspy(head, current, offset, numbers);
  20869.          }
  20870.          clrmsg();
  20871.  
  20872.          /* release the allocated text buffer memory */
  20873.          while (head->d_next != head) {
  20874.                  /* release text buffer */
  20875.                  free(head->d_next->d_line);
  20876.  
  20877.                  /* put node back on the freelist */
  20878.                  freelist = vf_del(head->d_next, freelist);
  20879.          }
  20880.          /* release the list header node */
  20881.          free((char *)head);
  20882.  
  20883.          return (errcount);
  20884.  }
  20885.  
  20886.  /*
  20887.   *      prtshift -- display the number of columns of horizontal shift
  20888.   */
  20889.  
  20890.  #define SHFTDSP 5
  20891.  
  20892.  static void
  20893.  prtshift(amt, pg)
  20894.  int amt, pg;
  20895.  {
  20896.          char number[17];
  20897.          int radix = 10;
  20898.  
  20899.          /* clear the shift display area */
  20900.          putcur(1, Maxcol[Vmode] - 1 - SHFTDSP, pg);
  20901.          writec(' ', SHFTDSP, pg);
  20902.  
  20903.          /* display the new shift amount, if any */
  20904.          if (amt > 0) {
  20905.                  putstr(itoa(amt, number, radix), pg);
  20906.                  putstr("->", pg);
  20907.          }
  20908.  }
  20909.  
  20910.  
  20911.  
  20912.  putstr
  20913.  ───────────────────────────────────────────────────────────────────────────
  20914.  
  20915.  /*
  20916.   *      putstr -- display a character string in the
  20917.   *      prevailing video attribute and return number
  20918.   *      characters displayed
  20919.   */
  20920.  
  20921.  int
  20922.  putstr(s, pg)
  20923.  register char *s;
  20924.  int pg;
  20925.  {
  20926.          int r, c, c0;
  20927.  
  20928.          readcur(&r, &c, pg);
  20929.          for (c0 = c; *s != '\0'; ++s, ++c) {
  20930.                  putcur(r, c, pg);
  20931.                  writec(*s, 1, pg);
  20932.          }
  20933.          putcur(r, c, pg);
  20934.          return (c - c0);
  20935.  }
  20936.  
  20937.  
  20938.  
  20939.  vf_list
  20940.  ───────────────────────────────────────────────────────────────────────────
  20941.  
  20942.  /*
  20943.   *      vf_list -- linked list management functions
  20944.   */
  20945.  
  20946.  #include <stdio.h>
  20947.  #include <malloc.h>
  20948.  #include "vf.h"
  20949.  
  20950.  /*
  20951.   *      vf_mklst -- create a new list by allocating a node,
  20952.   *      making it point to itself, and setting its values
  20953.   *      to zero (appropriately cast)
  20954.   */
  20955.  
  20956.  DNODE *
  20957.  vf_mklst()
  20958.  {
  20959.          DNODE *new;
  20960.  
  20961.          new = (DNODE *)malloc(sizeof (DNODE));
  20962.          if (new != NULL) {
  20963.                  new->d_next = new;
  20964.                  new->d_prev = new;
  20965.                  new->d_lnum = new->d_flags = 0;
  20966.                  new->d_line = (char *)NULL;
  20967.          }
  20968.          return (new);
  20969.  } /* end vf_mklst() */
  20970.  
  20971.  
  20972.  /*
  20973.   *      vf_alloc -- create a pool of available nodes
  20974.   */
  20975.  
  20976.  DNODE *
  20977.  vf_alloc(n)
  20978.  int n;
  20979.  {
  20980.          register DNODE *new;
  20981.          register DNODE *tmp;
  20982.  
  20983.          /* allocate a block of n nodes */
  20984.          new = (DNODE *)malloc(n * sizeof (DNODE));
  20985.  
  20986.          /* if allocation OK, string the nodes in one direction */
  20987.          if (new != NULL) {
  20988.                  for (tmp = new; 1 + tmp - new < n; tmp = tmp->d_next)
  20989.                          tmp->d_next = tmp + 1;
  20990.                  tmp->d_next = (DNODE *)NULL;
  20991.          }
  20992.          return (new);   /* pointer to free list */
  20993.  } /* end vf_alloc() */
  20994.  
  20995.  
  20996.  /*
  20997.   *      vf_ins -- insert a node into a list after the specified node
  20998.   */
  20999.  
  21000.  DNODE *
  21001.  vf_ins(node, avail)
  21002.  DNODE *node, *avail;
  21003.  {
  21004.          DNODE *tmp;
  21005.          DNODE *vf_alloc(int);
  21006.  
  21007.          /*
  21008.           *  check freelist -- get another block of nodes
  21009.           *  if the list is almost empty
  21010.           */
  21011.          if (avail->d_next == NULL)
  21012.                  if ((avail->d_next = vf_alloc(N_NODES)) == NULL)
  21013.                          /* not enough memory */
  21014.                          return (DNODE *)NULL;
  21015.  
  21016.          /* get a node from the freelist */
  21017.          tmp = avail;
  21018.          avail = avail->d_next;
  21019.  
  21020.          /* insert the node into the list after node */
  21021.          tmp->d_prev = node;
  21022.          tmp->d_next = node->d_next;
  21023.          node->d_next->d_prev = tmp;
  21024.          node->d_next = tmp;
  21025.  
  21026.          /* point to next node in the freelist */
  21027.          return (avail);
  21028.  } /* end vf_ins() */
  21029.  
  21030.  
  21031.  /*
  21032.   *      vf_del -- delete a node from a list
  21033.   */
  21034.  
  21035.  DNODE *
  21036.  vf_del(node, avail)
  21037.  DNODE *node, *avail;
  21038.  {
  21039.          /* unlink the node from the list */
  21040.          node->d_prev->d_next = node->d_next;
  21041.          node->d_next->d_prev = node->d_prev;
  21042.  
  21043.          /* return the deleted node to the freelist */
  21044.          node->d_next = avail;
  21045.          avail = node;
  21046.  
  21047.          /* point to the new freelist node */
  21048.          return (avail);
  21049.  } /* end vf_del() */
  21050.  
  21051.  
  21052.  
  21053.  vf_dspy
  21054.  ───────────────────────────────────────────────────────────────────────────
  21055.  
  21056.  /*
  21057.   *      vf_dspy -- display a screen page
  21058.   */
  21059.  
  21060.  #include <stdio.h>
  21061.  #include <stdlib.h>
  21062.  #include <string.h>
  21063.  #include <ctype.h>
  21064.  #include <local\video.h>
  21065.  #include <local\std.h>
  21066.  #include <local\bioslib.h>
  21067.  #include "vf.h"
  21068.  
  21069.  /* number field width */
  21070.  #define NFW     8
  21071.  
  21072.  void
  21073.  vf_dspy(buf, lp, os, numbers)
  21074.  DNODE *buf;
  21075.  register DNODE *lp;
  21076.  int os;
  21077.  BOOLEAN numbers;
  21078.  {
  21079.          register int i;
  21080.          int j;
  21081.          int textwidth;
  21082.          char *cp;
  21083.          char nbuf[NFW + 1];
  21084.  
  21085.          textwidth = Maxcol[Vmode];
  21086.          if (numbers == TRUE)
  21087.                  textwidth -= NFW;
  21088.  
  21089.          for (i = 0; i < NROWS; ++i) {
  21090.                  putcur(TOPROW + i, 0, Vpage);
  21091.                  cp = lp->d_line;
  21092.                  if (numbers == TRUE) {
  21093.                          sprintf(nbuf, "%6u", lp->d_lnum);
  21094.                          putfld(nbuf, NFW, Vpage);
  21095.                          putcur(TOPROW + i, NFW, Vpage);
  21096.                  }
  21097.                  if (os < strlen(cp))
  21098.                          putfld(cp + os, textwidth, Vpage);
  21099.                  else
  21100.                          writec(' ', textwidth, Vpage);
  21101.                  if (lp == buf->d_prev) {
  21102.                          ++i;
  21103.                          break;  /* no more displayable lines */
  21104.                  }
  21105.                  else
  21106.                          lp = lp->d_next;
  21107.          }
  21108.  
  21109.          /* clear and mark any unused lines */
  21110.          for ( ; i < NROWS; ++i) {
  21111.                  putcur(i + TOPROW, 0, Vpage);
  21112.                  writec(' ', Maxcol[Vmode], Vpage);
  21113.                  writec('~', 1, Vpage);
  21114.          }
  21115.          return;
  21116.  }
  21117.  
  21118.  
  21119.  
  21120.  putfld
  21121.  ───────────────────────────────────────────────────────────────────────────
  21122.  
  21123.  /*
  21124.   *      putfld -- display a string in the prevailing
  21125.   *      video attribute while compressing runs of a
  21126.   *      single character, and pad the field to full width
  21127.   *      with spaces if necessary
  21128.   */
  21129.  
  21130.  int
  21131.  putfld(s, w, pg)
  21132.  register char *s;       /* string to write */
  21133.  int w;                  /* field width */
  21134.  int pg;                 /* screen page for writes */
  21135.  {
  21136.          int r, c, cols;
  21137.          register int n;
  21138.  
  21139.          extern int putcur(int, int, int);
  21140.          extern int readcur(int *, int *, int);
  21141.          extern int writec(unsigned char, int, int);
  21142.  
  21143.          /* get starting (current) position */
  21144.          readcur(&r, &c, pg);
  21145.  
  21146.          /* write the string */
  21147.          for (n = 0; *s != '\0' && n < w; s += cols, n += cols) {
  21148.                  putcur(r, c + n, pg);
  21149.                  /* compress runs to a single call on writec() */
  21150.                  cols = 1;
  21151.                  while (*(s + cols) == *s && n + cols < w)
  21152.                          ++cols;
  21153.                  writec(*s, cols, pg);
  21154.          }
  21155.  
  21156.          /* pad the field, if necessary */
  21157.          if (n < w) {
  21158.                  putcur(r, c + n, pg);
  21159.                  writec(' ', w - n, pg);
  21160.          }
  21161.  
  21162.          return (w - n);
  21163.  }
  21164.  
  21165.  
  21166.  
  21167.  vf_util
  21168.  ───────────────────────────────────────────────────────────────────────────
  21169.  
  21170.  /*
  21171.   *      vf_util -- utility functions for ViewFile
  21172.   */
  21173.  
  21174.  #include <stdio.h>
  21175.  #include <stdlib.h>
  21176.  #include <string.h>
  21177.  #include <local\std.h>
  21178.  #include <local\video.h>
  21179.  #include <local\keydefs.h>
  21180.  #include "vf.h"
  21181.  
  21182.  extern int Startscan, Endscan;
  21183.  
  21184.  #define NDIGITS 6
  21185.  
  21186.  /*
  21187.   *      gotoln -- jump to an absolute line number
  21188.   */
  21189.  
  21190.  DNODE *
  21191.  gotoln(buf)
  21192.  DNODE *buf;
  21193.  {
  21194.          register int ln;
  21195.          register DNODE *lp;
  21196.          char line[NDIGITS + 1];
  21197.  
  21198.          extern void showmsg(char *);
  21199.          extern char *getstr(char *, int);
  21200.  
  21201.          /* get line number from user */
  21202.          showmsg("Line number: ");
  21203.          setctype(Startscan, Endscan);                   /* cursor on */
  21204.          ln = atoi(getstr(line, NDIGITS + 1));
  21205.          setctype(MAXSCAN, MAXSCAN);                     /* cursor off */
  21206.  
  21207.          /* check boundary conditions */
  21208.          if (ln > buf->d_prev->d_lnum || ln <= 0) {
  21209.                  showmsg("Line out of range");
  21210.                  return ((DNODE *)NULL);
  21211.          }
  21212.  
  21213.          /* find the line */
  21214.          for (lp = buf->d_next; ln != lp->d_lnum; lp = lp->d_next)
  21215.                  ;
  21216.          return (lp);
  21217.  }
  21218.  
  21219.  
  21220.  /*
  21221.   *      showhelp -- display a help frame
  21222.   */
  21223.  
  21224.  #define HELPROW TOPROW + 3
  21225.  #define HELPCOL 10
  21226.  #define VBORDER 1
  21227.  #define HBORDER 2
  21228.  
  21229.  void
  21230.  showhelp(textattr)
  21231.  unsigned char textattr; /* attribute of text area */
  21232.  {
  21233.          register int i, n;
  21234.          int nlines, ncols;
  21235.          unsigned char helpattr;
  21236.          static char *help[] = {
  21237.                  "PgUp (U)        Scroll up in the file one screen page",
  21238.                  "PgDn (D)        Scroll down in the file one screen page",
  21239.                  "Up arrow (-)    Scroll up in the file one line",
  21240.                  "Down arrow (+)  Scroll down in the file one line",
  21241.                  "Right arrow (>) Scroll right by 20 columns",
  21242.                  "Left arrow (<)  Scroll left by 20 columns",
  21243.                  "Home (B)        Go to beginning of file buffer",
  21244.                  "End (E)         Go to end of file buffer",
  21245.                  "Alt-g (G)       Go to a specified line in the buffer",
  21246.                  "Alt-h (H or ?)  Display this help frame",
  21247.                  "Alt-n (N)       Toggle line-numbering feature",
  21248.                  "\\ (R)          Reverse search for a literal
  21249.                                     text string",
  21250.                  "/ (S)           Search forward for a literal text string",
  21251.                  "Esc             Next file from list (quits if none)",
  21252.                  "Alt-q (Q)       Quit",
  21253.                  "--------------------------------------------------------",
  21254.                  "            << Press a key to continue >>",
  21255.                  (char *)NULL
  21256.          };
  21257.  
  21258.          /* prepare help window */
  21259.          ncols = 0;
  21260.          for (i = 0; help[i] != (char *)NULL; ++i)
  21261.                  if ((n = strlen(help[i])) > ncols)
  21262.                          ncols = n;
  21263.          nlines = i - 1;
  21264.          --ncols;
  21265.          helpattr = (RED << 4) | BWHT;
  21266.          clrw(HELPROW - VBORDER, HELPCOL - HBORDER,
  21267.                  HELPROW + nlines + VBORDER, HELPCOL + ncols + HBORDER,
  21268.                  helpattr);
  21269.          drawbox(HELPROW - VBORDER, HELPCOL - HBORDER,
  21270.                  HELPROW + nlines + VBORDER, HELPCOL + ncols + HBORDER,
  21271.                  Vpage);
  21272.  
  21273.          /* display the help text */
  21274.          for (i = 0; help[i] != (char *)NULL; ++i) {
  21275.                  putcur(HELPROW + i, HELPCOL, Vpage);
  21276.                  putstr(help[i], Vpage);
  21277.          }
  21278.  
  21279.          /* pause until told by a keypress to proceed */
  21280.          getkey();
  21281.  
  21282.          /* restore help display area to the text attribute */
  21283.          clrw(HELPROW - VBORDER, HELPCOL - HBORDER,
  21284.                  HELPROW + nlines + VBORDER, HELPCOL + ncols + HBORDER,
  21285.                  textattr);
  21286.  }
  21287.  
  21288.  
  21289.  
  21290.  getstr
  21291.  ───────────────────────────────────────────────────────────────────────────
  21292.  
  21293.  /*
  21294.   *      getstr -- get a string from the keyboard
  21295.   */
  21296.  
  21297.  #include <stdio.h>
  21298.  #include <string.h>
  21299.  #include <local\std.h>
  21300.  #include <local\video.h>
  21301.  #include <local\keydefs.h>
  21302.  
  21303.  char *
  21304.  getstr(buf, width)
  21305.  char *buf;
  21306.  int width;
  21307.  {
  21308.          int row, col;
  21309.          char *cp;
  21310.  
  21311.          /* function prototypes */
  21312.          extern int putcur(int, int, int);
  21313.          extern int readcur(int *, int *, int);
  21314.          extern int writec(char, int, int);
  21315.          extern int getkey();
  21316.  
  21317.          /* gather keyboard input into a string buffer */
  21318.          cp = buf;
  21319.          while ((*cp = getkey()) != K_RETURN && cp - buf < width) {
  21320.                  switch (*cp) {
  21321.                  case K_CTRLH:
  21322.                          /* destructive backspace */
  21323.                          if (cp > buf) {
  21324.                                  readcur(&row, &col, Vpage);
  21325.                                  putcur(row, col - 1, Vpage);
  21326.                                  writec(' ', 1, Vpage);
  21327.                                  --cp;
  21328.                          }
  21329.                          continue;
  21330.                  case K_ESC:
  21331.                          /* cancel string input operation */
  21332.                          return (char *)NULL;
  21333.                  }
  21334.                  put_ch(*cp, Vpage);
  21335.                  ++cp;
  21336.          }
  21337.          *cp = '\0';
  21338.          return (buf);
  21339.  }
  21340.  
  21341.  
  21342.  
  21343.  message
  21344.  ───────────────────────────────────────────────────────────────────────────
  21345.  
  21346.  /*
  21347.   *      message -- routines used to display and clear
  21348.   *      messages in a reserved message area
  21349.   */
  21350.  
  21351.  #include "message.h"
  21352.  
  21353.  MESSAGE Ml;
  21354.  extern int writec(char, int, int);
  21355.  
  21356.  /*
  21357.   *      set up the message-line manager
  21358.   */
  21359.  
  21360.  void
  21361.  initmsg(r, c, w, a, pg)
  21362.  int r;                  /* message row */
  21363.  int c;                  /* message column */
  21364.  int w;                  /* width of message field */
  21365.  unsigned char a;        /* message field video attribute */
  21366.  int pg;                 /* active page for messages */
  21367.  {
  21368.          MESSAGE *mp;
  21369.          void clrmsg();
  21370.  
  21371.          mp = &Ml;
  21372.          mp->m_row = r;
  21373.          mp->m_col = c;
  21374.          mp->m_wid = w;
  21375.          mp->m_attr = a;
  21376.          mp->m_pg = pg;
  21377.          mp->m_flag = 1;
  21378.          clrmsg();
  21379.  }
  21380.  
  21381.  /*
  21382.   *      showmsg -- display a message and set the message flag
  21383.   */
  21384.  
  21385.  void
  21386.  showmsg(msg)
  21387.  char *msg;
  21388.  {
  21389.          MESSAGE *mp;
  21390.  
  21391.          mp = &Ml;
  21392.          putcur(mp->m_row, mp->m_col, mp->m_pg);
  21393.          writec(' ', mp->m_wid, mp->m_pg);
  21394.          putstr(msg, mp->m_pg);
  21395.          mp->m_flag = 1;
  21396.          return;
  21397.  }
  21398.  
  21399.  /*
  21400.   *      clrmsg -- erase the message area and reset the message flag
  21401.   */
  21402.  
  21403.  void
  21404.  clrmsg()
  21405.  {
  21406.          MESSAGE *mp;
  21407.  
  21408.          mp = &Ml;
  21409.          if (mp->m_flag != 0) {
  21410.                  putcur(mp->m_row, mp->m_col, mp->m_pg);
  21411.                  writec(' ', mp->m_wid, mp->m_pg);
  21412.                  mp->m_flag = 0;
  21413.          }
  21414.          return;
  21415.  }
  21416.  
  21417.  
  21418.  
  21419.  vf_srch
  21420.  ───────────────────────────────────────────────────────────────────────────
  21421.  
  21422.  /*
  21423.   *      vf_srch -- search functions
  21424.   */
  21425.  
  21426.  #include <stdio.h>
  21427.  #include <stdlib.h>
  21428.  #include <string.h>
  21429.  #include <local\std.h>
  21430.  #include "vf.h"
  21431.  
  21432.  /*
  21433.   *      search -- search for a literal string in the buffer
  21434.   */
  21435.  
  21436.  DNODE * search(buf, dir, str)
  21437.  DNODE *buf;
  21438.  DIRECTION dir;
  21439.  char *str;
  21440.  {
  21441.          int n;
  21442.          register DNODE *lp;
  21443.          register char *cp;
  21444.          extern void showmsg(char *);
  21445.  
  21446.          /* try to find a match -- wraps around buffer boundaries */
  21447.          n = strlen(str);
  21448.          lp = (dir == FORWARD) ? buf->d_next : buf->d_prev;
  21449.          while (lp != buf) {
  21450.                  if ((cp = lp->d_line) != NULL)  /* skip over header node */
  21451.                          while (*cp != '\n' && *cp != '\0') {
  21452.                                  if (strncmp(cp, str, n) == 0)
  21453.                                          return (lp);
  21454.                                  ++cp;
  21455.                          }
  21456.                  lp = (dir == FORWARD) ? lp->d_next : lp->d_prev;
  21457.          }
  21458.          showmsg("Not found");
  21459.          return ((DNODE *)NULL);
  21460.  }
  21461.  
  21462.  
  21463.  /*
  21464.   *      getsstr -- prompt the user for a search string
  21465.   */
  21466.  
  21467.  extern int Startscan, Endscan;
  21468.  
  21469.  char *getsstr(str)
  21470.  char *str;
  21471.  {
  21472.          char line[MAXSTR];
  21473.          char *resp;
  21474.          extern int putstr(char *, int);
  21475.          extern char *getstr(char *, int);
  21476.          extern int put_ch(char, int);
  21477.          extern void showmsg(char *);
  21478.          extern void clrmsg();
  21479.          static char prompt[] = { "Search for: " };
  21480.  
  21481.          /* get search string */
  21482.          showmsg(prompt);
  21483.          setctype(Startscan, Endscan);           /* cursor on */
  21484.          resp = getstr(line, MAXSTR - strlen(prompt));
  21485.          setctype(MAXSCAN, MAXSCAN);             /* cursor off */
  21486.          if (resp == NULL)
  21487.                  return (char *)NULL;
  21488.          if (strlen(resp) == 0)
  21489.                  return (str);
  21490.          showmsg(resp);
  21491.          return (resp);
  21492.  }
  21493.  
  21494.  
  21495.  
  21496.  getxline
  21497.  ───────────────────────────────────────────────────────────────────────────
  21498.  
  21499.  /*
  21500.   *   getxline -- get a line of text while expanding tabs,
  21501.   *   put text into an array, and return a pointer to the
  21502.   *   resulting null-terminated line
  21503.   */
  21504.  
  21505.  #include <stdio.h>
  21506.  #include <stdlib.h>
  21507.  #include <ctype.h>
  21508.  #include <local\std.h>
  21509.  
  21510.  char *getxline(buf, size, fin)
  21511.  char *buf;
  21512.  int size;
  21513.  FILE *fin;
  21514.  {
  21515.       register int ch;    /* input character */
  21516.       register char *cp;  /* character pointer */
  21517.       extern BOOLEAN tabstop(int);
  21518.  
  21519.       cp = buf;
  21520.       while (--size > 0 && (ch = fgetc(fin)) != EOF) {
  21521.               if (ch == '\n') {
  21522.                       *cp++ = ch;
  21523.                       break;
  21524.            }
  21525.            else if (ch == '\t')
  21526.                    do {
  21527.                            *cp = ' ';
  21528.                    } while (--size > 0 && (tabstop(++cp - buf) == FALSE));
  21529.            else
  21530.                    *cp++ = ch & ASCII;
  21531.       }
  21532.       *cp = '\0';
  21533.       return ((ch == EOF && cp == buf) ? NULL : buf);
  21534.  }
  21535.  
  21536.  
  21537.  
  21538.  
  21539.  Index
  21540.  
  21541.  
  21542.  Page numbers for illustrations are in italics
  21543.  
  21544.  
  21545.  Symbols
  21546.  ──────────────────────────────────────────────────────────────────────
  21547.  #
  21548.  %var%
  21549.  / (DOS option switch)
  21550.  \ (DOS pathname separator)
  21551.  \\ (C escape)
  21552.  \include
  21553.  \include\local
  21554.  \lib
  21555.  \lib\local
  21556.  \x
  21557.  |
  21558.  
  21559.  
  21560.  A
  21561.  ──────────────────────────────────────────────────────────────────────
  21562.  Advanced MS-DOS
  21563.  affirm()
  21564.  Alt key status
  21565.  American National Standards Institute (ANSI)
  21566.     basics
  21567.     color attributes
  21568.     control sequences
  21569.     device driver (see ANSI.SYS)
  21570.     display memory
  21571.     local library summary
  21572.     proposed standard for C language
  21573.  ansi_cpr()
  21574.  ANSI_CUP
  21575.  ANSI_DSR
  21576.  ansi.h
  21577.  ANSI_SGR
  21578.  ANSI.SYS
  21579.     control codes
  21580.        numeric parameters
  21581.        selective parameters
  21582.     installing
  21583.     interface package
  21584.     pros and cons of
  21585.     SetColor program
  21586.     set graphic rendition (SGR)
  21587.     user-attribute function
  21588.  ansi_tst()
  21589.     SetColor program
  21590.  arguments
  21591.     argc
  21592.     argv
  21593.     fixed/variable-list
  21594.     pathname and type
  21595.     video row and col
  21596.  ASCII codes. See also displaying non-ASCII text
  21597.               and ANSI controls
  21598.     block characters
  21599.     character code data bytes
  21600.     control character table
  21601.     for Epson MX/FX printer control
  21602.     extended
  21603.     line-drawing characters
  21604.     printable character table
  21605.     video attributes
  21606.  asctime()
  21607.  Aspen Scientific
  21608.  Assembler
  21609.  AT&T
  21610.     PC6300
  21611.     UNIX System V
  21612.  atoi()
  21613.  attributes. See characters and attributes
  21614.  AUTOEXEC.BAT file
  21615.  automatic program configuration
  21616.     printer control functions
  21617.     selection functions
  21618.     using configuration files
  21619.     using the program name
  21620.  
  21621.  
  21622.  B
  21623.  ──────────────────────────────────────────────────────────────────────
  21624.  batch processing
  21625.     SetColor program
  21626.  bdos()
  21627.  beep()
  21628.  binary numbers, converting
  21629.  bios.lib
  21630.  bioslib.h
  21631.  BIOS library routines
  21632.     demonstration program
  21633.     equipment determination functions
  21634.     keyboard status
  21635.     library summary
  21636.     makefile
  21637.     video access
  21638.  bios.mk
  21639.  blink bit
  21640.  block characters
  21641.  block-copy routine
  21642.     synchronized
  21643.  blocking read
  21644.  box-drawing functions
  21645.  box.h header file
  21646.  buffer(s)
  21647.     input/output
  21648.     screen (see buffered screen interface; screen access routines)
  21649.     text (see text buffer)
  21650.  buffered screen interface
  21651.     box-drawing functions
  21652.     buffer management functions
  21653.     general window functions
  21654.     interface package
  21655.     screen buffer library
  21656.  byte2hex()
  21657.  
  21658.  
  21659.  C
  21660.  ──────────────────────────────────────────────────────────────────────
  21661.  call by reference
  21662.  call by value
  21663.  Caps Lock key status
  21664.  CAT program
  21665.  cells, buffer
  21666.  character I/O functions
  21667.     characters and attributes.
  21668.     See also ASCII codes; SetColor (SC) program
  21669.     display memory
  21670.     reading from screen buffers
  21671.     writing to windows
  21672.  char data type
  21673.  C language
  21674.     DOS-to-, (see DOS-to-C connection)
  21675.     language details
  21676.     Microsoft, version 4.00
  21677.     PC interfaces (see PC operating system interfaces)
  21678.     standard routines (see standard libraries)
  21679.     standards/compatibility
  21680.     technical highlights
  21681.     user interface (see user interface)
  21682.  CL control program
  21683.  clean()
  21684.  clearerr()
  21685.  clock. See PC timer
  21686.  close()
  21687.  clrmsg()
  21688.  clrprnt()
  21689.  clrscrn()
  21690.  clrw()
  21691.  CodeView
  21692.  color graphics adapter (CGA)
  21693.     display adapter basics
  21694.     memory interference
  21695.  colornum()
  21696.  color numbers
  21697.  colors. See SetColor (SC) program
  21698.  command-line processing
  21699.     accessing the command line
  21700.     ambiguous filenames
  21701.     arguments
  21702.     pointers and indirection
  21703.     program name
  21704.     standardizing, to improve user interface
  21705.  compact memory model
  21706.  compatibility. See portability
  21707.  compiler
  21708.     Microsoft version 4.00
  21709.     other compilers for DOS
  21710.  Computer Innovations C86
  21711.  configuration. See automatic program configuration
  21712.  configuration files
  21713.  confirm()
  21714.  conversion functions, numbers
  21715.  cpblk()
  21716.     screen updates
  21717.     ST program
  21718.  CP program
  21719.  The C Programming Language
  21720.  "C Reference Manual"
  21721.  CR/LF
  21722.  ctime()
  21723.  Ctrl-Break
  21724.  Ctrl key status
  21725.  Ctrl-Z
  21726.  Curses
  21727.  cursor
  21728.     control functions
  21729.     position report
  21730.     restoring
  21731.     split
  21732.     window
  21733.  cursor.c
  21734.  cursor.mk
  21735.  CURSOR program
  21736.     makefile
  21737.     output
  21738.     pseudocode
  21739.     source code
  21740.  
  21741.  
  21742.  D
  21743.  ──────────────────────────────────────────────────────────────────────
  21744.  DEBUG program. See also DUMP program
  21745.  debugging programs. See also CodeView
  21746.  decimal numbers, converting
  21747.  delay()
  21748.  device driver. See ANSI.SYS
  21749.  device status report
  21750.  directories
  21751.     disk
  21752.     list (see LS utility program)
  21753.     pathname (see PWD utility program)
  21754.  direct screen access
  21755.     alternative solutions
  21756.     display adapter basics
  21757.     programming considerations
  21758.  disk routine numbers, BIOS
  21759.  display adapter basics
  21760.  displaying non-ASCII text
  21761.     conversion functions
  21762.     DUMP
  21763.     SHOW
  21764.  display memory
  21765.  display system type, determining
  21766.  do_rm()
  21767.  DOS
  21768.     compared to UNIX and XENIX
  21769.     error messages
  21770.     library (see DOS library routines)
  21771.     link (see LINK (3.51) program)
  21772.     -to-C (see DOS-to-C connection)
  21773.     version number
  21774.  DOS environment
  21775.     pointer
  21776.     reserved space
  21777.     setting values correctly
  21778.     variables
  21779.  dos.h
  21780.  dos.lib
  21781.  doslib.h
  21782.  DOS library routines
  21783.     demonstration program
  21784.     DOS version number
  21785.     keyboard functions
  21786.     summary
  21787.  DOS-to-C connection
  21788.     command-line processing
  21789.     DOS environment variables
  21790.     input/output redirection and piping
  21791.  double buffering
  21792.  drawbox()
  21793.  drvpath()
  21794.  DSPYTYPE program
  21795.  dump.c
  21796.  dump.mk
  21797.  DUMP program
  21798.     makefile
  21799.     manual page
  21800.     output
  21801.     pseudocode
  21802.     source code
  21803.  Duncan, Ray
  21804.  
  21805.  
  21806.  E
  21807.  ──────────────────────────────────────────────────────────────────────
  21808.  ECHO command
  21809.  EDITOR
  21810.  EDLIN program
  21811.  ega_info()
  21812.  element, in display memory
  21813.  enhanced graphics adapter (EGA)
  21814.  enum type specifier
  21815.  environment pointer
  21816.  environment variables
  21817.  ENVSIZE program
  21818.  EOF (end of file) indicator
  21819.  epoch time
  21820.  Epson dot-matrix printer
  21821.     interface routines
  21822.     MX program
  21823.  equipchk()
  21824.  equip.h
  21825.  equipment
  21826.     determination functions
  21827.     standards
  21828.  ERASE/DEL command
  21829.  errno
  21830.  error functions
  21831.     ambiguous return values
  21832.     getreply()
  21833.     operating system interrupts
  21834.  ERRORLEVEL
  21835.  exception handling
  21836.  exec()
  21837.  execl()
  21838.  EXEMOD
  21839.  EXEPACK
  21840.  exit()
  21841.  external storage for large files
  21842.  
  21843.  
  21844.  F
  21845.  ──────────────────────────────────────────────────────────────────────
  21846.  fatal()
  21847.  fclose()
  21848.  fcloseall()
  21849.  fconfig()
  21850.  fcopy()
  21851.  feof()
  21852.  ferror()
  21853.  fgetc()
  21854.  fgetchar()
  21855.  fgets()
  21856.  file(s)
  21857.     basic functions
  21858.     configuration (see configuration files)
  21859.     names (see filenames, ambiguous)
  21860.     network sharing
  21861.     nontext (see displaying non-ASCII text)
  21862.     printing (see file printing)
  21863.     utilities (see file utilities)
  21864.     viewing (see ViewFile (VF) program)
  21865.  filenames, ambiguous
  21866.     wildcards
  21867.  file printing
  21868.     file handling
  21869.     option handling
  21870.     possible enhancements to
  21871.     program maintenance
  21872.     program specification
  21873.  file utilities
  21874.  filter
  21875.     DUMP as
  21876.     PR as
  21877.  first_fm()
  21878.  fit()
  21879.  fixtabs()
  21880.  floating point
  21881.  fopen()
  21882.  FORTRAN
  21883.  fprintf()
  21884.  fputc()
  21885.  fputs()
  21886.  free()
  21887.  fseek()
  21888.  function(s). See also names of individual functions
  21889.     classified by environment
  21890.     error
  21891.     file and character I/O
  21892.     keyboard
  21893.     library management of
  21894.     local library summary
  21895.     number conversion
  21896.     printer control
  21897.     process control
  21898.     selection
  21899.     suffix meanings
  21900.     system interface
  21901.     time/date
  21902.     timing
  21903.     user attribute
  21904.     window
  21905.  function keys
  21906.  
  21907.  
  21908.  G
  21909.  ──────────────────────────────────────────────────────────────────────
  21910.  game port
  21911.  getc()
  21912.  getch()
  21913.  getchar()
  21914.  getctype()
  21915.  getcwd()
  21916.  getdrive()
  21917.  getenv()
  21918.  getkey()
  21919.  getname()
  21920.  getopt()
  21921.     pseudocode
  21922.     source
  21923.  getpname()
  21924.  getreply()
  21925.  getstate()
  21926.     to determine video mode
  21927.  getstr()
  21928.  getticks()
  21929.  getxline()
  21930.  global variables
  21931.  gmtime()
  21932.  gotoln()
  21933.  graphic rendition
  21934.  Greenwich mean time (GMT)
  21935.  
  21936.  
  21937.  H
  21938.  ──────────────────────────────────────────────────────────────────────
  21939.  hardware cursor
  21940.  hexadecimal character constants
  21941.  hexadecimal numbers, converting
  21942.  hex.c
  21943.  hexdump()
  21944.  horizontal sweep signal
  21945.  
  21946.  
  21947.  I
  21948.  ──────────────────────────────────────────────────────────────────────
  21949.  ibmcolor.h
  21950.  initmsg()
  21951.  input/output (I/O)
  21952.     buffered
  21953.     character functions
  21954.     redirection and piping
  21955.        with TEE
  21956.     terminating
  21957.     user input
  21958.  Ins key status
  21959.  intdos()
  21960.  intdosx()
  21961.  int86()
  21962.  int86x()
  21963.  interactive mode, SETCOLOR program
  21964.  interlanguage calls
  21965.  interrupts
  21966.     BIOS
  21967.     PC operating system
  21968.  interval()
  21969.  intra-application communications area (ICA)
  21970.  iscolor()
  21971.  
  21972.  
  21973.  K
  21974.  ──────────────────────────────────────────────────────────────────────
  21975.  kbd_stat()
  21976.  kbhit()
  21977.  Kernighan, Brian. See also Ritchie, Dennis M.
  21978.  keybdlib.h
  21979.  keyboard
  21980.     BIOS routine numbers
  21981.     functions (DOS)
  21982.     status
  21983.     stdin stream
  21984.  keydefs.h
  21985.  keyready()
  21986.  
  21987.  
  21988.  L
  21989.  ──────────────────────────────────────────────────────────────────────
  21990.  language(s)
  21991.     details, Microsoft C
  21992.     interlanguage calls
  21993.  last_ch()
  21994.  Lattice-MS-DOS C Compiler
  21995.  LF (linefeed) character
  21996.  LIB, object-file maintainer
  21997.  libraries. See local library summary; screen buffer library;
  21998.        standard libraries
  21999.  linebuf.h
  22000.  line-drawing characters
  22001.  lines()
  22002.  LINK (3.51) program
  22003.  list directories. See LS utility program
  22004.  local library summary
  22005.     ANSI
  22006.     BIOS
  22007.     DOS
  22008.     screen buffer
  22009.     utility
  22010.  localtime()
  22011.  ls.c
  22012.  ls_dirx()
  22013.  lseek()
  22014.  ls_fcomp()
  22015.  ls_list()
  22016.  ls.mk
  22017.  ls_multi()
  22018.  ls_single()
  22019.  LS utility program
  22020.     makefile
  22021.     manual page
  22022.     pseudocode
  22023.     source code
  22024.  
  22025.  
  22026.  M
  22027.  ──────────────────────────────────────────────────────────────────────
  22028.  macros. See function(s)
  22029.  "magic cookies" attribute positions
  22030.  mainmenu()
  22031.  MAKE command program maintainer. See also makefile
  22032.  makefile
  22033.     ANSI
  22034.     BIOS
  22035.     CURSOR
  22036.     DUMP
  22037.     LS
  22038.     MX
  22039.     PR
  22040.     PRTSTR
  22041.     REPLAY
  22042.     sbuf.lib
  22043.     SetColor
  22044.     SHOW
  22045.     SHOWTABS
  22046.     ST
  22047.     ViewFile
  22048.  malloc()
  22049.  Mark Williams C Programming System (MWC)
  22050.  MASM macro assembler
  22051.  math coprocessor
  22052.  math libraries, multiple
  22053.  memchk()
  22054.  memcpy()
  22055.  memory. See also buffer(s)
  22056.     checking, to determine screen type
  22057.     compact model
  22058.     determining total
  22059.     display (see display memory)
  22060.     intra-application communications area (ICA)
  22061.     models
  22062.  memsize()
  22063.  message.c
  22064.  message.h
  22065.  Microsoft C, version 4.00
  22066.     language details
  22067.     technical highlights
  22068.  Microsoft C Compiler Library Reference Manual
  22069.  mkslist()
  22070.     pseudocode
  22071.  MODE command
  22072.  modeflag
  22073.  monochrome adapter
  22074.  MORE
  22075.  movedata()
  22076.  MSC control program
  22077.  MULTICOLUMN variable
  22078.  mx.mk
  22079.  MX program
  22080.     makefile
  22081.     manual page
  22082.     source code
  22083.  
  22084.  
  22085.  N
  22086.  ──────────────────────────────────────────────────────────────────────
  22087.  next_fm()
  22088.  NL (newline) character
  22089.  nlerase()
  22090.  non-ASCII text. See displaying non-ASCII text
  22091.  nonblocking read
  22092.  NOTES program
  22093.  NOTES2 program
  22094.  _nullcheck()
  22095.  numbers, conversion functions
  22096.  Num Lock key status
  22097.  
  22098.  
  22099.  O
  22100.  ──────────────────────────────────────────────────────────────────────
  22101.  open()
  22102.  operating system
  22103.     interrupts
  22104.     PC interfaces (see PC operating system interfaces)
  22105.  optarg
  22106.  opterr
  22107.  Optimizing C86
  22108.  optind
  22109.  option flag(s)
  22110.  option switches
  22111.  _osmajor
  22112.  _osminor
  22113.  OUTBUF
  22114.  overlays
  22115.  
  22116.  
  22117.  P
  22118.  ──────────────────────────────────────────────────────────────────────
  22119.  page(s)
  22120.     active/visual
  22121.     "flipping"
  22122.     formatting/copying text
  22123.     layout
  22124.  Pagelist
  22125.  palette()
  22126.  parse()
  22127.  Pascal
  22128.  pathname
  22129.     display
  22130.     and type argument
  22131.  PC operating system interfaces
  22132.     BIOS library routines
  22133.     demonstration program (CURSOR)
  22134.     DOS library routines
  22135.     functions
  22136.     library management issues
  22137.  PC timer
  22138.  p_dest
  22139.  perror()
  22140.  pointer(s)
  22141.     environment
  22142.     and indirection
  22143.  PolyMake
  22144.  portability
  22145.  P_OVERLAY
  22146.  pr.c
  22147.  pr_cpy()
  22148.     pseudocode
  22149.     source code
  22150.  preprocessor directives
  22151.  pr_file()
  22152.  pr_gcnf()
  22153.     pseudocode
  22154.     source code
  22155.  pr_getln()
  22156.  pr_help()
  22157.  PRINT
  22158.  printer control functions
  22159.     MX program
  22160.     PRTSTR program
  22161.  printer.c
  22162.  printer.h
  22163.  printf()
  22164.  print.h
  22165.  printing files. See file printing
  22166.  print working directory path. See PWD utility program
  22167.  prlib.lib
  22168.  pr_line()
  22169.  pr.mk
  22170.  pr.obj
  22171.  process control functions
  22172.  program development
  22173.     development cycle
  22174.     guiding principles
  22175.     local environment
  22176.  programmable peripheral interface (PPI)
  22177.  programming languages
  22178.     interlanguage calls
  22179.  program name
  22180.     using, to alter program behavior
  22181.  program size
  22182.  PR program
  22183.     calling hierarchy
  22184.     file handling
  22185.     as filter
  22186.     folded lines
  22187.     formatting/copying text pages
  22188.     makefile
  22189.     manual page
  22190.     option handling
  22191.     page layout
  22192.     possible enhancements
  22193.     program maintenance
  22194.     as a sink
  22195.     source code
  22196.     specification
  22197.  prtshift()
  22198.  prtstr.c
  22199.  prtstr.mk
  22200.  PRTSTR program
  22201.     makefile
  22202.     manual page
  22203.     source code
  22204.  pseudocode
  22205.     for configuration layers
  22206.     CURSOR
  22207.     DUMP
  22208.     getopt()
  22209.     mkslist()
  22210.     pr_cpy()
  22211.     pr_gcnf()
  22212.     RM
  22213.     save_range
  22214.     showit()
  22215.     TEE
  22216.     TOUCH
  22217.  putc()
  22218.  put_ch()
  22219.  putcur()
  22220.  putenv()
  22221.  putfld()
  22222.  putstr()
  22223.  pwd.c
  22224.  PWD utility program
  22225.     manual page
  22226.     source code
  22227.  
  22228.  
  22229.  Q
  22230.  ──────────────────────────────────────────────────────────────────────
  22231.  qsort()
  22232.  
  22233.  
  22234.  R
  22235.  ──────────────────────────────────────────────────────────────────────
  22236.  read
  22237.     blocking
  22238.     characters/attributes from screen buffers
  22239.     from the DOS environment
  22240.     nonblocking
  22241.     video characters
  22242.  read()
  22243.  readca()
  22244.  readcur()
  22245.  readdot()
  22246.  realloc()
  22247.  REGION
  22248.  registers
  22249.     structures
  22250.     word/byte
  22251.  remove files. See RM utility program
  22252.  replay.c
  22253.  replay.mk
  22254.  REPLAY program
  22255.     makefile
  22256.  return values, ambiguous
  22257.  rewind()
  22258.  Ritchie, Dennis M.. See also Kernighan, Brian
  22259.  rm.c
  22260.  RM utility program
  22261.     manual page
  22262.     pseudocode
  22263.     source code
  22264.  routines. See function(s)
  22265.  rows and columns, video
  22266.  RUN_ONCE program
  22267.  
  22268.  
  22269.  S
  22270.  ──────────────────────────────────────────────────────────────────────
  22271.  SAMPLE.BAT file
  22272.  save_range()
  22273.     pseudocode
  22274.  sb_box()
  22275.  sb_fill()
  22276.  sb_init()
  22277.  sb_move()
  22278.  sb_new()
  22279.  sb_putc()
  22280.  sb_puts()
  22281.  sb_ra()
  22282.  sb_rc()
  22283.  sb_rca()
  22284.  sb_read()
  22285.  sb_scrl()
  22286.  sb_set_scrl()
  22287.  sb_show()
  22288.  sb_test.c
  22289.  SB_TEST driver program
  22290.     header file
  22291.     makefile
  22292.     source code
  22293.  sb_test.mk
  22294.  sbuf.h
  22295.  sbuf.lib
  22296.     makefile
  22297.  sb_wa()
  22298.  sb_wc()
  22299.  sb_wca()
  22300.  sb_write()
  22301.  scanf()
  22302.  sc.c
  22303.  sc_cmds()
  22304.  Schinnell, Rich
  22305.  sc.mk
  22306.  screen(s)
  22307.     access (see buffered screen interface;
  22308.        screen access routines; video access)
  22309.     color/graphics adapter basics
  22310.     determining type of
  22311.     scrolling/clearing commands
  22312.     stderr/stdout streams
  22313.     updates
  22314.     updates with cpblk()
  22315.     user input
  22316.  screen access routines. See also buffered screen interface
  22317.     demonstration program ST
  22318.     design considerations
  22319.     determining display system type
  22320.     direct screen access
  22321.     double buffering
  22322.     synchronized block-copy routine
  22323.  screen buffer library
  22324.  scroll()
  22325.  scrolling windows/screens
  22326.     commands
  22327.  Scroll Lock key status
  22328.  search()
  22329.  segread()
  22330.  select.c
  22331.  selected()
  22332.  selection functions
  22333.  SELECT module routines
  22334.  setargv()
  22335.  setattr()
  22336.  SetColor (SC) program
  22337.     batch mode
  22338.     function keys in
  22339.     interactive mode
  22340.        makefile
  22341.     manual page
  22342.     pseudocode
  22343.     source code
  22344.     using
  22345.  SET command
  22346.  setctype()
  22347.  setdta()
  22348.  _setenvp()
  22349.  SETENV program
  22350.  setfont()
  22351.  setfreq()
  22352.  set graphic rendition (SGR)
  22353.  SETMYDIR program
  22354.  setpage()
  22355.  setprnt()
  22356.  setvmode()
  22357.  sflag
  22358.  shift key status
  22359.  show.c
  22360.  SHOWENV program
  22361.  showhelp()
  22362.  showit()
  22363.  show.mk
  22364.  showmsg()
  22365.  SHOW program
  22366.     makefile
  22367.     manual page
  22368.     pseudocode
  22369.     source code
  22370.  SHOWTABS program
  22371.     makefile
  22372.     output
  22373.  signal()
  22374.  signal catching
  22375.  sink. See also filter
  22376.  Slist
  22377.  sorting
  22378.  sound()
  22379.  sound.c
  22380.  sound generation
  22381.  sound.h
  22382.  SOUNDS program
  22383.  spaces()
  22384.  spawn()
  22385.  spawnvp()
  22386.  SPKR program
  22387.  standard data streams
  22388.  standard libraries. See also function(s)
  22389.     basic file and character I/O functions
  22390.     BIOS routines
  22391.     DOS routines
  22392.     exception handling
  22393.     functions management
  22394.     LIB
  22395.     other library functions
  22396.     process control functions
  22397.     reasons for using
  22398.     system interface functions
  22399.     time functions
  22400.  st.c
  22401.  stderr (standard error) stream
  22402.  std.h
  22403.  stdin (standard input) stream
  22404.  stdio.h
  22405.  stdlib.h
  22406.  stdout (standard output) stream
  22407.  st.mk
  22408.  ST (screen test) program
  22409.     makefile
  22410.     source code
  22411.  strdup()
  22412.  string(s)
  22413.     creating time
  22414.     into windows
  22415.  strlwr()
  22416.  strtok()
  22417.  struct BYTEREGS
  22418.  struct pr_st
  22419.  struct tm
  22420.  structure handling
  22421.  struct WORDREGS
  22422.  strupr()
  22423.  support tools for C compiler
  22424.  swap_int()
  22425.  sweep.c
  22426.  switches. See option switches
  22427.  SYMDEB program
  22428.  system()
  22429.  
  22430.  
  22431.  T
  22432.  ──────────────────────────────────────────────────────────────────────
  22433.  tabs.c
  22434.  tabstop()
  22435.  tab stops, setting
  22436.  tee.c
  22437.  TEE utility program
  22438.     manual page
  22439.     pseudocode
  22440.     source code
  22441.  Termcap
  22442.  testing programs
  22443.  text buffer
  22444.     management
  22445.  TICKRATE
  22446.  time()
  22447.  time conversions
  22448.     creating strings
  22449.     time zones
  22450.  timedata.c
  22451.  TIMEDATA program
  22452.  time/date functions
  22453.  time delays
  22454.  time.h
  22455.  TIMER program
  22456.     manual page
  22457.  timing functions
  22458.  audible feedback
  22459.  PC timer and time delays
  22460.  TONE program
  22461.  tools.ini
  22462.  touch.c
  22463.  TOUCH utility program
  22464.     manual page
  22465.     pseudocode
  22466.     source code
  22467.  TSTREPLY program
  22468.  tstsel.c
  22469.  TSTSEL program
  22470.     output
  22471.  typedef
  22472.  TZ
  22473.  tzset()
  22474.  
  22475.  
  22476.  U
  22477.  ──────────────────────────────────────────────────────────────────────
  22478.  Universal time (UT)
  22479.  "UNIX 1984 Standard"
  22480.  UNIX operating system
  22481.     command-line processing
  22482.     compared to DOS
  22483.     control program (cc)
  22484.     ctime()
  22485.     getopt() error
  22486.     MAKE program
  22487.     portability
  22488.     standardization of
  22489.     starting optional arguments (-)
  22490.     System V
  22491.  unlink()
  22492.  UPDATEMODE
  22493.  userattr()
  22494.  user interface
  22495.     command-line processing
  22496.     timing functions
  22497.     user input
  22498.  utility programs
  22499.     library summary
  22500.  util.lib
  22501.  
  22502.  
  22503.  V
  22504.  ──────────────────────────────────────────────────────────────────────
  22505.  variables
  22506.     DOS environment
  22507.     global (see global variables)
  22508.     time
  22509.  vartabs()
  22510.  ver()
  22511.  vf_alloc()
  22512.  vf_cmd()
  22513.  vf_cmd.c
  22514.  vf_del()
  22515.  vf_dspy()
  22516.  vf.h
  22517.  vf_ins()
  22518.  vflag
  22519.  vf_list.c
  22520.  vf.mk
  22521.  vf_mklst()
  22522.  vf_srch.c
  22523.  vf_util.c
  22524.  video access. See also screen(s)
  22525.  video attributes
  22526.  video.h
  22527.  video routine numbers, BIOS
  22528.  ViewFile (VF) program
  22529.     external storage
  22530.     features
  22531.     header file
  22532.     help frame
  22533.     implementation details
  22534.     makefile
  22535.     manual page
  22536.     source code
  22537.  void
  22538.  
  22539.  
  22540.  W
  22541.  ──────────────────────────────────────────────────────────────────────
  22542.  warn()
  22543.  wflag
  22544.  wildcard characters in ambiguous filenames
  22545.  window(s)
  22546.     general functions
  22547.     REGION
  22548.  word2hex()
  22549.  write
  22550.     characters/attributes to windows
  22551.     to the DOS environment
  22552.     video characters
  22553.  write()
  22554.  writea()
  22555.  writec()
  22556.  writeca()
  22557.  writedot()
  22558.  writemsg()
  22559.  writestr()
  22560.  writetty()
  22561.  
  22562.  
  22563.  X
  22564.  ──────────────────────────────────────────────────────────────────────
  22565.  XENIX
  22566.        compared to DOS
  22567.        control program (cc)
  22568.        getopt() error
  22569.        portability
  22570.  
  22571.