Initial revision
authorJim Meyering <jim@meyering.net>
Sun, 1 Nov 1992 05:44:29 +0000 (05:44 +0000)
committerJim Meyering <jim@meyering.net>
Sun, 1 Nov 1992 05:44:29 +0000 (05:44 +0000)
44 files changed:
lib/alloca.c [new file with mode: 0644]
lib/basename.c [new file with mode: 0644]
lib/error.c [new file with mode: 0644]
lib/getdate.y [new file with mode: 0644]
lib/gethostname.c [new file with mode: 0644]
lib/getopt.c [new file with mode: 0644]
lib/getopt.h [new file with mode: 0644]
lib/getopt1.c [new file with mode: 0644]
lib/getugroups.c [new file with mode: 0644]
lib/getusershell.c [new file with mode: 0644]
lib/mktime.c [new file with mode: 0644]
lib/posixtm.y [new file with mode: 0644]
lib/putenv.c [new file with mode: 0644]
lib/stime.c [new file with mode: 0644]
lib/strcspn.c [new file with mode: 0644]
lib/strftime.c [new file with mode: 0644]
lib/strtod.c [new file with mode: 0644]
lib/xmalloc.c [new file with mode: 0644]
old/sh-utils/ChangeLog [new file with mode: 0644]
old/sh-utils/NEWS [new file with mode: 0644]
src/basename.c [new file with mode: 0644]
src/date.c [new file with mode: 0644]
src/dirname.c [new file with mode: 0644]
src/echo.c [new file with mode: 0644]
src/env.c [new file with mode: 0644]
src/expr.c [new file with mode: 0644]
src/groups.sh [new file with mode: 0755]
src/id.c [new file with mode: 0644]
src/logname.c [new file with mode: 0644]
src/nice.c [new file with mode: 0644]
src/nohup.sh [new file with mode: 0755]
src/pathchk.c [new file with mode: 0644]
src/printenv.c [new file with mode: 0644]
src/printf.c [new file with mode: 0644]
src/sleep.c [new file with mode: 0644]
src/stty.c [new file with mode: 0644]
src/su.c [new file with mode: 0644]
src/tee.c [new file with mode: 0644]
src/test.c [new file with mode: 0644]
src/tty.c [new file with mode: 0644]
src/uname.c [new file with mode: 0644]
src/who.c [new file with mode: 0644]
src/whoami.c [new file with mode: 0644]
src/yes.c [new file with mode: 0644]

diff --git a/lib/alloca.c b/lib/alloca.c
new file mode 100644 (file)
index 0000000..c1ff222
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+       alloca -- (mostly) portable public-domain implementation -- D A Gwyn
+
+       last edit:      86/05/30        rms
+          include config.h, since on VMS it renames some symbols.
+          Use xmalloc instead of malloc.
+
+       This implementation of the PWB library alloca() function,
+       which is used to allocate space off the run-time stack so
+       that it is automatically reclaimed upon procedure exit, 
+       was inspired by discussions with J. Q. Johnson of Cornell.
+
+       It should work under any C implementation that uses an
+       actual procedure stack (as opposed to a linked list of
+       frames).  There are some preprocessor constants that can
+       be defined when compiling for your specific system, for
+       improved efficiency; however, the defaults should be okay.
+
+       The general concept of this implementation is to keep
+       track of all alloca()-allocated blocks, and reclaim any
+       that are found to be deeper in the stack than the current
+       invocation.  This heuristic does not reclaim storage as
+       soon as it becomes invalid, but it will do so eventually.
+
+       As a special case, alloca(0) reclaims storage without
+       allocating any.  It is a good idea to use alloca(0) in
+       your main control loop, etc. to force garbage collection.
+*/
+#ifndef lint
+static char    SCCSid[] = "@(#)alloca.c        1.1";   /* for the "what" utility */
+#endif
+
+#ifdef emacs
+#include "config.h"
+#ifdef static
+/* actually, only want this if static is defined as ""
+   -- this is for usg, in which emacs must undefine static
+   in order to make unexec workable
+   */
+#ifndef STACK_DIRECTION
+you
+lose
+-- must know STACK_DIRECTION at compile-time
+#endif /* STACK_DIRECTION undefined */
+#endif /* static */
+#endif /* emacs */
+
+#ifndef alloca  /* If compiling with GCC, this file's not needed.  */
+
+#ifdef __STDC__
+typedef void   *pointer;               /* generic pointer type */
+#else
+typedef char   *pointer;               /* generic pointer type */
+#endif
+
+#define        NULL    0                       /* null pointer constant */
+
+extern void    free();
+extern pointer xmalloc();
+
+/*
+       Define STACK_DIRECTION if you know the direction of stack
+       growth for your system; otherwise it will be automatically
+       deduced at run-time.
+
+       STACK_DIRECTION > 0 => grows toward higher addresses
+       STACK_DIRECTION < 0 => grows toward lower addresses
+       STACK_DIRECTION = 0 => direction of growth unknown
+*/
+
+#ifndef STACK_DIRECTION
+#define        STACK_DIRECTION 0               /* direction unknown */
+#endif
+
+#if STACK_DIRECTION != 0
+
+#define        STACK_DIR       STACK_DIRECTION /* known at compile-time */
+
+#else  /* STACK_DIRECTION == 0; need run-time code */
+
+static int     stack_dir;              /* 1 or -1 once known */
+#define        STACK_DIR       stack_dir
+
+static void
+find_stack_direction (/* void */)
+{
+  static char  *addr = NULL;   /* address of first
+                                  `dummy', once known */
+  auto char    dummy;          /* to get stack address */
+
+  if (addr == NULL)
+    {                          /* initial entry */
+      addr = &dummy;
+
+      find_stack_direction (); /* recurse once */
+    }
+  else                         /* second entry */
+    if (&dummy > addr)
+      stack_dir = 1;           /* stack grew upward */
+    else
+      stack_dir = -1;          /* stack grew downward */
+}
+
+#endif /* STACK_DIRECTION == 0 */
+
+/*
+       An "alloca header" is used to:
+       (a) chain together all alloca()ed blocks;
+       (b) keep track of stack depth.
+
+       It is very important that sizeof(header) agree with malloc()
+       alignment chunk size.  The following default should work okay.
+*/
+
+#ifndef        ALIGN_SIZE
+#define        ALIGN_SIZE      sizeof(double)
+#endif
+
+typedef union hdr
+{
+  char align[ALIGN_SIZE];      /* to force sizeof(header) */
+  struct
+    {
+      union hdr *next;         /* for chaining headers */
+      char *deep;              /* for stack depth measure */
+    } h;
+} header;
+
+/*
+       alloca( size ) returns a pointer to at least `size' bytes of
+       storage which will be automatically reclaimed upon exit from
+       the procedure that called alloca().  Originally, this space
+       was supposed to be taken from the current stack frame of the
+       caller, but that method cannot be made to work for some
+       implementations of C, for example under Gould's UTX/32.
+*/
+
+static header *last_alloca_header = NULL; /* -> last alloca header */
+
+pointer
+alloca (size)                  /* returns pointer to storage */
+     unsigned  size;           /* # bytes to allocate */
+{
+  auto char    probe;          /* probes stack depth: */
+  register char        *depth = &probe;
+
+#if STACK_DIRECTION == 0
+  if (STACK_DIR == 0)          /* unknown growth direction */
+    find_stack_direction ();
+#endif
+
+                               /* Reclaim garbage, defined as all alloca()ed storage that
+                                  was allocated from deeper in the stack than currently. */
+
+  {
+    register header    *hp;    /* traverses linked list */
+
+    for (hp = last_alloca_header; hp != NULL;)
+      if ((STACK_DIR > 0 && hp->h.deep > depth)
+         || (STACK_DIR < 0 && hp->h.deep < depth))
+       {
+         register header       *np = hp->h.next;
+
+         free ((pointer) hp);  /* collect garbage */
+
+         hp = np;              /* -> next header */
+       }
+      else
+       break;                  /* rest are not deeper */
+
+    last_alloca_header = hp;   /* -> last valid storage */
+  }
+
+  if (size == 0)
+    return NULL;               /* no allocation required */
+
+  /* Allocate combined header + user data storage. */
+
+  {
+    register pointer   new = xmalloc (sizeof (header) + size);
+    /* address of header */
+
+    ((header *)new)->h.next = last_alloca_header;
+    ((header *)new)->h.deep = depth;
+
+    last_alloca_header = (header *)new;
+
+    /* User storage begins just after header. */
+
+    return (pointer)((char *)new + sizeof(header));
+  }
+}
+
+#endif /* no alloca */
diff --git a/lib/basename.c b/lib/basename.c
new file mode 100644 (file)
index 0000000..b8e7e1f
--- /dev/null
@@ -0,0 +1,35 @@
+/* basename.c -- return the last element in a path
+   Copyright (C) 1990 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+#if defined(USG) || defined(STDC_HEADERS)
+#include <string.h>
+#define rindex strrchr
+#else
+#include <strings.h>
+#endif
+
+/* Return NAME with any leading path stripped off.  */
+
+char *
+basename (name)
+     char *name;
+{
+  char *base;
+
+  base = rindex (name, '/');
+  return base ? base + 1 : name;
+}
diff --git a/lib/error.c b/lib/error.c
new file mode 100644 (file)
index 0000000..7e61f15
--- /dev/null
@@ -0,0 +1,105 @@
+/* error.c -- error handler for noninteractive utilities
+   Copyright (C) 1990, 1991, 1992 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Written by David MacKenzie.  */
+
+#include <stdio.h>
+
+#ifdef HAVE_VPRINTF
+
+#if __STDC__
+#include <stdarg.h>
+#define VA_START(args, lastarg) va_start(args, lastarg)
+#else /* !__STDC__ */
+#include <varargs.h>
+#define VA_START(args, lastarg) va_start(args)
+#endif /* !__STDC__ */
+
+#else /* !HAVE_VPRINTF */
+
+#ifdef HAVE_DOPRNT
+#define va_alist args
+#define va_dcl int args;
+#else /* !HAVE_DOPRNT */
+#define va_alist a1, a2, a3, a4, a5, a6, a7, a8
+#define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8;
+#endif /* !HAVE_DOPRNT */
+
+#endif /* !HAVE_VPRINTF */
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#include <string.h>
+#else /* !STDC_HEADERS */
+void exit ();
+#endif /* !STDC_HEADERS */
+
+#ifndef HAVE_STRERROR
+static char *
+private_strerror (errnum)
+     int errnum;
+{
+  extern char *sys_errlist[];
+  extern int sys_nerr;
+
+  if (errnum > 0 && errnum <= sys_nerr)
+    return sys_errlist[errnum];
+  return "Unknown system error";
+}
+#define strerror private_strerror
+#endif /* !HAVE_STRERROR */
+
+/* Print the program name and error message MESSAGE, which is a printf-style
+   format string with optional args.
+   If ERRNUM is nonzero, print its corresponding system error message.
+   Exit with status STATUS if it is nonzero.  */
+/* VARARGS */
+void
+#if defined (HAVE_VPRINTF) && __STDC__
+error (int status, int errnum, char *message, ...)
+#else /* !HAVE_VPRINTF or !__STDC__ */
+error (status, errnum, message, va_alist)
+     int status;
+     int errnum;
+     char *message;
+     va_dcl
+#endif /* !HAVE_VPRINTF or !__STDC__ */
+{
+  extern char *program_name;
+#ifdef HAVE_VPRINTF
+  va_list args;
+#endif /* HAVE_VPRINTF */
+
+  fprintf (stderr, "%s: ", program_name);
+#ifdef HAVE_VPRINTF
+  VA_START (args, message);
+  vfprintf (stderr, message, args);
+  va_end (args);
+#else /* !HAVE_VPRINTF */
+#ifdef HAVE_DOPRNT
+  _doprnt (message, &args, stderr);
+#else /* !HAVE_DOPRNT */
+  fprintf (stderr, message, a1, a2, a3, a4, a5, a6, a7, a8);
+#endif /* !HAVE_DOPRNT */
+#endif /* !HAVE_VPRINTF */
+  if (errnum)
+    fprintf (stderr, ": %s", strerror (errnum));
+  putc ('\n', stderr);
+  fflush (stderr);
+  if (status)
+    exit (status);
+}
diff --git a/lib/getdate.y b/lib/getdate.y
new file mode 100644 (file)
index 0000000..bff1a9b
--- /dev/null
@@ -0,0 +1,965 @@
+%{
+/* $Revision: 2.1 $
+**
+**  Originally written by Steven M. Bellovin <smb@research.att.com> while
+**  at the University of North Carolina at Chapel Hill.  Later tweaked by
+**  a couple of people on Usenet.  Completely overhauled by Rich $alz
+**  <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
+**  send any email to Rich.
+**
+**  This grammar has eight shift/reduce conflicts.
+**
+**  This code is in the public domain and has no copyright.
+*/
+/* SUPPRESS 287 on yaccpar_sccsid *//* Unusd static variable */
+/* SUPPRESS 288 on yyerrlab *//* Label unused */
+
+#ifdef __GNUC__
+#define alloca __builtin_alloca
+#else
+#ifdef sparc
+#include <alloca.h>
+#else
+#ifdef _AIX /* for Bison */
+ #pragma alloca
+#else
+char *alloca ();
+#endif
+#endif
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+
+/* The code at the top of get_date which figures out the offset of the
+   current time zone checks various CPP symbols to see if special
+   tricks are need, but defaults to using the gettimeofday system call.
+   Include <sys/time.h> if that will be used.  */
+
+#if !defined (USG) && !defined (sgi) && !defined (__386BSD__)
+#include <sys/time.h>
+#endif
+
+#if    defined(vms)
+
+#include <types.h>
+#include <time.h>
+
+#else
+
+#include <sys/types.h>
+
+#if    defined(USG) || !defined(HAVE_FTIME)
+/*
+**  If you need to do a tzset() call to set the
+**  timezone, and don't have ftime().
+*/
+struct timeb {
+    time_t             time;           /* Seconds since the epoch      */
+    unsigned short     millitm;        /* Field not used               */
+    short              timezone;
+    short              dstflag;        /* Field not used               */
+};
+
+#else
+
+#include <sys/timeb.h>
+
+#endif /* defined(USG) && !defined(HAVE_FTIME) */
+
+#if    defined(BSD4_2) || defined(BSD4_1C) || (defined (hp9000) && !defined (hpux))
+#include <sys/time.h>
+#else
+#if defined(_AIX)
+#include <sys/time.h>
+#endif
+#include <time.h>
+#endif /* defined(BSD4_2) */
+
+#endif /* defined(vms) */
+
+#if defined (STDC_HEADERS) || defined (USG)
+#include <string.h>
+#endif
+
+#if sgi
+#undef timezone
+#endif
+
+extern struct tm       *localtime();
+
+#define yyparse getdate_yyparse
+#define yylex getdate_yylex
+#define yyerror getdate_yyerror
+
+#if    !defined(lint) && !defined(SABER)
+static char RCS[] =
+       "$Header: str2date.y,v 2.1 90/09/06 08:15:06 cronan Exp $";
+#endif /* !defined(lint) && !defined(SABER) */
+
+
+#define EPOCH          1970
+#define HOUR(x)                ((time_t)(x) * 60)
+#define SECSPERDAY     (24L * 60L * 60L)
+
+
+/*
+**  An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+    char       *name;
+    int                type;
+    time_t     value;
+} TABLE;
+
+
+/*
+**  Daylight-savings mode:  on, off, or not yet known.
+*/
+typedef enum _DSTMODE {
+    DSTon, DSToff, DSTmaybe
+} DSTMODE;
+
+/*
+**  Meridian:  am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+    MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+**  Global variables.  We could get rid of most of these by using a good
+**  union as the yacc stack.  (This routine was originally written before
+**  yacc had the %union construct.)  Maybe someday; right now we only use
+**  the %union very rarely.
+*/
+static char    *yyInput;
+static DSTMODE yyDSTmode;
+static time_t  yyDayOrdinal;
+static time_t  yyDayNumber;
+static int     yyHaveDate;
+static int     yyHaveDay;
+static int     yyHaveRel;
+static int     yyHaveTime;
+static int     yyHaveZone;
+static time_t  yyTimezone;
+static time_t  yyDay;
+static time_t  yyHour;
+static time_t  yyMinutes;
+static time_t  yyMonth;
+static time_t  yySeconds;
+static time_t  yyYear;
+static MERIDIAN        yyMeridian;
+static time_t  yyRelMonth;
+static time_t  yyRelSeconds;
+
+%}
+
+%union {
+    time_t             Number;
+    enum _MERIDIAN     Meridian;
+}
+
+%token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
+%token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
+
+%type  <Number>        tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
+%type  <Number>        tSEC_UNIT tSNUMBER tUNUMBER tZONE
+%type  <Meridian>      tMERIDIAN o_merid
+
+%%
+
+spec   : /* NULL */
+       | spec item
+       ;
+
+item   : time {
+           yyHaveTime++;
+       }
+       | zone {
+           yyHaveZone++;
+       }
+       | date {
+           yyHaveDate++;
+       }
+       | day {
+           yyHaveDay++;
+       }
+       | rel {
+           yyHaveRel++;
+       }
+       | number
+       ;
+
+time   : tUNUMBER tMERIDIAN {
+           yyHour = $1;
+           yyMinutes = 0;
+           yySeconds = 0;
+           yyMeridian = $2;
+       }
+       | tUNUMBER ':' tUNUMBER o_merid {
+           yyHour = $1;
+           yyMinutes = $3;
+           yySeconds = 0;
+           yyMeridian = $4;
+       }
+       | tUNUMBER ':' tUNUMBER tSNUMBER {
+           yyHour = $1;
+           yyMinutes = $3;
+           yyMeridian = MER24;
+           yyDSTmode = DSToff;
+           yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
+       }
+       | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
+           yyHour = $1;
+           yyMinutes = $3;
+           yySeconds = $5;
+           yyMeridian = $6;
+       }
+       | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
+           yyHour = $1;
+           yyMinutes = $3;
+           yySeconds = $5;
+           yyMeridian = MER24;
+           yyDSTmode = DSToff;
+           yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
+       }
+       ;
+
+zone   : tZONE {
+           yyTimezone = $1;
+           yyDSTmode = DSToff;
+       }
+       | tDAYZONE {
+           yyTimezone = $1;
+           yyDSTmode = DSTon;
+       }
+       |
+         tZONE tDST {
+           yyTimezone = $1;
+           yyDSTmode = DSTon;
+       }
+       ;
+
+day    : tDAY {
+           yyDayOrdinal = 1;
+           yyDayNumber = $1;
+       }
+       | tDAY ',' {
+           yyDayOrdinal = 1;
+           yyDayNumber = $1;
+       }
+       | tUNUMBER tDAY {
+           yyDayOrdinal = $1;
+           yyDayNumber = $2;
+       }
+       ;
+
+date   : tUNUMBER '/' tUNUMBER {
+           yyMonth = $1;
+           yyDay = $3;
+       }
+       | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+           yyMonth = $1;
+           yyDay = $3;
+           yyYear = $5;
+       }
+       | tUNUMBER tSNUMBER tSNUMBER {
+           /* ISO 8601 format.  yyyy-mm-dd.  */
+           yyYear = $1;
+           yyMonth = -$2;
+           yyDay = -$3;
+       }
+       | tMONTH tUNUMBER {
+           yyMonth = $1;
+           yyDay = $2;
+       }
+       | tMONTH tUNUMBER ',' tUNUMBER {
+           yyMonth = $1;
+           yyDay = $2;
+           yyYear = $4;
+       }
+       | tUNUMBER tMONTH {
+           yyMonth = $2;
+           yyDay = $1;
+       }
+       | tUNUMBER tMONTH tUNUMBER {
+           yyMonth = $2;
+           yyDay = $1;
+           yyYear = $3;
+       }
+       ;
+
+rel    : relunit tAGO {
+           yyRelSeconds = -yyRelSeconds;
+           yyRelMonth = -yyRelMonth;
+       }
+       | relunit
+       ;
+
+relunit        : tUNUMBER tMINUTE_UNIT {
+           yyRelSeconds += $1 * $2 * 60L;
+       }
+       | tSNUMBER tMINUTE_UNIT {
+           yyRelSeconds += $1 * $2 * 60L;
+       }
+       | tMINUTE_UNIT {
+           yyRelSeconds += $1 * 60L;
+       }
+       | tSNUMBER tSEC_UNIT {
+           yyRelSeconds += $1;
+       }
+       | tUNUMBER tSEC_UNIT {
+           yyRelSeconds += $1;
+       }
+       | tSEC_UNIT {
+           yyRelSeconds++;
+       }
+       | tSNUMBER tMONTH_UNIT {
+           yyRelMonth += $1 * $2;
+       }
+       | tUNUMBER tMONTH_UNIT {
+           yyRelMonth += $1 * $2;
+       }
+       | tMONTH_UNIT {
+           yyRelMonth += $1;
+       }
+       ;
+
+number : tUNUMBER {
+           if (yyHaveTime && yyHaveDate && !yyHaveRel)
+               yyYear = $1;
+           else {
+               if($1>10000) {
+                   time_t date_part;
+
+                   date_part= $1/10000;
+                   yyHaveDate++;
+                   yyDay= (date_part)%100;
+                   yyMonth= (date_part/100)%100;
+                   yyYear = date_part/10000;
+               } 
+               yyHaveTime++;
+               if ($1 < 100) {
+                   yyHour = $1;
+                   yyMinutes = 0;
+               }
+               else {
+                   yyHour = $1 / 100;
+                   yyMinutes = $1 % 100;
+               }
+               yySeconds = 0;
+               yyMeridian = MER24;
+           }
+       }
+       ;
+
+o_merid        : /* NULL */ {
+           $$ = MER24;
+       }
+       | tMERIDIAN {
+           $$ = $1;
+       }
+       ;
+
+%%
+
+/* Month and day table. */
+static TABLE   MonthDayTable[] = {
+    { "january",       tMONTH,  1 },
+    { "february",      tMONTH,  2 },
+    { "march",         tMONTH,  3 },
+    { "april",         tMONTH,  4 },
+    { "may",           tMONTH,  5 },
+    { "june",          tMONTH,  6 },
+    { "july",          tMONTH,  7 },
+    { "august",                tMONTH,  8 },
+    { "september",     tMONTH,  9 },
+    { "sept",          tMONTH,  9 },
+    { "october",       tMONTH, 10 },
+    { "november",      tMONTH, 11 },
+    { "december",      tMONTH, 12 },
+    { "sunday",                tDAY, 0 },
+    { "monday",                tDAY, 1 },
+    { "tuesday",       tDAY, 2 },
+    { "tues",          tDAY, 2 },
+    { "wednesday",     tDAY, 3 },
+    { "wednes",                tDAY, 3 },
+    { "thursday",      tDAY, 4 },
+    { "thur",          tDAY, 4 },
+    { "thurs",         tDAY, 4 },
+    { "friday",                tDAY, 5 },
+    { "saturday",      tDAY, 6 },
+    { NULL }
+};
+
+/* Time units table. */
+static TABLE   UnitsTable[] = {
+    { "year",          tMONTH_UNIT,    12 },
+    { "month",         tMONTH_UNIT,    1 },
+    { "fortnight",     tMINUTE_UNIT,   14 * 24 * 60 },
+    { "week",          tMINUTE_UNIT,   7 * 24 * 60 },
+    { "day",           tMINUTE_UNIT,   1 * 24 * 60 },
+    { "hour",          tMINUTE_UNIT,   60 },
+    { "minute",                tMINUTE_UNIT,   1 },
+    { "min",           tMINUTE_UNIT,   1 },
+    { "second",                tSEC_UNIT,      1 },
+    { "sec",           tSEC_UNIT,      1 },
+    { NULL }
+};
+
+/* Assorted relative-time words. */
+static TABLE   OtherTable[] = {
+    { "tomorrow",      tMINUTE_UNIT,   1 * 24 * 60 },
+    { "yesterday",     tMINUTE_UNIT,   -1 * 24 * 60 },
+    { "today",         tMINUTE_UNIT,   0 },
+    { "now",           tMINUTE_UNIT,   0 },
+    { "last",          tUNUMBER,       -1 },
+    { "this",          tMINUTE_UNIT,   0 },
+    { "next",          tUNUMBER,       2 },
+    { "first",         tUNUMBER,       1 },
+/*  { "second",                tUNUMBER,       2 }, */
+    { "third",         tUNUMBER,       3 },
+    { "fourth",                tUNUMBER,       4 },
+    { "fifth",         tUNUMBER,       5 },
+    { "sixth",         tUNUMBER,       6 },
+    { "seventh",       tUNUMBER,       7 },
+    { "eighth",                tUNUMBER,       8 },
+    { "ninth",         tUNUMBER,       9 },
+    { "tenth",         tUNUMBER,       10 },
+    { "eleventh",      tUNUMBER,       11 },
+    { "twelfth",       tUNUMBER,       12 },
+    { "ago",           tAGO,   1 },
+    { NULL }
+};
+
+/* The timezone table. */
+/* Some of these are commented out because a time_t can't store a float. */
+static TABLE   TimezoneTable[] = {
+    { "gmt",   tZONE,     HOUR( 0) },  /* Greenwich Mean */
+    { "ut",    tZONE,     HOUR( 0) },  /* Universal (Coordinated) */
+    { "utc",   tZONE,     HOUR( 0) },
+    { "wet",   tZONE,     HOUR( 0) },  /* Western European */
+    { "bst",   tDAYZONE,  HOUR( 0) },  /* British Summer */
+    { "wat",   tZONE,     HOUR( 1) },  /* West Africa */
+    { "at",    tZONE,     HOUR( 2) },  /* Azores */
+#if    0
+    /* For completeness.  BST is also British Summer, and GST is
+     * also Guam Standard. */
+    { "bst",   tZONE,     HOUR( 3) },  /* Brazil Standard */
+    { "gst",   tZONE,     HOUR( 3) },  /* Greenland Standard */
+#endif
+#if 0
+    { "nft",   tZONE,     HOUR(3.5) }, /* Newfoundland */
+    { "nst",   tZONE,     HOUR(3.5) }, /* Newfoundland Standard */
+    { "ndt",   tDAYZONE,  HOUR(3.5) }, /* Newfoundland Daylight */
+#endif
+    { "ast",   tZONE,     HOUR( 4) },  /* Atlantic Standard */
+    { "adt",   tDAYZONE,  HOUR( 4) },  /* Atlantic Daylight */
+    { "est",   tZONE,     HOUR( 5) },  /* Eastern Standard */
+    { "edt",   tDAYZONE,  HOUR( 5) },  /* Eastern Daylight */
+    { "cst",   tZONE,     HOUR( 6) },  /* Central Standard */
+    { "cdt",   tDAYZONE,  HOUR( 6) },  /* Central Daylight */
+    { "mst",   tZONE,     HOUR( 7) },  /* Mountain Standard */
+    { "mdt",   tDAYZONE,  HOUR( 7) },  /* Mountain Daylight */
+    { "pst",   tZONE,     HOUR( 8) },  /* Pacific Standard */
+    { "pdt",   tDAYZONE,  HOUR( 8) },  /* Pacific Daylight */
+    { "yst",   tZONE,     HOUR( 9) },  /* Yukon Standard */
+    { "ydt",   tDAYZONE,  HOUR( 9) },  /* Yukon Daylight */
+    { "hst",   tZONE,     HOUR(10) },  /* Hawaii Standard */
+    { "hdt",   tDAYZONE,  HOUR(10) },  /* Hawaii Daylight */
+    { "cat",   tZONE,     HOUR(10) },  /* Central Alaska */
+    { "ahst",  tZONE,     HOUR(10) },  /* Alaska-Hawaii Standard */
+    { "nt",    tZONE,     HOUR(11) },  /* Nome */
+    { "idlw",  tZONE,     HOUR(12) },  /* International Date Line West */
+    { "cet",   tZONE,     -HOUR(1) },  /* Central European */
+    { "met",   tZONE,     -HOUR(1) },  /* Middle European */
+    { "mewt",  tZONE,     -HOUR(1) },  /* Middle European Winter */
+    { "mest",  tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
+    { "swt",   tZONE,     -HOUR(1) },  /* Swedish Winter */
+    { "sst",   tDAYZONE,  -HOUR(1) },  /* Swedish Summer */
+    { "fwt",   tZONE,     -HOUR(1) },  /* French Winter */
+    { "fst",   tDAYZONE,  -HOUR(1) },  /* French Summer */
+    { "eet",   tZONE,     -HOUR(2) },  /* Eastern Europe, USSR Zone 1 */
+    { "bt",    tZONE,     -HOUR(3) },  /* Baghdad, USSR Zone 2 */
+#if 0
+    { "it",    tZONE,     -HOUR(3.5) },/* Iran */
+#endif
+    { "zp4",   tZONE,     -HOUR(4) },  /* USSR Zone 3 */
+    { "zp5",   tZONE,     -HOUR(5) },  /* USSR Zone 4 */
+#if 0
+    { "ist",   tZONE,     -HOUR(5.5) },/* Indian Standard */
+#endif
+    { "zp6",   tZONE,     -HOUR(6) },  /* USSR Zone 5 */
+#if    0
+    /* For completeness.  NST is also Newfoundland Stanard, and SST is
+     * also Swedish Summer. */
+    { "nst",   tZONE,     -HOUR(6.5) },/* North Sumatra */
+    { "sst",   tZONE,     -HOUR(7) },  /* South Sumatra, USSR Zone 6 */
+#endif /* 0 */
+    { "wast",  tZONE,     -HOUR(7) },  /* West Australian Standard */
+    { "wadt",  tDAYZONE,  -HOUR(7) },  /* West Australian Daylight */
+#if 0
+    { "jt",    tZONE,     -HOUR(7.5) },/* Java (3pm in Cronusland!) */
+#endif
+    { "cct",   tZONE,     -HOUR(8) },  /* China Coast, USSR Zone 7 */
+    { "jst",   tZONE,     -HOUR(9) },  /* Japan Standard, USSR Zone 8 */
+#if 0
+    { "cast",  tZONE,     -HOUR(9.5) },/* Central Australian Standard */
+    { "cadt",  tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
+#endif
+    { "east",  tZONE,     -HOUR(10) }, /* Eastern Australian Standard */
+    { "eadt",  tDAYZONE,  -HOUR(10) }, /* Eastern Australian Daylight */
+    { "gst",   tZONE,     -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
+    { "nzt",   tZONE,     -HOUR(12) }, /* New Zealand */
+    { "nzst",  tZONE,     -HOUR(12) }, /* New Zealand Standard */
+    { "nzdt",  tDAYZONE,  -HOUR(12) }, /* New Zealand Daylight */
+    { "idle",  tZONE,     -HOUR(12) }, /* International Date Line East */
+    {  NULL  }
+};
+
+/* Military timezone table. */
+static TABLE   MilitaryTable[] = {
+    { "a",     tZONE,  HOUR(  1) },
+    { "b",     tZONE,  HOUR(  2) },
+    { "c",     tZONE,  HOUR(  3) },
+    { "d",     tZONE,  HOUR(  4) },
+    { "e",     tZONE,  HOUR(  5) },
+    { "f",     tZONE,  HOUR(  6) },
+    { "g",     tZONE,  HOUR(  7) },
+    { "h",     tZONE,  HOUR(  8) },
+    { "i",     tZONE,  HOUR(  9) },
+    { "k",     tZONE,  HOUR( 10) },
+    { "l",     tZONE,  HOUR( 11) },
+    { "m",     tZONE,  HOUR( 12) },
+    { "n",     tZONE,  HOUR(- 1) },
+    { "o",     tZONE,  HOUR(- 2) },
+    { "p",     tZONE,  HOUR(- 3) },
+    { "q",     tZONE,  HOUR(- 4) },
+    { "r",     tZONE,  HOUR(- 5) },
+    { "s",     tZONE,  HOUR(- 6) },
+    { "t",     tZONE,  HOUR(- 7) },
+    { "u",     tZONE,  HOUR(- 8) },
+    { "v",     tZONE,  HOUR(- 9) },
+    { "w",     tZONE,  HOUR(-10) },
+    { "x",     tZONE,  HOUR(-11) },
+    { "y",     tZONE,  HOUR(-12) },
+    { "z",     tZONE,  HOUR(  0) },
+    { NULL }
+};
+
+\f
+
+
+/* ARGSUSED */
+int
+yyerror(s)
+    char       *s;
+{
+  return 0;
+}
+
+
+static time_t
+ToSeconds(Hours, Minutes, Seconds, Meridian)
+    time_t     Hours;
+    time_t     Minutes;
+    time_t     Seconds;
+    MERIDIAN   Meridian;
+{
+    if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
+       return -1;
+    switch (Meridian) {
+    case MER24:
+       if (Hours < 0 || Hours > 23)
+           return -1;
+       return (Hours * 60L + Minutes) * 60L + Seconds;
+    case MERam:
+       if (Hours < 1 || Hours > 12)
+           return -1;
+       return (Hours * 60L + Minutes) * 60L + Seconds;
+    case MERpm:
+       if (Hours < 1 || Hours > 12)
+           return -1;
+       return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
+    }
+    /* NOTREACHED */
+}
+
+
+static time_t
+Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
+    time_t     Month;
+    time_t     Day;
+    time_t     Year;
+    time_t     Hours;
+    time_t     Minutes;
+    time_t     Seconds;
+    MERIDIAN   Meridian;
+    DSTMODE    DSTmode;
+{
+    static int DaysInMonth[12] = {
+       31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+    };
+    time_t     tod;
+    time_t     Julian;
+    int                i;
+
+    if (Year < 0)
+       Year = -Year;
+    if (Year < 100)
+       Year += 1900;
+    DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
+                   ? 29 : 28;
+    if (Year < EPOCH || Year > 1999
+     || Month < 1 || Month > 12
+     /* Lint fluff:  "conversion from long may lose accuracy" */
+     || Day < 1 || Day > DaysInMonth[(int)--Month])
+       return -1;
+
+    for (Julian = Day - 1, i = 0; i < Month; i++)
+       Julian += DaysInMonth[i];
+    for (i = EPOCH; i < Year; i++)
+       Julian += 365 + (i % 4 == 0);
+    Julian *= SECSPERDAY;
+    Julian += yyTimezone * 60L;
+    if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
+       return -1;
+    Julian += tod;
+    if (DSTmode == DSTon
+     || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
+       Julian -= 60 * 60;
+    return Julian;
+}
+
+
+static time_t
+DSTcorrect(Start, Future)
+    time_t     Start;
+    time_t     Future;
+{
+    time_t     StartDay;
+    time_t     FutureDay;
+
+    StartDay = (localtime(&Start)->tm_hour + 1) % 24;
+    FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
+    return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
+}
+
+
+static time_t
+RelativeDate(Start, DayOrdinal, DayNumber)
+    time_t     Start;
+    time_t     DayOrdinal;
+    time_t     DayNumber;
+{
+    struct tm  *tm;
+    time_t     now;
+
+    now = Start;
+    tm = localtime(&now);
+    now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
+    now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
+    return DSTcorrect(Start, now);
+}
+
+
+static time_t
+RelativeMonth(Start, RelMonth)
+    time_t     Start;
+    time_t     RelMonth;
+{
+    struct tm  *tm;
+    time_t     Month;
+    time_t     Year;
+
+    if (RelMonth == 0)
+       return 0;
+    tm = localtime(&Start);
+    Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
+    Year = Month / 12;
+    Month = Month % 12 + 1;
+    return DSTcorrect(Start,
+           Convert(Month, (time_t)tm->tm_mday, Year,
+               (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
+               MER24, DSTmaybe));
+}
+
+
+static int
+LookupWord(buff)
+    char               *buff;
+{
+    register char      *p;
+    register char      *q;
+    register TABLE     *tp;
+    int                        i;
+    int                        abbrev;
+
+    /* Make it lowercase. */
+    for (p = buff; *p; p++)
+       if (isupper(*p))
+           *p = tolower(*p);
+
+    if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
+       yylval.Meridian = MERam;
+       return tMERIDIAN;
+    }
+    if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
+       yylval.Meridian = MERpm;
+       return tMERIDIAN;
+    }
+
+    /* See if we have an abbreviation for a month. */
+    if (strlen(buff) == 3)
+       abbrev = 1;
+    else if (strlen(buff) == 4 && buff[3] == '.') {
+       abbrev = 1;
+       buff[3] = '\0';
+    }
+    else
+       abbrev = 0;
+
+    for (tp = MonthDayTable; tp->name; tp++) {
+       if (abbrev) {
+           if (strncmp(buff, tp->name, 3) == 0) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+       }
+       else if (strcmp(buff, tp->name) == 0) {
+           yylval.Number = tp->value;
+           return tp->type;
+       }
+    }
+
+    for (tp = TimezoneTable; tp->name; tp++)
+       if (strcmp(buff, tp->name) == 0) {
+           yylval.Number = tp->value;
+           return tp->type;
+       }
+
+    if (strcmp(buff, "dst") == 0) 
+       return tDST;
+
+    for (tp = UnitsTable; tp->name; tp++)
+       if (strcmp(buff, tp->name) == 0) {
+           yylval.Number = tp->value;
+           return tp->type;
+       }
+
+    /* Strip off any plural and try the units table again. */
+    i = strlen(buff) - 1;
+    if (buff[i] == 's') {
+       buff[i] = '\0';
+       for (tp = UnitsTable; tp->name; tp++)
+           if (strcmp(buff, tp->name) == 0) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+       buff[i] = 's';          /* Put back for "this" in OtherTable. */
+    }
+
+    for (tp = OtherTable; tp->name; tp++)
+       if (strcmp(buff, tp->name) == 0) {
+           yylval.Number = tp->value;
+           return tp->type;
+       }
+
+    /* Military timezones. */
+    if (buff[1] == '\0' && isalpha(*buff)) {
+       for (tp = MilitaryTable; tp->name; tp++)
+           if (strcmp(buff, tp->name) == 0) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+    }
+
+    /* Drop out any periods and try the timezone table again. */
+    for (i = 0, p = q = buff; *q; q++)
+       if (*q != '.')
+           *p++ = *q;
+       else
+           i++;
+    *p = '\0';
+    if (i)
+       for (tp = TimezoneTable; tp->name; tp++)
+           if (strcmp(buff, tp->name) == 0) {
+               yylval.Number = tp->value;
+               return tp->type;
+           }
+
+    return tID;
+}
+
+
+int
+yylex()
+{
+    register char      c;
+    register char      *p;
+    char               buff[20];
+    int                        Count;
+    int                        sign;
+
+    for ( ; ; ) {
+       while (isspace(*yyInput))
+           yyInput++;
+
+       if (isdigit(c = *yyInput) || c == '-' || c == '+') {
+           if (c == '-' || c == '+') {
+               sign = c == '-' ? -1 : 1;
+               if (!isdigit(*++yyInput))
+                   /* skip the '-' sign */
+                   continue;
+           }
+           else
+               sign = 0;
+           for (yylval.Number = 0; isdigit(c = *yyInput++); )
+               yylval.Number = 10 * yylval.Number + c - '0';
+           yyInput--;
+           if (sign < 0)
+               yylval.Number = -yylval.Number;
+           return sign ? tSNUMBER : tUNUMBER;
+       }
+       if (isalpha(c)) {
+           for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
+               if (p < &buff[sizeof buff - 1])
+                   *p++ = c;
+           *p = '\0';
+           yyInput--;
+           return LookupWord(buff);
+       }
+       if (c != '(')
+           return *yyInput++;
+       Count = 0;
+       do {
+           c = *yyInput++;
+           if (c == '\0')
+               return c;
+           if (c == '(')
+               Count++;
+           else if (c == ')')
+               Count--;
+       } while (Count > 0);
+    }
+}
+
+
+time_t
+get_date(p, now)
+    char               *p;
+    struct timeb       *now;
+{
+    struct tm          *tm;
+    struct timeb       ftz;
+    time_t             Start;
+    time_t             tod;
+
+    yyInput = p;
+    if (now == NULL) {
+        now = &ftz;
+#if    !defined(HAVE_FTIME)
+       (void)time(&ftz.time);
+       /* Set the timezone global. */
+       tzset();
+       {
+#if sgi
+           ftz.timezone = (int) _timezone / 60;
+#else /* not sgi */
+#ifdef __386BSD__
+           ftz.timezone = 0;
+#else /* neither sgi nor 386BSD */
+#if defined (USG)
+           extern time_t timezone;
+
+           ftz.timezone = (int) timezone / 60;
+#else /* neither sgi nor 386BSD nor USG */
+           struct timeval tv;
+           struct timezone tz;
+
+           gettimeofday (&tv, &tz);
+           ftz.timezone = (int) tz.tz_minuteswest;
+#endif /* neither sgi nor 386BSD nor USG */
+#endif /* neither sgi nor 386BSD */
+#endif /* not sgi */
+       }
+#else /* HAVE_FTIME */
+       (void)ftime(&ftz);
+#endif /* HAVE_FTIME */
+    }
+
+    tm = localtime(&now->time);
+    yyYear = tm->tm_year;
+    yyMonth = tm->tm_mon + 1;
+    yyDay = tm->tm_mday;
+    yyTimezone = now->timezone;
+    yyDSTmode = DSTmaybe;
+    yyHour = 0;
+    yyMinutes = 0;
+    yySeconds = 0;
+    yyMeridian = MER24;
+    yyRelSeconds = 0;
+    yyRelMonth = 0;
+    yyHaveDate = 0;
+    yyHaveDay = 0;
+    yyHaveRel = 0;
+    yyHaveTime = 0;
+    yyHaveZone = 0;
+
+    if (yyparse()
+     || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
+       return -1;
+
+    if (yyHaveDate || yyHaveTime || yyHaveDay) {
+       Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
+                   yyMeridian, yyDSTmode);
+       if (Start < 0)
+           return -1;
+    }
+    else {
+       Start = now->time;
+       if (!yyHaveRel)
+           Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
+    }
+
+    Start += yyRelSeconds;
+    Start += RelativeMonth(Start, yyRelMonth);
+
+    if (yyHaveDay && !yyHaveDate) {
+       tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
+       Start += tod;
+    }
+
+    /* Have to do *something* with a legitimate -1 so it's distinguishable
+     * from the error return value.  (Alternately could set errno on error.) */
+    return Start == -1 ? 0 : Start;
+}
+
+
+#if    defined(TEST)
+
+/* ARGSUSED */
+main(ac, av)
+    int                ac;
+    char       *av[];
+{
+    char       buff[128];
+    time_t     d;
+
+    (void)printf("Enter date, or blank line to exit.\n\t> ");
+    (void)fflush(stdout);
+    while (gets(buff) && buff[0]) {
+       d = get_date(buff, (struct timeb *)NULL);
+       if (d == -1)
+           (void)printf("Bad format - couldn't convert.\n");
+       else
+           (void)printf("%s", ctime(&d));
+       (void)printf("\t> ");
+       (void)fflush(stdout);
+    }
+    exit(0);
+    /* NOTREACHED */
+}
+#endif /* defined(TEST) */
diff --git a/lib/gethostname.c b/lib/gethostname.c
new file mode 100644 (file)
index 0000000..fe78dbb
--- /dev/null
@@ -0,0 +1,49 @@
+/* gethostname emulation for SysV and POSIX.1.
+   Copyright (C) 1992 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* David MacKenzie <djm@gnu.ai.mit.edu> */
+
+#ifdef HAVE_UNAME
+#include <sys/utsname.h>
+#endif
+
+/* Put up to LEN chars of the host name into NAME.
+   Null terminate it if the name is shorter than LEN.
+   Return 0 if ok, -1 if error.  */
+
+int
+gethostname (name, len)
+     char *name;
+     int len;
+{
+#ifdef HAVE_UNAME
+  struct utsname uts;
+
+  if (uname (&uts) == -1)
+    return -1;
+  if (len > sizeof (uts.nodename))
+    {
+      /* More space than we need is available.  */
+      name[sizeof (uts.nodename)] = '\0';
+      len = sizeof (uts.nodename);
+    }
+  strncpy (name, uts.nodename, len);
+#else
+  strcpy (name, "");           /* Hardcode your system name if you want.  */
+#endif
+  return 0;
+}
diff --git a/lib/getopt.c b/lib/getopt.c
new file mode 100644 (file)
index 0000000..771b511
--- /dev/null
@@ -0,0 +1,679 @@
+/* Getopt for GNU.
+   NOTE: getopt is now part of the C library, so if you don't know what
+   "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu
+   before changing it!
+
+   Copyright (C) 1987, 88, 89, 90, 91, 1992 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by the
+   Free Software Foundation; either version 2, or (at your option) any
+   later version.
+   
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+   
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
+\f
+/* AIX requires this to be the first thing in the file.  */
+#ifdef __GNUC__
+#define alloca __builtin_alloca
+#else /* not __GNUC__ */
+#if defined (HAVE_ALLOCA_H) || (defined(sparc) && (defined(sun) || (!defined(USG) && !defined(SVR4) && !defined(__svr4__))))
+#include <alloca.h>
+#else
+#ifdef _AIX
+ #pragma alloca
+#else
+char *alloca ();
+#endif
+#endif /* alloca.h */
+#endif /* not __GNUC__ */
+
+#include <stdio.h>
+
+/* This needs to come after some library #include
+   to get __GNU_LIBRARY__ defined.  */
+#ifdef __GNU_LIBRARY__
+#undef alloca
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+   contain conflicting prototypes for getopt.  */
+#include <stdlib.h>
+#else  /* Not GNU C library.  */
+#define        __alloca        alloca
+#endif /* GNU C library.  */
+
+#if !__STDC__
+#define const
+#endif
+
+/* If GETOPT_COMPAT is defined, `+' as well as `--' can introduce a
+   long-named option.  Because this is not POSIX.2 compliant, it is
+   being phased out.  */
+#define GETOPT_COMPAT
+
+/* This version of `getopt' appears to the caller like standard Unix `getopt'
+   but it behaves differently for the user, since it allows the user
+   to intersperse the options with the other arguments.
+
+   As `getopt' works, it permutes the elements of ARGV so that,
+   when it is done, all the options precede everything else.  Thus
+   all application programs are extended to handle flexible argument order.
+
+   Setting the environment variable POSIXLY_CORRECT disables permutation.
+   Then the behavior is completely standard.
+
+   GNU application programs can use a third alternative mode in which
+   they can distinguish the relative order of options and other arguments.  */
+
+#include "getopt.h"
+
+/* For communication from `getopt' to the caller.
+   When `getopt' finds an option that takes an argument,
+   the argument value is returned here.
+   Also, when `ordering' is RETURN_IN_ORDER,
+   each non-option ARGV-element is returned here.  */
+
+char *optarg = 0;
+
+/* Index in ARGV of the next element to be scanned.
+   This is used for communication to and from the caller
+   and for communication between successive calls to `getopt'.
+
+   On entry to `getopt', zero means this is the first call; initialize.
+
+   When `getopt' returns EOF, this is the index of the first of the
+   non-option elements that the caller should itself scan.
+
+   Otherwise, `optind' communicates from one call to the next
+   how much of ARGV has been scanned so far.  */
+
+int optind = 0;
+
+/* The next char to be scanned in the option-element
+   in which the last option character we returned was found.
+   This allows us to pick up the scan where we left off.
+
+   If this is zero, or a null string, it means resume the scan
+   by advancing to the next ARGV-element.  */
+
+static char *nextchar;
+
+/* Callers store zero here to inhibit the error message
+   for unrecognized options.  */
+
+int opterr = 1;
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+   If the caller did not specify anything,
+   the default is REQUIRE_ORDER if the environment variable
+   POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+   REQUIRE_ORDER means don't recognize them as options;
+   stop option processing when the first non-option is seen.
+   This is what Unix does.
+   This mode of operation is selected by either setting the environment
+   variable POSIXLY_CORRECT, or using `+' as the first character
+   of the list of option characters.
+
+   PERMUTE is the default.  We permute the contents of ARGV as we scan,
+   so that eventually all the non-options are at the end.  This allows options
+   to be given in any order, even with programs that were not written to
+   expect this.
+
+   RETURN_IN_ORDER is an option available to programs that were written
+   to expect options and other ARGV-elements in any order and that care about
+   the ordering of the two.  We describe each non-option ARGV-element
+   as if it were the argument of an option with character code 1.
+   Using `-' as the first character of the list of option characters
+   selects this mode of operation.
+
+   The special argument `--' forces an end of option-scanning regardless
+   of the value of `ordering'.  In the case of RETURN_IN_ORDER, only
+   `--' can cause `getopt' to return EOF with `optind' != ARGC.  */
+
+static enum
+{
+  REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+} ordering;
+\f
+#ifdef __GNU_LIBRARY__
+/* We want to avoid inclusion of string.h with non-GNU libraries
+   because there are many ways it can cause trouble.
+   On some systems, it contains special magic macros that don't work
+   in GCC.  */
+#include <string.h>
+#define        my_index        strchr
+#define        my_bcopy(src, dst, n)   memcpy ((dst), (src), (n))
+#else
+
+/* Avoid depending on library functions or files
+   whose names are inconsistent.  */
+
+char *getenv ();
+
+static char *
+my_index (string, chr)
+     char *string;
+     int chr;
+{
+  while (*string)
+    {
+      if (*string == chr)
+       return string;
+      string++;
+    }
+  return 0;
+}
+
+static void
+my_bcopy (from, to, size)
+     char *from, *to;
+     int size;
+{
+  int i;
+  for (i = 0; i < size; i++)
+    to[i] = from[i];
+}
+#endif                         /* GNU C library.  */
+\f
+/* Handle permutation of arguments.  */
+
+/* Describe the part of ARGV that contains non-options that have
+   been skipped.  `first_nonopt' is the index in ARGV of the first of them;
+   `last_nonopt' is the index after the last of them.  */
+
+static int first_nonopt;
+static int last_nonopt;
+
+/* Exchange two adjacent subsequences of ARGV.
+   One subsequence is elements [first_nonopt,last_nonopt)
+   which contains all the non-options that have been skipped so far.
+   The other is elements [last_nonopt,optind), which contains all
+   the options processed since those non-options were skipped.
+
+   `first_nonopt' and `last_nonopt' are relocated so that they describe
+   the new indices of the non-options in ARGV after they are moved.  */
+
+static void
+exchange (argv)
+     char **argv;
+{
+  int nonopts_size = (last_nonopt - first_nonopt) * sizeof (char *);
+  char **temp = (char **) __alloca (nonopts_size);
+
+  /* Interchange the two blocks of data in ARGV.  */
+
+  my_bcopy ((char *) &argv[first_nonopt], (char *) temp, nonopts_size);
+  my_bcopy ((char *) &argv[last_nonopt], (char *) &argv[first_nonopt],
+           (optind - last_nonopt) * sizeof (char *));
+  my_bcopy ((char *) temp,
+           (char *) &argv[first_nonopt + optind - last_nonopt],
+           nonopts_size);
+
+  /* Update records for the slots the non-options now occupy.  */
+
+  first_nonopt += (optind - last_nonopt);
+  last_nonopt = optind;
+}
+\f
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+   given in OPTSTRING.
+
+   If an element of ARGV starts with '-', and is not exactly "-" or "--",
+   then it is an option element.  The characters of this element
+   (aside from the initial '-') are option characters.  If `getopt'
+   is called repeatedly, it returns successively each of the option characters
+   from each of the option elements.
+
+   If `getopt' finds another option character, it returns that character,
+   updating `optind' and `nextchar' so that the next call to `getopt' can
+   resume the scan with the following option character or ARGV-element.
+
+   If there are no more option characters, `getopt' returns `EOF'.
+   Then `optind' is the index in ARGV of the first ARGV-element
+   that is not an option.  (The ARGV-elements have been permuted
+   so that those that are not options now come last.)
+
+   OPTSTRING is a string containing the legitimate option characters.
+   If an option character is seen that is not listed in OPTSTRING,
+   return '?' after printing an error message.  If you set `opterr' to
+   zero, the error message is suppressed but we still return '?'.
+
+   If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+   so the following text in the same ARGV-element, or the text of the following
+   ARGV-element, is returned in `optarg'.  Two colons mean an option that
+   wants an optional arg; if there is text in the current ARGV-element,
+   it is returned in `optarg', otherwise `optarg' is set to zero.
+
+   If OPTSTRING starts with `-' or `+', it requests different methods of
+   handling the non-option ARGV-elements.
+   See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+   Long-named options begin with `--' instead of `-'.
+   Their names may be abbreviated as long as the abbreviation is unique
+   or is an exact match for some defined option.  If they have an
+   argument, it follows the option name in the same ARGV-element, separated
+   from the option name by a `=', or else the in next ARGV-element.
+   When `getopt' finds a long-named option, it returns 0 if that option's
+   `flag' field is nonzero, the value of the option's `val' field
+   if the `flag' field is zero.
+
+   The elements of ARGV aren't really const, because we permute them.
+   But we pretend they're const in the prototype to be compatible
+   with other systems.
+
+   LONGOPTS is a vector of `struct option' terminated by an
+   element containing a name which is zero.
+
+   LONGIND returns the index in LONGOPT of the long-named option found.
+   It is only valid when a long-named option has been found by the most
+   recent call.
+
+   If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+   long-named options.  */
+
+int
+_getopt_internal (argc, argv, optstring, longopts, longind, long_only)
+     int argc;
+     char *const *argv;
+     const char *optstring;
+     const struct option *longopts;
+     int *longind;
+     int long_only;
+{
+  int option_index;
+
+  optarg = 0;
+
+  /* Initialize the internal data when the first call is made.
+     Start processing options with ARGV-element 1 (since ARGV-element 0
+     is the program name); the sequence of previously skipped
+     non-option ARGV-elements is empty.  */
+
+  if (optind == 0)
+    {
+      first_nonopt = last_nonopt = optind = 1;
+
+      nextchar = NULL;
+
+      /* Determine how to handle the ordering of options and nonoptions.  */
+
+      if (optstring[0] == '-')
+       {
+         ordering = RETURN_IN_ORDER;
+         ++optstring;
+       }
+      else if (optstring[0] == '+')
+       {
+         ordering = REQUIRE_ORDER;
+         ++optstring;
+       }
+      else if (getenv ("POSIXLY_CORRECT") != NULL)
+       ordering = REQUIRE_ORDER;
+      else
+       ordering = PERMUTE;
+    }
+
+  if (nextchar == NULL || *nextchar == '\0')
+    {
+      if (ordering == PERMUTE)
+       {
+         /* If we have just processed some options following some non-options,
+            exchange them so that the options come first.  */
+
+         if (first_nonopt != last_nonopt && last_nonopt != optind)
+           exchange ((char **) argv);
+         else if (last_nonopt != optind)
+           first_nonopt = optind;
+
+         /* Now skip any additional non-options
+            and extend the range of non-options previously skipped.  */
+
+         while (optind < argc
+                && (argv[optind][0] != '-' || argv[optind][1] == '\0')
+#ifdef GETOPT_COMPAT
+                && (longopts == NULL
+                    || argv[optind][0] != '+' || argv[optind][1] == '\0')
+#endif                         /* GETOPT_COMPAT */
+                )
+           optind++;
+         last_nonopt = optind;
+       }
+
+      /* Special ARGV-element `--' means premature end of options.
+        Skip it like a null option,
+        then exchange with previous non-options as if it were an option,
+        then skip everything else like a non-option.  */
+
+      if (optind != argc && !strcmp (argv[optind], "--"))
+       {
+         optind++;
+
+         if (first_nonopt != last_nonopt && last_nonopt != optind)
+           exchange ((char **) argv);
+         else if (first_nonopt == last_nonopt)
+           first_nonopt = optind;
+         last_nonopt = argc;
+
+         optind = argc;
+       }
+
+      /* If we have done all the ARGV-elements, stop the scan
+        and back over any non-options that we skipped and permuted.  */
+
+      if (optind == argc)
+       {
+         /* Set the next-arg-index to point at the non-options
+            that we previously skipped, so the caller will digest them.  */
+         if (first_nonopt != last_nonopt)
+           optind = first_nonopt;
+         return EOF;
+       }
+
+      /* If we have come to a non-option and did not permute it,
+        either stop the scan or describe it to the caller and pass it by.  */
+
+      if ((argv[optind][0] != '-' || argv[optind][1] == '\0')
+#ifdef GETOPT_COMPAT
+         && (longopts == NULL
+             || argv[optind][0] != '+' || argv[optind][1] == '\0')
+#endif                         /* GETOPT_COMPAT */
+         )
+       {
+         if (ordering == REQUIRE_ORDER)
+           return EOF;
+         optarg = argv[optind++];
+         return 1;
+       }
+
+      /* We have found another option-ARGV-element.
+        Start decoding its characters.  */
+
+      nextchar = (argv[optind] + 1
+                 + (longopts != NULL && argv[optind][1] == '-'));
+    }
+
+  if (longopts != NULL
+      && ((argv[optind][0] == '-'
+          && (argv[optind][1] == '-' || long_only))
+#ifdef GETOPT_COMPAT
+         || argv[optind][0] == '+'
+#endif                         /* GETOPT_COMPAT */
+         ))
+    {
+      const struct option *p;
+      char *s = nextchar;
+      int exact = 0;
+      int ambig = 0;
+      const struct option *pfound = NULL;
+      int indfound;
+
+      while (*s && *s != '=')
+       s++;
+
+      /* Test all options for either exact match or abbreviated matches.  */
+      for (p = longopts, option_index = 0; p->name;
+          p++, option_index++)
+       if (!strncmp (p->name, nextchar, s - nextchar))
+         {
+           if (s - nextchar == strlen (p->name))
+             {
+               /* Exact match found.  */
+               pfound = p;
+               indfound = option_index;
+               exact = 1;
+               break;
+             }
+           else if (pfound == NULL)
+             {
+               /* First nonexact match found.  */
+               pfound = p;
+               indfound = option_index;
+             }
+           else
+             /* Second nonexact match found.  */
+             ambig = 1;
+         }
+
+      if (ambig && !exact)
+       {
+         if (opterr)
+           fprintf (stderr, "%s: option `%s' is ambiguous\n",
+                    argv[0], argv[optind]);
+         nextchar += strlen (nextchar);
+         optind++;
+         return '?';
+       }
+
+      if (pfound != NULL)
+       {
+         option_index = indfound;
+         optind++;
+         if (*s)
+           {
+             /* Don't test has_arg with >, because some C compilers don't
+                allow it to be used on enums.  */
+             if (pfound->has_arg)
+               optarg = s + 1;
+             else
+               {
+                 if (opterr)
+                   {
+                     if (argv[optind - 1][1] == '-')
+                       /* --option */
+                       fprintf (stderr,
+                                "%s: option `--%s' doesn't allow an argument\n",
+                                argv[0], pfound->name);
+                     else
+                       /* +option or -option */
+                       fprintf (stderr,
+                            "%s: option `%c%s' doesn't allow an argument\n",
+                            argv[0], argv[optind - 1][0], pfound->name);
+                   }
+                 nextchar += strlen (nextchar);
+                 return '?';
+               }
+           }
+         else if (pfound->has_arg == 1)
+           {
+             if (optind < argc)
+               optarg = argv[optind++];
+             else
+               {
+                 if (opterr)
+                   fprintf (stderr, "%s: option `%s' requires an argument\n",
+                            argv[0], argv[optind - 1]);
+                 nextchar += strlen (nextchar);
+                 return '?';
+               }
+           }
+         nextchar += strlen (nextchar);
+         if (longind != NULL)
+           *longind = option_index;
+         if (pfound->flag)
+           {
+             *(pfound->flag) = pfound->val;
+             return 0;
+           }
+         return pfound->val;
+       }
+      /* Can't find it as a long option.  If this is not getopt_long_only,
+        or the option starts with '--' or is not a valid short
+        option, then it's an error.
+        Otherwise interpret it as a short option.  */
+      if (!long_only || argv[optind][1] == '-'
+#ifdef GETOPT_COMPAT
+         || argv[optind][0] == '+'
+#endif                         /* GETOPT_COMPAT */
+         || my_index (optstring, *nextchar) == NULL)
+       {
+         if (opterr)
+           {
+             if (argv[optind][1] == '-')
+               /* --option */
+               fprintf (stderr, "%s: unrecognized option `--%s'\n",
+                        argv[0], nextchar);
+             else
+               /* +option or -option */
+               fprintf (stderr, "%s: unrecognized option `%c%s'\n",
+                        argv[0], argv[optind][0], nextchar);
+           }
+         nextchar = (char *) "";
+         optind++;
+         return '?';
+       }
+    }
+
+  /* Look at and handle the next option-character.  */
+
+  {
+    char c = *nextchar++;
+    char *temp = my_index (optstring, c);
+
+    /* Increment `optind' when we start to process its last character.  */
+    if (*nextchar == '\0')
+      ++optind;
+
+    if (temp == NULL || c == ':')
+      {
+       if (opterr)
+         {
+           if (c < 040 || c >= 0177)
+             fprintf (stderr, "%s: unrecognized option, character code 0%o\n",
+                      argv[0], c);
+           else
+             fprintf (stderr, "%s: unrecognized option `-%c'\n", argv[0], c);
+         }
+       return '?';
+      }
+    if (temp[1] == ':')
+      {
+       if (temp[2] == ':')
+         {
+           /* This is an option that accepts an argument optionally.  */
+           if (*nextchar != '\0')
+             {
+               optarg = nextchar;
+               optind++;
+             }
+           else
+             optarg = 0;
+           nextchar = NULL;
+         }
+       else
+         {
+           /* This is an option that requires an argument.  */
+           if (*nextchar != '\0')
+             {
+               optarg = nextchar;
+               /* If we end this ARGV-element by taking the rest as an arg,
+                  we must advance to the next element now.  */
+               optind++;
+             }
+           else if (optind == argc)
+             {
+               if (opterr)
+                 fprintf (stderr, "%s: option `-%c' requires an argument\n",
+                          argv[0], c);
+               c = '?';
+             }
+           else
+             /* We already incremented `optind' once;
+                increment it again when taking next ARGV-elt as argument.  */
+             optarg = argv[optind++];
+           nextchar = NULL;
+         }
+      }
+    return c;
+  }
+}
+
+int
+getopt (argc, argv, optstring)
+     int argc;
+     char *const *argv;
+     const char *optstring;
+{
+  return _getopt_internal (argc, argv, optstring,
+                          (const struct option *) 0,
+                          (int *) 0,
+                          0);
+}
+\f
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+   the above definition of `getopt'.  */
+
+int
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int c;
+  int digit_optind = 0;
+
+  while (1)
+    {
+      int this_option_optind = optind ? optind : 1;
+
+      c = getopt (argc, argv, "abc:d:0123456789");
+      if (c == EOF)
+       break;
+
+      switch (c)
+       {
+       case '0':
+       case '1':
+       case '2':
+       case '3':
+       case '4':
+       case '5':
+       case '6':
+       case '7':
+       case '8':
+       case '9':
+         if (digit_optind != 0 && digit_optind != this_option_optind)
+           printf ("digits occur in two different argv-elements.\n");
+         digit_optind = this_option_optind;
+         printf ("option %c\n", c);
+         break;
+
+       case 'a':
+         printf ("option a\n");
+         break;
+
+       case 'b':
+         printf ("option b\n");
+         break;
+
+       case 'c':
+         printf ("option c with value `%s'\n", optarg);
+         break;
+
+       case '?':
+         break;
+
+       default:
+         printf ("?? getopt returned character code 0%o ??\n", c);
+       }
+    }
+
+  if (optind < argc)
+    {
+      printf ("non-option ARGV-elements: ");
+      while (optind < argc)
+       printf ("%s ", argv[optind++]);
+      printf ("\n");
+    }
+
+  exit (0);
+}
+
+#endif /* TEST */
diff --git a/lib/getopt.h b/lib/getopt.h
new file mode 100644 (file)
index 0000000..93a5cf7
--- /dev/null
@@ -0,0 +1,125 @@
+/* Declarations for getopt.
+   Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by the
+   Free Software Foundation; either version 2, or (at your option) any
+   later version.
+   
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+   
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+#ifndef _GETOPT_H
+#define _GETOPT_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* For communication from `getopt' to the caller.
+   When `getopt' finds an option that takes an argument,
+   the argument value is returned here.
+   Also, when `ordering' is RETURN_IN_ORDER,
+   each non-option ARGV-element is returned here.  */
+
+extern char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+   This is used for communication to and from the caller
+   and for communication between successive calls to `getopt'.
+
+   On entry to `getopt', zero means this is the first call; initialize.
+
+   When `getopt' returns EOF, this is the index of the first of the
+   non-option elements that the caller should itself scan.
+
+   Otherwise, `optind' communicates from one call to the next
+   how much of ARGV has been scanned so far.  */
+
+extern int optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+   for unrecognized options.  */
+
+extern int opterr;
+
+/* Describe the long-named options requested by the application.
+   The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+   of `struct option' terminated by an element containing a name which is
+   zero.
+
+   The field `has_arg' is:
+   no_argument         (or 0) if the option does not take an argument,
+   required_argument   (or 1) if the option requires an argument,
+   optional_argument   (or 2) if the option takes an optional argument.
+
+   If the field `flag' is not NULL, it points to a variable that is set
+   to the value given in the field `val' when the option is found, but
+   left unchanged if the option is not found.
+
+   To have a long-named option do something other than set an `int' to
+   a compiled-in constant, such as set a value from `optarg', set the
+   option's `flag' field to zero and its `val' field to a nonzero
+   value (the equivalent single-letter option character, if there is
+   one).  For long options that have a zero `flag' field, `getopt'
+   returns the contents of the `val' field.  */
+
+struct option
+{
+#if    __STDC__
+  const char *name;
+#else
+  char *name;
+#endif
+  /* has_arg can't be an enum because some compilers complain about
+     type mismatches in all the code that assumes it is an int.  */
+  int has_arg;
+  int *flag;
+  int val;
+};
+
+/* Names for the values of the `has_arg' field of `struct option'.  */
+
+#define        no_argument             0
+#define required_argument      1
+#define optional_argument      2
+
+#if __STDC__
+#if defined(__GNU_LIBRARY__)
+/* Many other libraries have conflicting prototypes for getopt, with
+   differences in the consts, in stdlib.h.  To avoid compilation
+   errors, only prototype getopt for the GNU C library.  */
+extern int getopt (int argc, char *const *argv, const char *shortopts);
+#else /* not __GNU_LIBRARY__ */
+extern int getopt ();
+#endif /* not __GNU_LIBRARY__ */
+extern int getopt_long (int argc, char *const *argv, const char *shortopts,
+                       const struct option *longopts, int *longind);
+extern int getopt_long_only (int argc, char *const *argv,
+                            const char *shortopts,
+                            const struct option *longopts, int *longind);
+
+/* Internal only.  Users should not call this directly.  */
+extern int _getopt_internal (int argc, char *const *argv,
+                            const char *shortopts,
+                            const struct option *longopts, int *longind,
+                            int long_only);
+#else /* not __STDC__ */
+extern int getopt ();
+extern int getopt_long ();
+extern int getopt_long_only ();
+
+extern int _getopt_internal ();
+#endif /* not __STDC__ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _GETOPT_H */
diff --git a/lib/getopt1.c b/lib/getopt1.c
new file mode 100644 (file)
index 0000000..36eb7cd
--- /dev/null
@@ -0,0 +1,153 @@
+/* Getopt for GNU.
+   Copyright (C) 1987, 88, 89, 90, 91, 1992 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by the
+   Free Software Foundation; either version 2, or (at your option) any
+   later version.
+   
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+   
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
+\f
+#include "getopt.h"
+
+#ifndef __STDC__
+#define const
+#endif
+
+#if defined(STDC_HEADERS) || defined(__GNU_LIBRARY__) || defined (LIBC)
+#include <stdlib.h>
+#else /* STDC_HEADERS or __GNU_LIBRARY__ */
+char *getenv ();
+#endif /* STDC_HEADERS or __GNU_LIBRARY__ */
+
+#ifndef        NULL
+#define NULL 0
+#endif
+
+int
+getopt_long (argc, argv, options, long_options, opt_index)
+     int argc;
+     char *const *argv;
+     const char *options;
+     const struct option *long_options;
+     int *opt_index;
+{
+  return _getopt_internal (argc, argv, options, long_options, opt_index, 0);
+}
+
+/* Like getopt_long, but '-' as well as '--' can indicate a long option.
+   If an option that starts with '-' (not '--') doesn't match a long option,
+   but does match a short option, it is parsed as a short option
+   instead.  */
+
+int 
+getopt_long_only (argc, argv, options, long_options, opt_index)
+     int argc;
+     char *const *argv;
+     const char *options;
+     const struct option *long_options;
+     int *opt_index;
+{
+  return _getopt_internal (argc, argv, options, long_options, opt_index, 1);
+}
+\f
+#ifdef TEST
+
+#include <stdio.h>
+
+int
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int c;
+  int digit_optind = 0;
+
+  while (1)
+    {
+      int this_option_optind = optind ? optind : 1;
+      int option_index = 0;
+      static struct option long_options[] =
+      {
+       {"add", 1, 0, 0},
+       {"append", 0, 0, 0},
+       {"delete", 1, 0, 0},
+       {"verbose", 0, 0, 0},
+       {"create", 0, 0, 0},
+       {"file", 1, 0, 0},
+       {0, 0, 0, 0}
+      };
+
+      c = getopt_long (argc, argv, "abc:d:0123456789",
+                      long_options, &option_index);
+      if (c == EOF)
+       break;
+
+      switch (c)
+       {
+       case 0:
+         printf ("option %s", long_options[option_index].name);
+         if (optarg)
+           printf (" with arg %s", optarg);
+         printf ("\n");
+         break;
+
+       case '0':
+       case '1':
+       case '2':
+       case '3':
+       case '4':
+       case '5':
+       case '6':
+       case '7':
+       case '8':
+       case '9':
+         if (digit_optind != 0 && digit_optind != this_option_optind)
+           printf ("digits occur in two different argv-elements.\n");
+         digit_optind = this_option_optind;
+         printf ("option %c\n", c);
+         break;
+
+       case 'a':
+         printf ("option a\n");
+         break;
+
+       case 'b':
+         printf ("option b\n");
+         break;
+
+       case 'c':
+         printf ("option c with value `%s'\n", optarg);
+         break;
+
+       case 'd':
+         printf ("option d with value `%s'\n", optarg);
+         break;
+
+       case '?':
+         break;
+
+       default:
+         printf ("?? getopt returned character code 0%o ??\n", c);
+       }
+    }
+
+  if (optind < argc)
+    {
+      printf ("non-option ARGV-elements: ");
+      while (optind < argc)
+       printf ("%s ", argv[optind++]);
+      printf ("\n");
+    }
+
+  exit (0);
+}
+
+#endif /* TEST */
diff --git a/lib/getugroups.c b/lib/getugroups.c
new file mode 100644 (file)
index 0000000..4ac6dfc
--- /dev/null
@@ -0,0 +1,86 @@
+/* getugroups.c -- return a list of the groups a user is in
+   Copyright (C) 1990, 1991 Free Software Foundation.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Written by David MacKenzie. */
+
+#include <sys/types.h>
+#include <grp.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/* Even though SunOS 4, Ultrix 4, and 386BSD are mostly POSIX.1 compliant,
+   their getgroups system call (except in the `System V' environment, which
+   is troublesome in other ways) fills in an array of int, not gid_t
+   (which is `short' on those systems).  We do the same, for consistency.
+   Kludge, kludge.  */
+
+#ifdef _POSIX_VERSION
+#if !defined(sun) && !defined(ultrix) && !defined(__386BSD__)
+#define GETGROUPS_T gid_t
+#else /* sun or ultrix or 386BSD */
+#define GETGROUPS_T int
+#endif /* sun or ultrix or 386BSD */
+#else /* not _POSIX_VERSION */
+#define GETGROUPS_T int
+#endif /* not _POSIX_VERSION */
+
+/* setgrent, getgrent, and endgrent are not specified by POSIX.1,
+   so header files might not declare them.
+   If you don't have them at all, we can't implement this function.
+   You lose!  */
+struct group *getgrent ();
+
+#if defined(USG) || defined(STDC_HEADERS)
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+/* Like `getgroups', but for user USERNAME instead of for
+   the current process. */
+
+int
+getugroups (maxcount, grouplist, username)
+     int maxcount;
+     GETGROUPS_T *grouplist;
+     char *username;
+{
+  struct group *grp;
+  register char **cp;
+  register int count = 0;
+
+  setgrent ();
+  while ((grp = getgrent ()) != 0)
+    for (cp = grp->gr_mem; *cp; ++cp)
+      if (!strcmp (username, *cp))
+       {
+         if (maxcount != 0)
+           {
+             if (count >= maxcount)
+               {
+                 endgrent ();
+                 return count;
+               }
+             grouplist[count] = grp->gr_gid;
+           }
+         count++;
+       }
+  endgrent ();
+  return count;
+}
diff --git a/lib/getusershell.c b/lib/getusershell.c
new file mode 100644 (file)
index 0000000..6e2e0c1
--- /dev/null
@@ -0,0 +1,196 @@
+/* getusershell.c -- Return names of valid user shells.
+   Copyright (C) 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Written by David MacKenzie <djm@gnu.ai.mit.edu> */
+
+#ifndef SHELLS_FILE
+/* File containing a list of nonrestricted shells, one per line. */
+#define SHELLS_FILE "/etc/shells"
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+char *malloc ();
+char *realloc ();
+#endif
+
+static int readname ();
+
+/* List of shells to use if the shells file is missing. */
+static char *default_shells[] =
+{
+  "/bin/sh", "/bin/csh", "/usr/bin/sh", "/usr/bin/csh", NULL
+};
+
+/* Index of the next shell in `default_shells' to return.
+   0 means we are not using `default_shells'. */
+static int default_index = 0;
+
+/* Input stream from the shells file. */
+static FILE *shellstream = NULL;
+
+/* Line of input from the shells file. */
+static char *line = NULL;
+
+/* Number of bytes allocated for `line'. */
+static int line_size = 0;
+\f
+/* Return an entry from the shells file, ignoring comment lines.
+   Return NULL if there are no more entries.  */
+
+char *
+getusershell ()
+{
+  if (default_index > 0)
+    {
+      if (default_shells[default_index])
+       /* Not at the end of the list yet.  */
+       return default_shells[default_index++];
+      return NULL;
+    }
+
+  if (shellstream == NULL)
+    {
+      shellstream = fopen (SHELLS_FILE, "r");
+      if (shellstream == NULL)
+       {
+         /* No shells file.  Use the default list.  */
+         default_index = 1;
+         return default_shells[0];
+       }
+    }
+
+  while (readname (&line, &line_size, shellstream))
+    {
+      if (*line != '#')
+       return line;
+    }
+  return NULL;                 /* End of file. */
+}
+
+/* Rewind the shells file. */
+
+void
+setusershell ()
+{
+  default_index = 0;
+  if (shellstream == NULL)
+    shellstream = fopen (SHELLS_FILE, "r");
+  else
+    fseek (shellstream, 0L, 0);
+}
+
+/* Close the shells file. */
+
+void
+endusershell ()
+{
+  if (shellstream)
+    {
+      fclose (shellstream);
+      shellstream = NULL;
+    }
+}
+
+/* Allocate N bytes of memory dynamically, with error checking.  */
+
+static char *
+xmalloc (n)
+     unsigned n;
+{
+  char *p;
+
+  p = malloc (n);
+  if (p == 0)
+    {
+      fprintf (stderr, "virtual memory exhausted\n");
+      exit (1);
+    }
+  return p;
+}
+
+/* Reallocate space P to size N, with error checking.  */
+
+static char *
+xrealloc (p, n)
+     char *p;
+     unsigned n;
+{
+  p = realloc (p, n);
+  if (p == 0)
+    {
+      fprintf (stderr, "virtual memory exhausted\n");
+      exit (1);
+    }
+  return p;
+}
+
+/* Read a line from STREAM, removing any newline at the end.
+   Place the result in *NAME, which is malloc'd
+   and/or realloc'd as necessary and can start out NULL,
+   and whose size is passed and returned in *SIZE.
+
+   Return the number of characters placed in *NAME
+   if some nonempty sequence was found, otherwise 0.  */
+
+static int
+readname (name, size, stream)
+     char **name;
+     int *size;
+     FILE *stream;
+{
+  int c;
+  int name_index = 0;
+
+  if (*name == NULL)
+    {
+      *size = 10;
+      *name = (char *) xmalloc (*size);
+    }
+
+  /* Skip blank space.  */
+  while ((c = getc (stream)) != EOF && isspace (c))
+    /* Do nothing. */ ;
+  
+  while (c != EOF && !isspace (c))
+    {
+      (*name)[name_index++] = c;
+      while (name_index >= *size)
+       {
+         *size *= 2;
+         *name = (char *) xrealloc (*name, *size);
+       }
+      c = getc (stream);
+    }
+  (*name)[name_index] = '\0';
+  return name_index;
+}
+
+#ifdef TEST
+main ()
+{
+  char *s;
+
+  while (s = getusershell ())
+    puts (s);
+  exit (0);
+}
+#endif
diff --git a/lib/mktime.c b/lib/mktime.c
new file mode 100644 (file)
index 0000000..1c5bf64
--- /dev/null
@@ -0,0 +1,224 @@
+/* Copyright (C) 1991 Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB.  If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA.  */
+
+#include <sys/types.h>
+#include <errno.h>
+#ifndef STDC_HEADERS
+extern int errno;
+#endif
+#ifdef TM_IN_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#else
+#define        LONG_MAX (~(1 << (sizeof (long) * 8 - 1)))
+#define LONG_MIN (-LONG_MAX - 1)
+#define        INT_MAX (~(1 << (sizeof (int) * 8 - 1)))
+#define INT_MIN (-INT_MAX - 1)
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+#ifndef __isleap
+/* Nonzero if YEAR is a leap year (every 4 years,
+   except every 100th isn't, and every 1000th is).  */
+#define        __isleap(year)  \
+  ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 1000 == 0))
+#endif
+
+/* How many days are in each month.  */
+static unsigned short int __mon_lengths[2][12] =
+{
+  /* Normal years.  */
+  { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+  /* Leap years.  */
+  { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
+};
+
+#define        invalid()       return (time_t) -1
+
+/* Return the `time_t' representation of TP and normalizes TP.
+   Return (time_t) -1 if TP is not representable as a `time_t'.
+   Note that 31 Dec 1969 23:59:59 is not representable
+   because it is represented as (time_t) -1.  */
+time_t
+mktime(tp)
+register struct tm *tp;
+{
+  static struct tm min, max;
+  static char init = 0;
+
+  register time_t result;
+  register time_t t;
+  register int i;
+  register unsigned short *l;
+  register struct tm *new;
+  time_t end;
+
+  if (tp == NULL)
+    {
+      errno = EINVAL;
+      invalid();
+    }
+
+  if (!init)
+    {
+      init = 1;
+      end = (time_t) LONG_MIN;
+      new = gmtime(&end);
+      if (new != NULL)
+       min = *new;
+      else
+       min.tm_sec = min.tm_min = min.tm_hour =
+         min.tm_mday = min.tm_mon = min.tm_year = INT_MIN;
+
+      end = (time_t) LONG_MAX;
+      new = gmtime(&end);
+      if (new != NULL)
+       max = *new;
+      else
+       max.tm_sec = max.tm_min = max.tm_hour =
+         max.tm_mday = max.tm_mon = max.tm_year = INT_MAX;
+    }
+
+  /* Make all the elements of TP that we pay attention to
+     be within the ranges of reasonable values for those things.  */
+#define        normalize(elt, min, max, nextelt) \
+  while (tp->elt < min)                                                              \
+    {                                                                        \
+      --tp->nextelt;                                                         \
+      tp->elt += max + 1;                                                    \
+    }                                                                        \
+  while (tp->elt > max)                                                              \
+    {                                                                        \
+      ++tp->nextelt;                                                         \
+      tp->elt -= max + 1;                                                    \
+    }
+
+  normalize (tm_sec, 0, 59, tm_min);
+  normalize (tm_min, 0, 59, tm_hour);
+  normalize (tm_hour, 0, 24, tm_mday);
+
+  /* Normalize the month first so we can use
+     it to figure the range for the day.  */
+  normalize (tm_mon, 0, 11, tm_year);
+  normalize (tm_mday, 1, __mon_lengths[__isleap (tp->tm_year)][tp->tm_mon],
+            tm_mon);
+
+  /* Normalize the month again, since normalizing
+     the day may have pushed it out of range.  */
+  normalize (tm_mon, 0, 11, tm_year);
+
+  /* Normalize the day again, because normalizing
+     the month may have changed the range.  */
+  normalize (tm_mday, 1, __mon_lengths[__isleap (tp->tm_year)][tp->tm_mon],
+            tm_mon);
+
+  /* Check for out-of-range values.  */
+#define        lowhigh(field, minmax, cmp)     (tp->field cmp minmax.field)
+#define        low(field)                      lowhigh(field, min, <)
+#define        high(field)                     lowhigh(field, max, >)
+#define        oor(field)                      (low(field) || high(field))
+#define        lowbound(field)                 (tp->field == min.field)
+#define        highbound(field)                (tp->field == max.field)
+  if (oor(tm_year))
+    invalid();
+  else if (lowbound(tm_year))
+    {
+      if (low(tm_mon))
+       invalid();
+      else if (lowbound(tm_mon))
+       {
+         if (low(tm_mday))
+           invalid();
+         else if (lowbound(tm_mday))
+           {
+             if (low(tm_hour))
+               invalid();
+             else if (lowbound(tm_hour))
+               {
+                 if (low(tm_min))
+                   invalid();
+                 else if (lowbound(tm_min))
+                   {
+                     if (low(tm_sec))
+                       invalid();
+                   }
+               }
+           }
+       }
+    }
+  else if (highbound(tm_year))
+    {
+      if (high(tm_mon))
+       invalid();
+      else if (highbound(tm_mon))
+       {
+         if (high(tm_mday))
+           invalid();
+         else if (highbound(tm_mday))
+           {
+             if (high(tm_hour))
+               invalid();
+             else if (highbound(tm_hour))
+               {
+                 if (high(tm_min))
+                   invalid();
+                 else if (highbound(tm_min))
+                   {
+                     if (high(tm_sec))
+                       invalid();
+                   }
+               }
+           }
+       }
+    }
+
+  t = 0;
+  for (i = 1970; i > 1900 + tp->tm_year; --i)
+    t -= __isleap(i) ? 366 : 365;
+  for (i = 1970; i < 1900 + tp->tm_year; ++i)
+    t += __isleap(i) ? 366 : 365;
+  l = __mon_lengths[__isleap(1900 + tp->tm_year)];
+  for (i = 0; i < tp->tm_mon; ++i)
+    t += l[i];
+  t += tp->tm_mday - 1;
+  result = ((t * 60 * 60 * 24) +
+           (tp->tm_hour * 60 * 60) +
+           (tp->tm_min * 60) +
+           tp->tm_sec);
+
+  end = result;
+#if 0                          /* This code breaks it, on SunOS anyway. */
+  if (tp->tm_isdst < 0)
+    new = localtime(&end);
+  else
+#endif
+    new = gmtime(&end);
+  if (new == NULL)
+    invalid();
+  new->tm_isdst = tp->tm_isdst;
+  *tp = *new;
+
+  return result;
+}
diff --git a/lib/posixtm.y b/lib/posixtm.y
new file mode 100644 (file)
index 0000000..bb5b40e
--- /dev/null
@@ -0,0 +1,173 @@
+/* Parse dates for touch.
+   Copyright (C) 1989, 1990, 1991 Free Software Foundation Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Written by Jim Kingdon and David MacKenzie. */
+%{
+#ifdef __GNUC__
+#define alloca __builtin_alloca
+#else
+#ifdef sparc
+#include <alloca.h>
+#else
+#ifdef _AIX
+ #pragma alloca
+#else
+char *alloca ();
+#endif
+#endif
+#endif
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <time.h>
+
+#define YYDEBUG 1
+
+/* Lexical analyzer's current scan position in the input string. */
+static char *curpos;
+
+/* The return value. */
+static struct tm t;
+
+time_t mktime ();
+
+#define yyparse posixtime_yyparse
+static int yylex ();
+static int yyerror ();
+%}
+
+%token DIGIT
+
+%%
+date :
+       digitpair /* month */
+       digitpair /* day */
+       digitpair /* hours */
+       digitpair /* minutes */
+       year
+       seconds {
+                if ($1 >= 1 && $1 <= 12)
+                  t.tm_mon = $1 - 1;
+                else {
+                  YYABORT;
+                }
+                if ($2 >= 1 && $2 <= 31)
+                  t.tm_mday = $2;
+                else {
+                  YYABORT;
+                }
+                if ($3 >= 0 && $3 <= 23)
+                  t.tm_hour = $3;
+                else {
+                  YYABORT;
+                }
+                if ($4 >= 0 && $4 <= 59)
+                  t.tm_min = $4;
+                else {
+                  YYABORT;
+                }
+              }
+
+year : digitpair {
+                   t.tm_year = $1;
+                  /* Deduce the century based on the year.
+                     See POSIX.2 section 4.63.3.  */
+                  if ($1 <= 68)
+                    t.tm_year += 100;
+                }
+    | digitpair digitpair {
+                            t.tm_year = $1 * 100 + $2;
+                           if (t.tm_year < 1900) {
+                             YYABORT;
+                           } else
+                             t.tm_year -= 1900;
+                         }
+    | /* empty */ {
+                    time_t now;
+                   struct tm *tmp;
+
+                    /* Use current year.  */
+                    time (&now);
+                   tmp = localtime (&now);
+                   t.tm_year = tmp->tm_year;
+                 }
+    ;
+
+seconds : /* empty */ {
+                        t.tm_sec = 0;
+                     }
+        | '.' digitpair {
+                         if ($2 >= 0 && $2 <= 61)
+                           t.tm_sec = $2;
+                         else {
+                           YYABORT;
+                         }
+                       }
+        ;
+
+digitpair : DIGIT DIGIT {
+                          $$ = $1 * 10 + $2;
+                       }
+          ;
+%%
+static int
+yylex ()
+{
+  char ch = *curpos++;
+
+  if (ch >= '0' && ch <= '9')
+    {
+      yylval = ch - '0';
+      return DIGIT;
+    }
+  else if (ch == '.' || ch == 0)
+    return ch;
+  else
+    return '?';                        /* Cause an error.  */
+}
+
+static int
+yyerror ()
+{
+  return 0;
+}
+
+/* Parse a POSIX-style date and return it, or (time_t)-1 for an error.  */
+
+time_t
+posixtime (s)
+     char *s;
+{
+  curpos = s;
+  /* Let mktime decide whether it is daylight savings time.  */
+  t.tm_isdst = -1;
+  if (yyparse ())
+    return (time_t)-1;
+  else
+    return mktime (&t);
+}
+
+/* Parse a POSIX-style date and return it, or NULL for an error.  */
+
+struct tm *
+posixtm (s)
+     char *s;
+{
+  if (posixtime (s) == -1)
+    return NULL;
+  return &t;
+}
diff --git a/lib/putenv.c b/lib/putenv.c
new file mode 100644 (file)
index 0000000..512878c
--- /dev/null
@@ -0,0 +1,101 @@
+/* Copyright (C) 1991 Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB.  If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA.  */
+
+#include <sys/types.h>
+#include <errno.h>
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+extern int errno;
+#endif
+
+#if defined(STDC_HEADERS) || defined(USG)
+#include <string.h>
+#define index strchr
+#define bcopy(s, d, n) memcpy((d), (s), (n))
+#else /* not (STDC_HEADERS or USG) */
+#include <strings.h>
+#endif /* STDC_HEADERS or USG */
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+#if !__STDC__
+#define const
+#endif
+
+extern char **environ;
+
+/* Put STRING, which is of the form "NAME=VALUE", in the environment.  */
+int
+putenv (string)
+     const char *string;
+{
+  char *name_end = index (string, '=');
+  register size_t size;
+  register char **ep;
+
+  if (name_end == NULL)
+    {
+      /* Remove the variable from the environment.  */
+      size = strlen (string);
+      for (ep = environ; *ep != NULL; ++ep)
+       if (!strncmp (*ep, string, size) && (*ep)[size] == '=')
+         {
+           while (ep[1] != NULL)
+             {
+               ep[0] = ep[1];
+               ++ep;
+             }
+           *ep = NULL;
+           return 0;
+         }
+    }
+
+  size = 0;
+  for (ep = environ; *ep != NULL; ++ep)
+    if (!strncmp (*ep, string, name_end - string) &&
+       (*ep)[name_end - string] == '=')
+      break;
+    else
+      ++size;
+
+  if (*ep == NULL)
+    {
+      static char **last_environ = NULL;
+      char **new_environ = (char **) malloc ((size + 2) * sizeof (char *));
+      if (new_environ == NULL)
+       return -1;
+      (void) bcopy ((char *) environ, (char *) new_environ, size * sizeof (char *));
+      new_environ[size] = (char *) string;
+      new_environ[size + 1] = NULL;
+      if (last_environ != NULL)
+       free ((char *) last_environ);
+      last_environ = new_environ;
+      environ = new_environ;
+    }
+  else
+    *ep = (char *) string;
+
+  return 0;
+}
diff --git a/lib/stime.c b/lib/stime.c
new file mode 100644 (file)
index 0000000..e647953
--- /dev/null
@@ -0,0 +1,34 @@
+/* stime -- set the system clock
+   Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* David MacKenzie <djm@ai.mit.edu> */
+
+#include <sys/types.h>
+#include <sys/time.h>
+
+/* Set the system time to *WHEN seconds past the start of 1970 GMT. */
+
+int
+stime (when)
+     time_t *when;
+{
+  struct timeval tv;
+
+  tv.tv_sec = *when;
+  tv.tv_usec = 0;
+  return settimeofday (&tv, (struct timezone *) 0);
+}
diff --git a/lib/strcspn.c b/lib/strcspn.c
new file mode 100644 (file)
index 0000000..ea61aa1
--- /dev/null
@@ -0,0 +1,37 @@
+/* Copyright (C) 1991 Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB.  If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA.  */
+
+char *index ();
+
+/* Return the length of the maximum inital segment of S
+   which contains no characters from REJECT.  */
+int
+strcspn (s, reject)
+     register char *s;
+     register char *reject;
+{
+  register int count = 0;
+
+  while (*s != '\0')
+    if (index (reject, *s++) == 0)
+      ++count;
+    else
+      return count;
+
+  return count;
+}
diff --git a/lib/strftime.c b/lib/strftime.c
new file mode 100644 (file)
index 0000000..cc4953e
--- /dev/null
@@ -0,0 +1,428 @@
+/* strftime - custom formatting of date and/or time
+   Copyright (C) 1989, 1991, 1992 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Note: this version of strftime lacks locale support,
+   but it is standalone.
+
+   Performs `%' substitutions similar to those in printf.  Except
+   where noted, substituted fields have a fixed size; numeric fields are
+   padded if necessary.  Padding is with zeros by default; for fields
+   that display a single number, padding can be changed or inhibited by
+   following the `%' with one of the modifiers described below.  Unknown
+   field specifiers are copied as normal characters.  All other
+   characters are copied to the output without change.
+
+   Supports a superset of the ANSI C field specifiers.
+
+   Literal character fields:
+   %   %
+   n   newline
+   t   tab
+
+   Numeric modifiers (a nonstandard extension):
+   -   do not pad the field
+   _   pad the field with spaces
+
+   Time fields:
+   %H  hour (00..23)
+   %I  hour (01..12)
+   %k  hour ( 0..23)
+   %l  hour ( 1..12)
+   %M  minute (00..59)
+   %p  locale's AM or PM
+   %r  time, 12-hour (hh:mm:ss [AP]M)
+   %R  time, 24-hour (hh:mm)
+   %S  second (00..61)
+   %T  time, 24-hour (hh:mm:ss)
+   %X  locale's time representation (%H:%M:%S)
+   %Z  time zone (EDT), or nothing if no time zone is determinable
+
+   Date fields:
+   %a  locale's abbreviated weekday name (Sun..Sat)
+   %A  locale's full weekday name, variable length (Sunday..Saturday)
+   %b  locale's abbreviated month name (Jan..Dec)
+   %B  locale's full month name, variable length (January..December)
+   %c  locale's date and time (Sat Nov 04 12:02:33 EST 1989)
+   %C  century (00..99)
+   %d  day of month (01..31)
+   %e  day of month ( 1..31)
+   %D  date (mm/dd/yy)
+   %h  same as %b
+   %j  day of year (001..366)
+   %m  month (01..12)
+   %U  week number of year with Sunday as first day of week (00..53)
+   %w  day of week (0..6)
+   %W  week number of year with Monday as first day of week (00..53)
+   %x  locale's date representation (mm/dd/yy)
+   %y  last two digits of year (00..99)
+   %Y  year (1970...)
+
+   David MacKenzie <djm@gnu.ai.mit.edu> */
+
+#include <sys/types.h>
+#if defined(TM_IN_SYS_TIME) || (!defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME))
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+
+#if defined(HAVE_TZNAME)
+extern char *tzname[2];
+#endif
+
+#if !__STDC__
+#define const
+#endif
+
+/* Types of padding for numbers in date and time. */
+enum padding
+{
+  none, blank, zero
+};
+
+static char *days[] =
+{
+  "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
+};
+
+static char *months[] =
+{
+  "January", "February", "March", "April", "May", "June",
+  "July", "August", "September", "October", "November", "December"
+};
+
+/* Add character C to STRING and increment LENGTH,
+   unless LENGTH would exceed MAX. */
+
+#define add_char(c) (length + 1 <= max) && (string[length++] = (c))
+
+/* Add a 2 digit number to STRING, padding if specified.
+   Return the number of characters added, up to MAX. */
+
+static int
+add_num2 (string, num, max, pad)
+     char *string;
+     int num;
+     int max;
+     enum padding pad;
+{
+  int top = num / 10;
+  int length = 0;
+
+  if (top == 0 && pad == blank)
+    add_char (' ');
+  else if (top != 0 || pad == zero)
+    add_char (top + '0');
+  add_char (num % 10 + '0');
+  return length;
+}
+
+/* Add a 3 digit number to STRING, padding if specified.
+   Return the number of characters added, up to MAX. */
+
+static int
+add_num3 (string, num, max, pad)
+     char *string;
+     int num;
+     int max;
+     enum padding pad;
+{
+  int top = num / 100;
+  int mid = (num - top * 100) / 10;
+  int length = 0;
+
+  if (top == 0 && pad == blank)
+    add_char (' ');
+  else if (top != 0 || pad == zero)
+    add_char (top + '0');
+  if (mid == 0 && top == 0 && pad == blank)
+    add_char (' ');
+  else if (mid != 0 || top != 0 || pad == zero)
+    add_char (mid + '0');
+  add_char (num % 10 + '0');
+  return length;
+}
+
+/* Like strncpy except return the number of characters copied. */
+
+static int
+add_str (to, from, max)
+     char *to;
+     char *from;
+     int max;
+{
+  int i;
+
+  for (i = 0; from[i] && i <= max; ++i)
+    to[i] = from[i];
+  return i;
+}
+
+/* Return the week in the year of the time in TM, with the weeks
+   starting on Sundays. */
+
+static int
+sun_week (tm)
+     struct tm *tm;
+{
+  int dl;
+
+  /* Set `dl' to the day in the year of the last day of the week previous
+     to the one containing the day specified in TM.  If the day specified
+     in TM is in the first week of the year, `dl' will be negative or 0.
+     Otherwise, calculate the number of complete weeks before our week
+     (dl / 7) and add any partial week at the start of the year (dl % 7). */
+  dl = tm->tm_yday - tm->tm_wday;
+  return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
+}
+
+/* Return the week in the year of the time in TM, with the weeks
+   starting on Mondays. */
+
+static int
+mon_week (tm)
+     struct tm *tm;
+{
+  int dl, wday;
+
+  if (tm->tm_wday == 0)
+    wday = 6;
+  else
+    wday = tm->tm_wday - 1;
+  dl = tm->tm_yday - wday;
+  return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
+}
+
+#if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)
+char *
+zone_name (tp)
+     struct tm *tp;
+{
+  char *timezone ();
+  struct timeval tv;
+  struct timezone tz;
+
+  gettimeofday (&tv, &tz);
+  return timezone (tz.tz_minuteswest, tp->tm_isdst);
+}
+#endif
+
+/* Format the time given in TM according to FORMAT, and put the
+   results in STRING.
+   Return the number of characters (not including terminating null)
+   that were put into STRING, or 0 if the length would have
+   exceeded MAX. */
+
+size_t
+strftime (string, max, format, tm)
+     char *string;
+     size_t max;
+     const char *format;
+     const struct tm *tm;
+{
+  enum padding pad;            /* Type of padding to apply. */
+  size_t length = 0;           /* Characters put in STRING so far. */
+
+  for (; *format && length < max; ++format)
+    {
+      if (*format != '%')
+       add_char (*format);
+      else
+       {
+         ++format;
+         /* Modifiers: */
+         if (*format == '-')
+           {
+             pad = none;
+             ++format;
+           }
+         else if (*format == '_')
+           {
+             pad = blank;
+             ++format;
+           }
+         else
+           pad = zero;
+
+         switch (*format)
+           {
+             /* Literal character fields: */
+           case 0:
+           case '%':
+             add_char ('%');
+             break;
+           case 'n':
+             add_char ('\n');
+             break;
+           case 't':
+             add_char ('\t');
+             break;
+           default:
+             add_char (*format);
+             break;
+
+             /* Time fields: */
+           case 'H':
+           case 'k':
+             length +=
+               add_num2 (&string[length], tm->tm_hour, max - length,
+                         *format == 'H' ? pad : blank);
+             break;
+           case 'I':
+           case 'l':
+             {
+               int hour12;
+
+               if (tm->tm_hour == 0)
+                 hour12 = 12;
+               else if (tm->tm_hour > 12)
+                 hour12 = tm->tm_hour - 12;
+               else
+                 hour12 = tm->tm_hour;
+               length +=
+                 add_num2 (&string[length], hour12, max - length,
+                           *format == 'I' ? pad : blank);
+             }
+             break;
+           case 'M':
+             length +=
+               add_num2 (&string[length], tm->tm_min, max - length, pad);
+             break;
+           case 'p':
+             if (tm->tm_hour < 12)
+               add_char ('A');
+             else
+               add_char ('P');
+             add_char ('M');
+             break;
+           case 'r':
+             length +=
+               strftime (&string[length], max - length, "%I:%M:%S %p", tm);
+             break;
+           case 'R':
+             length +=
+               strftime (&string[length], max - length, "%H:%M", tm);
+             break;
+           case 'S':
+             length +=
+               add_num2 (&string[length], tm->tm_sec, max - length, pad);
+             break;
+           case 'T':
+             length +=
+               strftime (&string[length], max - length, "%H:%M:%S", tm);
+             break;
+           case 'X':
+             length +=
+               strftime (&string[length], max - length, "%H:%M:%S", tm);
+             break;
+           case 'Z':
+#ifdef HAVE_TM_ZONE
+             length += add_str (&string[length], tm->tm_zone, max - length);
+#else
+#ifdef HAVE_TZNAME
+             if (tm->tm_isdst && tzname[1] && *tzname[1])
+               length += add_str (&string[length], tzname[1], max - length);
+             else
+               length += add_str (&string[length], tzname[0], max - length);
+#else
+             length += add_str (&string[length], zone_name (tm), max - length);
+#endif
+#endif
+             break;
+
+             /* Date fields: */
+           case 'a':
+             add_char (days[tm->tm_wday][0]);
+             add_char (days[tm->tm_wday][1]);
+             add_char (days[tm->tm_wday][2]);
+             break;
+           case 'A':
+             length +=
+               add_str (&string[length], days[tm->tm_wday], max - length);
+             break;
+           case 'b':
+           case 'h':
+             add_char (months[tm->tm_mon][0]);
+             add_char (months[tm->tm_mon][1]);
+             add_char (months[tm->tm_mon][2]);
+             break;
+           case 'B':
+             length +=
+               add_str (&string[length], months[tm->tm_mon], max - length);
+             break;
+           case 'c':
+             length +=
+               strftime (&string[length], max - length,
+                         "%a %b %d %H:%M:%S %Z %Y", tm);
+             break;
+           case 'C':
+             length +=
+               add_num2 (&string[length], (tm->tm_year + 1900) / 100,
+                         max - length, pad);
+             break;
+           case 'd':
+             length +=
+               add_num2 (&string[length], tm->tm_mday, max - length, pad);
+             break;
+           case 'e':
+             length +=
+               add_num2 (&string[length], tm->tm_mday, max - length, blank);
+             break;
+           case 'D':
+             length +=
+               strftime (&string[length], max - length, "%m/%d/%y", tm);
+             break;
+           case 'j':
+             length +=
+               add_num3 (&string[length], tm->tm_yday + 1, max - length, pad);
+             break;
+           case 'm':
+             length +=
+               add_num2 (&string[length], tm->tm_mon + 1, max - length, pad);
+             break;
+           case 'U':
+             length +=
+               add_num2 (&string[length], sun_week (tm), max - length, pad);
+             break;
+           case 'w':
+             add_char (tm->tm_wday + '0');
+             break;
+           case 'W':
+             length +=
+               add_num2 (&string[length], mon_week (tm), max - length, pad);
+             break;
+           case 'x':
+             length +=
+               strftime (&string[length], max - length, "%m/%d/%y", tm);
+             break;
+           case 'y':
+             length +=
+               add_num2 (&string[length], tm->tm_year % 100,
+                         max - length, pad);
+             break;
+           case 'Y':
+             add_char ((tm->tm_year + 1900) / 1000 + '0');
+             length +=
+               add_num3 (&string[length],
+                         (1900 + tm->tm_year) % 1000, max - length, zero);
+             break;
+           }
+       }
+    }
+  add_char (0);
+  return length - 1;
+}
diff --git a/lib/strtod.c b/lib/strtod.c
new file mode 100644 (file)
index 0000000..4441102
--- /dev/null
@@ -0,0 +1,188 @@
+/* Copyright (C) 1991, 1992 Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB.  If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA.  */
+
+#include <errno.h>
+#include <ctype.h>
+#include <math.h>
+
+#if STDC_HEADERS
+#include <float.h>
+#include <stdlib.h>
+#include <string.h>
+#else
+#define NULL 0
+#define DBL_MAX 1.7976931348623159e+308
+#define DBL_MIN 2.2250738585072010e-308
+extern int errno;
+#endif
+#ifndef HUGE_VAL
+#define HUGE_VAL HUGE
+#endif
+
+#if !__STDC__
+#define const
+#endif
+
+/* Convert NPTR to a double.  If ENDPTR is not NULL, a pointer to the
+   character after the last one used in the number is put in *ENDPTR.  */
+double
+strtod (nptr, endptr)
+     const char *nptr;
+     char **endptr;
+{
+  register const char *s;
+  short int sign;
+
+  /* The number so far.  */
+  double num;
+
+  int got_dot;                 /* Found a decimal point.  */
+  int got_digit;               /* Seen any digits.  */
+
+  /* The exponent of the number.  */
+  long int exponent;
+
+  if (nptr == NULL)
+    {
+      errno = EINVAL;
+      goto noconv;
+    }
+
+  s = nptr;
+
+  /* Eat whitespace.  */
+  while (isspace (*s))
+    ++s;
+
+  /* Get the sign.  */
+  sign = *s == '-' ? -1 : 1;
+  if (*s == '-' || *s == '+')
+    ++s;
+
+  num = 0.0;
+  got_dot = 0;
+  got_digit = 0;
+  exponent = 0;
+  for (;; ++s)
+    {
+      if (isdigit (*s))
+       {
+         got_digit = 1;
+
+         /* Make sure that multiplication by 10 will not overflow.  */
+         if (num > DBL_MAX * 0.1)
+           /* The value of the digit doesn't matter, since we have already
+              gotten as many digits as can be represented in a `double'.
+              This doesn't necessarily mean the result will overflow.
+              The exponent may reduce it to within range.
+
+              We just need to record that there was another
+              digit so that we can multiply by 10 later.  */
+           ++exponent;
+         else
+           num = (num * 10.0) + (*s - '0');
+
+         /* Keep track of the number of digits after the decimal point.
+            If we just divided by 10 here, we would lose precision.  */
+         if (got_dot)
+           --exponent;
+       }
+      else if (!got_dot && *s == '.')
+       /* Record that we have found the decimal point.  */
+       got_dot = 1;
+      else
+       /* Any other character terminates the number.  */
+       break;
+    }
+
+  if (!got_digit)
+    goto noconv;
+
+  if (tolower (*s) == 'e')
+    {
+      /* Get the exponent specified after the `e' or `E'.  */
+      int save = errno;
+      char *end;
+      long int exp;
+
+      errno = 0;
+      ++s;
+      exp = strtol (s, &end, 10);
+      if (errno == ERANGE)
+       {
+         /* The exponent overflowed a `long int'.  It is probably a safe
+            assumption that an exponent that cannot be represented by
+            a `long int' exceeds the limits of a `double'.  */
+         if (endptr != NULL)
+           *endptr = end;
+         if (exp < 0)
+           goto underflow;
+         else
+           goto overflow;
+       }
+      else if (end == s)
+       /* There was no exponent.  Reset END to point to
+          the 'e' or 'E', so *ENDPTR will be set there.  */
+       end = (char *) s - 1;
+      errno = save;
+      s = end;
+      exponent += exp;
+    }
+
+  if (endptr != NULL)
+    *endptr = (char *) s;
+
+  if (num == 0.0)
+    return 0.0;
+
+  /* Multiply NUM by 10 to the EXPONENT power,
+     checking for overflow and underflow.  */
+
+  if (exponent < 0)
+    {
+      if (num < DBL_MIN * pow (10.0, (double) -exponent))
+       goto underflow;
+    }
+  else if (exponent > 0)
+    {
+      if (num > DBL_MAX * pow (10.0, (double) -exponent))
+       goto overflow;
+    }
+
+  num *= pow (10.0, (double) exponent);
+
+  return num * sign;
+
+overflow:
+  /* Return an overflow error.  */
+  errno = ERANGE;
+  return HUGE_VAL * sign;
+
+underflow:
+  /* Return an underflow error.  */
+  if (endptr != NULL)
+    *endptr = (char *) nptr;
+  errno = ERANGE;
+  return 0.0;
+
+noconv:
+  /* There was no number.  */
+  if (endptr != NULL)
+    *endptr = (char *) nptr;
+  return 0.0;
+}
diff --git a/lib/xmalloc.c b/lib/xmalloc.c
new file mode 100644 (file)
index 0000000..f989004
--- /dev/null
@@ -0,0 +1,65 @@
+/* xmalloc.c -- malloc with out of memory checking
+   Copyright (C) 1990, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+char *malloc ();
+char *realloc ();
+void free ();
+#endif
+
+void error ();
+
+/* Allocate N bytes of memory dynamically, with error checking.  */
+
+char *
+xmalloc (n)
+     unsigned n;
+{
+  char *p;
+
+  p = malloc (n);
+  if (p == 0)
+    /* Must exit with 2 for `cmp'.  */
+    error (2, 0, "virtual memory exhausted");
+  return p;
+}
+
+/* Change the size of an allocated block of memory P to N bytes,
+   with error checking.
+   If P is NULL, run xmalloc.
+   If N is 0, run free and return NULL.  */
+
+char *
+xrealloc (p, n)
+     char *p;
+     unsigned n;
+{
+  if (p == 0)
+    return xmalloc (n);
+  if (n == 0)
+    {
+      free (p);
+      return 0;
+    }
+  p = realloc (p, n);
+  if (p == 0)
+    /* Must exit with 2 for `cmp'.  */
+    error (2, 0, "virtual memory exhausted");
+  return p;
+}
diff --git a/old/sh-utils/ChangeLog b/old/sh-utils/ChangeLog
new file mode 100644 (file)
index 0000000..b3cb288
--- /dev/null
@@ -0,0 +1,696 @@
+Wed Oct 28 14:16:48 1992  David J. MacKenzie  (djm@goldman.gnu.ai.mit.edu)
+
+       * Version 1.8.
+
+       * stty.c: Accept Irix VRPRNT for VREPRINT.
+       From Jim Meyering.
+
+       * stty.c: Fix some type mismatches.  From Bruce Evans, bde@runx.oz.au.
+
+       * who.c (read_utmp): Close file on error.
+       From Bruce Evans.
+
+       * su.c, test.c: Add some decls.  From Bruce Evans.
+
+       * sleep.c (main): Arg to sleep is unsigned, not long.
+       From Bruce Evans.
+
+Fri Sep 11 00:25:52 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * echo.c, echo.1: New files.
+
+Thu Sep 10 18:42:44 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * pathchk.c (main): Don't strip trailing slashes from args;
+       they might make a path invalid.
+       (portable_chars_only, dir_ok): New functions.
+       (validate_path): Renamed from validate_new_path.
+       Call them.  Don't complain if a leading
+       dir doesn't exist.  Don't replace `parent' with a dir that
+       doesn't exist.  Don't print a message when falling back
+       from pathconf to constant values.
+
+       * who.c [!UTMP_FILE]: If _PATH_UTMP is defined, use it instead
+       of /etc/utmp.  From Marc Boucher <marc@cam.org>.
+
+Tue Aug 25 17:02:25 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * Version 1.7.
+
+       * groups.sh, nohup.sh: Add $(bindir) to front of path.
+
+Mon Aug 24 16:39:39 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * stty.c: make sane value for "min" 1, not 0.
+       From haible@ma2s2.mathematik.uni-karlsruhe.de (Bruno Haible).
+
+Sun Aug 23 03:02:07 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * id.c, test.c: Use NGROUPS_MAX if it's defined.  386BSD is like sun.
+
+Sat Aug 22 03:16:41 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * test.c: Rename STANDALONE to TEST_STANDALONE to avoid IBM RT
+       ACIS sys/param.h conflict.
+
+       * su.c (correct_password) [HAVE_SHADOW_H]: Try to get the
+       encrypted correct password from the shadow password file.
+
+Fri Jul 17 15:25:01 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * su.c, getusershell.c: New files.
+
+Fri Jul  3 15:08:43 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * stty.c, who.c: Change FOO_MISSING to HAVE_FOO.
+
+Fri Jun  5 01:49:29 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * strcspn.c: New file.
+
+       * expr.c: Misc. cleanups.
+
+       * expr.c (eval7): Renamed from eval6.
+       Give syntax error if no more args.  Don't coerce all values to numbers.
+       (eval6): New function.
+       (eval5): Accept == as a synonym for =.
+       (eval2): Coerce values to numbers for comparisons.
+       Above all from Dana Jacobsen (jacobsd@prism.cs.orst.edu).
+
+Thu Jun  4 19:32:09 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * printf.c (print_formatted): Move main loop into new function.
+       (main): Add an outer loop to use the format multiple times.
+       (verify): Don't reject a completely empty string.
+       Check errno (for overflow).
+
+       * false.sh, true.sh: New programs.  Oh, boy.
+
+Thu May 14 01:17:22 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * stty.c (set_mode): Support crt and dec modes partially if
+       necessary, so they work on, for example, Ultrix . . . .
+
+Wed May 13 14:47:45 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * stty.c (set_mode): Swap nl and -nl.  Have them also affect
+       output as well as input.
+
+Tue May 12 00:07:28 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * date.c (show_date): Use strftime for the whole conversion.
+
+Tue May  5 15:20:24 1992  David J. MacKenzie  (djm@hal)
+
+       * stty.c (wrapf): Print the formatted buffer; don't redo the
+       formatting using vprintf.
+
+Thu Apr 30 01:17:08 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * printf.c (xstrtol, xstrtoul, xstrtod, verify): New functions.
+       (main, print_direc): Use them.  Make error messages more specific.
+
+       * tee.c (tee): Only malloc and free the table of file descriptors
+       if >0 files are given.
+
+Fri Apr 17 11:56:48 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * pathchk.c (validate_new_path): Print the name of the component that
+       failed the length test, not the whole path.
+       From Andreas Schwab (schwab@ls5.informatik.uni-dortmund.de).
+
+Mon Apr  6 15:11:36 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * who.c (read_utmp): Check close return for error.
+       (print_heading): Align columns based on sizes of utmp members.
+       (who_am_i): Skip past /dev/ instead of skipping leading path.
+
+Mon Mar 16 23:47:03 1992  David J. MacKenzie  (djm@apple-gunkies.gnu.ai.mit.edu)
+
+       * date.c (show_date): Don't call strftime if FORMAT is the
+       empty string.
+
+       * date.c (main): Reorganize to reduce duplicated code.
+       Add -d option.
+       (usage): Document -d.
+       (set_date): Function removed.
+
+Tue Feb 11 16:12:18 1992  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * printf.c (print_esc): When a numeric escape is given,
+       don't call print_esc_char, and return 1 less.
+       From Thorston Ohl.
+
+Mon Jan 20 02:17:18 1992  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * Version 1.6.
+
+       * test.c: HAVE_MULTIPLE_GROUPS -> HAVE_GETGROUPS, for bash 1.11.
+
+Fri Jan 17 15:46:18 1992  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * expr.c (docolon): Use re_nsub to find the number of
+       subexpressions . . . From Karl Berry, who knows.
+
+Wed Dec 25 23:27:53 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * expr.c (docolon): Use the new way (re_regs.num_regs > 0) to find
+       out if there were any subexpressions, instead of the old way
+       (re_regs.start[1] >= 0), which can cause random memory
+       accesses with regex 0.1.  From Brian Matthews.
+
+Tue Dec 24 02:12:15 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * system.h, id.c, pathchk.c, tee.c: Change POSIX ifdefs to
+       HAVE_UNISTD_H and _POSIX_VERSION.
+
+Wed Dec 11 13:15:09 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * Version 1.5.
+
+       * expr.c (main): Set obscure_syntax to tell re_match to
+       allocate memory for the group registers.
+
+Mon Dec  9 16:03:14 1991  Charles Hannum  (mycroft at hal.gnu.ai.mit.edu)
+
+       * who.c (list_entries): Check type == USER_PROCESS if defined, for SysV.
+
+Sat Dec  7 00:32:02 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * Version 1.4.
+
+       * env, id, nice, pathchk, stty, tee, tty, uname: Change usage
+       messages and documentation to list long-named options starting
+       with `--' rather than `+'. 
+
+       * env.c (main), nice.c (main): Simplify test for which exit
+       status to use if exec fails.
+
+Fri Dec  6 23:49:42 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * tee.c (main) [POSIX]:  Use sigaction instead of signal, which
+       POSIX doesn't have.
+
+Fri Oct 18 00:31:35 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * test.c (two_arguments): Fix from Chet.
+
+       * expr.c: Include regex.h after sys/types.h, not before, so
+       size_t gets defined.
+
+       * test.c: New version, adapted from bash 1.10.
+
+       * id.c: GID_T -> GETGROUPS_T, for clarity.
+
+Sat Oct 12 14:38:34 1991  David J. MacKenzie  (djm at churchy.gnu.ai.mit.edu)
+
+       * configure: Define uid_t and gid_t as int if they're not
+       defined in sys/types.h.  That's probably right for old Unixes
+       and avoids trying to find the C preprocessor.
+
+Sat Sep 28 13:01:23 1991  David J. MacKenzie  (djm at churchy.gnu.ai.mit.edu)
+
+       * stty.c (set_mode): Make `raw' and `cooked' not change parity
+       and character size, which would probably make them useless on
+       7-bit lines.
+       Make `raw' set the `time' character to 0, not 1.
+       From Bruce Evans.
+
+       * nohup.sh: If creating nohup.out, give it mode 0600, for POSIX.
+
+Fri Sep 13 14:59:51 1991  David J. MacKenzie  (djm at churchy.gnu.ai.mit.edu)
+
+       * id.c [POSIX]: Always use sysconf to get NGROUPS_MAX.
+
+Thu Aug 29 14:43:07 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * test.c: Don't include sys/file.h if POSIX.
+       Use gid_t for getgroups.
+
+       * stty.c (set_mode): Use CEOF and CEOL instead of hardcoding them.
+       (display_speed): Fix a printf string type mismatch.
+       From Bruce Evans.
+
+Mon Aug 26 16:52:51 1991  David J. MacKenzie  (djm at pogo.gnu.ai.mit.edu)
+
+       * configure, src/Makefile.in, lib/Makefile.in: Only put $< in
+       Makefiles if VPATH is being used, because older makes don't
+       understand it.
+
+Mon Aug 19 01:57:46 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * Version 1.3.
+
+Sat Aug 17 22:48:15 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * src/Makefile.in (install): Install a link to test called '['.
+
+Wed Aug 14 12:22:57 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * test.c (unary_operator): Check first char of string, not its address.
+
+Sun Aug 11 18:10:30 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * Version 1.2.
+
+       * system.h: Define S_IFMT if needed, for test.c.
+
+       * test.c: New file, from bash.
+
+       * nice.c: Change +priority to +adjustment (more accurate).
+
+Sat Aug 10 13:09:51 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * stty.c [WINSIZE_IN_PTEM]: sys/ptem.h requires sys/stream.h.
+
+       * nice.c, configure: Use nice if available and setpriority is missing.
+
+Thu Aug  8 01:34:05 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * date.c: USG needs TZ=GMT0 for UCT timezone, also.
+
+       * stty.c: Add pass8 and litout modes.
+
+Sun Aug  4 22:45:51 1991  David J. MacKenzie  (djm at wheat-chex)
+
+       * Version 1.1.
+
+Fri Aug  2 13:22:31 1991  David J. MacKenzie  (djm at apple-gunkies)
+
+       * configure: Implement +srcdir.  Don't check for bison.
+
+       * stty.c: Don't change ixon in "sane" mode.
+
+       * configure: Use 1 instead of 255 for checking tzname,
+       because of signedness.
+
+Thu Aug  1 13:40:58 1991  David J. MacKenzie  (djm at apple-gunkies)
+
+       * printenv.c (main): Don't print the variable names when given
+       args, as people seem to use printenv in scripts after all . . . .
+
+       * stty.c: Don't change parity or character size settings in
+       "sane" mode.  The right values for those depend on the hardware.
+
+Wed Jul 31 01:19:01 1991  David J. MacKenzie  (djm at hal)
+
+       * stty.c [_AIX]: Include sys/ioctl.h -- needed on
+       AIX to get window size.
+
+Tue Jul 30 00:06:54 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * getdate.y: New file.
+       * date.c: Add -s option to set date in English.
+       * configure: Check for ftime.
+
+       * date.c: Remove COMPUTE_TM_ZONE code, which didn't work.
+       * configure: Instead of checking whether tzname is declared,
+       check whether it exists.
+
+       * logname.c (main): Go back to just printing an error message
+       if getlogin fails, as required by POSIX.
+
+       * stty.c (screen_columns, wrapf): New functions to implement
+       output wrapping.
+       Globally: use them.
+
+       * configure: Define uid_t and gid_t if sys/types.h doesn't.
+       * system.h: Define F_OK et al. if nothing else does.
+
+Mon Jul 29 21:11:16 1991  David J. MacKenzie  (djm at wombat.gnu.ai.mit.edu)
+
+       * pathchk.c (validate_new_path): Rearrange tests so that
+       pathconf is only called on existing directories.  Use access
+       instead of stat to determine directory searchability.
+       From Jim Meyering.
+
+       * stty.c, configure: Add WINSIZE_IN_PTEM and GWINSZ_BROKEN for SCO.
+
+Wed Jul 24 02:13:31 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * stty.c (sane_mode): Always set control chars to sane values.
+       Set min and time if they're different from eof and eol.
+
+       * whoami.c: Print UID as unsigned.
+       * logname.c: Do "whoami" if getlogin fails.
+
+       * logname.c (main): fprintf was missing an arg.
+
+Tue Jul 23 02:20:15 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * id.c: GID_T is int if ultrix as well as if sun.
+
+       * stty.c: Implement raw and cooked modes.
+
+Mon Jul 22 15:21:21 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * tee.c (main): close stdin and stdout to check for errors.
+
+       * stty.c: Use tcflag_t for termios bitmasks.
+       Use speed_t for speeds.  Use unsigned long for baud rates to
+       accomodate large values, and support 57600 and 115200 if available.
+
+       * date.c, configure: Instead of SIZE_T_MISSING,
+       define size_t if it's missing.
+
+       * id.c, whoami.c: Use uid_t and gid_t.
+
+       * id.c: If POSIX and not sun (bogus!), pass getgroups and
+       getugroups an array of gid_t instead of int.
+
+       * system.h: New file.
+       * Most programs: include it.
+
+Fri Jul 19 12:04:58 1991  David J. MacKenzie  (djm at apple-gunkies)
+
+       * env.c [!STDC_HEADERS]: Declare errno.
+       * printf.c, pathchk.c: Don't include errno.h; not needed.
+
+       * version.c: New file.
+       * All C programs: Link with it, to get version number in the
+       binary where at least `strings -' and grep can find it.
+
+       * pathchk.c (strip_trailing_slashes): Function removed; use
+       version in lib.
+
+Mon Jul 15 11:34:22 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * Version 1.0.
+
+       * pathchk.c: Always check whether _POSIX_PATH_MAX and
+       _POSIX_NAME_MAX need to be defined.
+       [POSIX]: If no PATH_MAX or NAME_MAX and pathconf for the path
+       returns -1 (some systems do this if the path does not exist),
+       use pathconf for "/". 
+
+Sun Jul 14 21:17:22 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * date.c (date_seconds): Function removed, replaced with
+       posixtm.y in lib.
+       (set_date): Change caller.
+       * configure: Check for bison.
+
+       * stty.c [!C_LINE_MISSING]: Add support for setting and
+       printing the line discipline.
+       * configure: Check for C_LINE_MISSING.
+
+       * configure: Check for Minix.
+
+Sat Jul 13 01:33:59 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * Add `man' directory and manual pages.
+       * configure: Set INSTALLDATA and MAN.
+
+       * id.c: Add #ifdefs for POSIX ways of getting max groups list size.
+       (print_group_list, print_full_info): Allocate list of groups
+       with malloc since its size might not be constant.
+
+       * nice.c (main): Don't adjust priority if printing it.
+       Default adjustment of 10, not 0.
+
+       * printf.c: Add \c escape and %b conversion.
+       Implement '*' for field width and precision.
+       Make all errors fatal.
+       (print_esc_string, print_esc): New functions.
+
+       * configure, date.c: Change SYS_TIME_H to TM_IN_SYS_TIME.
+       * configure: Always check where to find struct tm.
+
+       * yes.c: Rewrite to accept multiple arguments.
+
+       * Add groups.sh.
+
+Fri Jul 12 10:57:00 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * dirname.c: Move code from dirname function into main,
+       simplifying things quite a bit.  From Jim Meyering.
+       * Omit strdup from lib; no longer used.
+       * configure: Don't check for strdup.
+
+       * printenv.c (main): If args given, print the values in the order
+       given on the command line rather than the order given in the
+       environment.
+
+       * tee.c, tty.c (struct longopts): Revise to make short-option
+       equivalents clear.
+
+Thu Jul 11 12:46:11 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * nice.c: Add long options.
+
+       * Add date command and libraries it needs.
+       * configure: Updated.
+
+       * env.c: Add long options.  Use GNU putenv instead of custom
+       setenv function.
+
+       * id.c: Add long options.
+
+       * pathchk.c [POSIX]: Use pathconf if necessary to get NAME_MAX
+       and PATH_MAX. 
+
+       * nice.c: Use exit status required for nohup by POSIX.2
+       (nohup execs nice).
+
+       * sleep.c: Don't bother with hex and octal.
+
+       * env.c: Fix exit status for POSIX.2 draft 11.1.
+
+       * Many files: Remove private copies of xmalloc, error, xstrdup,
+       etc. to use shared versions.
+       Fix #includes for USG, STDC_HEADERS, POSIX.
+
+Mon Jul  8 18:56:24 1991  David J. MacKenzie  (djm at churchy.gnu.ai.mit.edu)
+
+       * date.c (main): For -u, set TZ to "" instead of "GMT0",
+       unless on HP-UX or Ultrix.
+
+       * Rename some feature-test macros.
+       * stime.c: Created from code in date.c.
+       * date.c (compute_tm_zone): New function.
+       (date_seconds, show_date): Use it.
+       (xmalloc, xrealloc): Functions removed; use xmalloc.c instead.
+
+Tue Jul  2 02:28:11 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * tee.c (tee): Report errors in closing files.
+
+Mon Mar 18 10:13:59 1991  Jeffrey A. Law  (law at geech.ai.mit.edu)
+
+       * date.c (date_seconds, show_date): #if COMPUTE_TMZONE then
+       compute the proper value to place in tm->tm_zone from
+       information returned by localtime and gettimeofday.
+
+Fri Apr 26 11:38:09 1991  David J. MacKenzie  (djm at mole.gnu.ai.mit.edu)
+
+       * stty.c: Define default values for control chars if necessary.
+       Complain about invalid options if no other options follow.
+       Use POSIX functions instead of ioctl, for manipulating termios.
+
+       * expr.c (main): Exit status was backwards.
+
+Thu Dec 20 00:36:01 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * id.c: Reorganization and many changes to fix bugs and POSIX
+       compliance problems.
+
+Mon Dec 10 03:09:13 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * stty.c: Don't declare printf and some other functions that
+       might have variable numbers of args in system header file decls.
+
+Tue Nov 14 23:37:22 1990  Roland McGrath  (roland at geech.ai.mit.edu)
+
+       * id.c (print_groups): Put spaces after commas.
+       (print_group): New fn, to print a group id.  Uses numeric fmt
+       unless -n, in which case it uses group names.
+       (print_groups): Call it.  Find the rgid and egid, and print them as
+       well as the supplementary groups.  Make sure we print each group only
+       once.
+
+Sun Sep 16 01:49:14 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * id.c (main): Add -G option for POSIX.2 draft 10.
+       Allow a username to be given.
+       (print_groups): New function from code in main.
+       (getugroups): New function.
+
+Sun Aug 12 00:32:01 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * env.c (main): Instead of setting _POSIX_OPTION_ORDER,
+       tell getopt to not permute, with `+'.
+
+Sat Aug 11 01:32:53 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * expr.c: Use regex.c library instead of private regex routines.
+
+       * nice.c (main): Add -n option for POSIX.2a.
+       (usage): New function.
+
+Fri Aug 10 23:58:11 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * who.c: Add -m, -i, -w options for POSIX.2a.
+
+Tue Aug  7 00:01:02 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * expr.c: Use exit directly instead of longjmp on error.
+       Use argv[0] instead of hardcoded "expr" in messages.
+       Make some functions void.
+
+Sat Aug  4 21:19:25 1990  David J. MacKenzie  (djm at pogo.ai.mit.edu)
+
+       * env.c: Change exit statuses for POSIX draft 10.
+
+Wed Jul  4 04:32:51 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * tee.c: Use error instead of perror_with_name and
+       out_of_memory. 
+
+Wed Jun 20 02:39:49 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * date.c: Change -DSETTOD to -DSTIME_MISSING, -DSIZE_T to
+       -DSIZE_T_IN_TYPES, and -DSTDC_HDRS to -DSTDC_HEADERS.
+       Declare some more functions.  Replace fatal, memory_out, and
+       nonfatal_perror with error.
+
+Mon Jun 18 00:16:52 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * stty.c: Add some Unix compatibility modes.
+
+Sat Jun 16 21:05:59 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * stty.c (display_changed, display_all): Print values of min
+       and time.
+
+Thu Jun 14 17:49:31 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * stty.c: Implement tab, backspace, etc. delay args.
+
+Thu May 31 12:25:40 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * nohup.sh: Don't ignore SIGTERM.
+       If ./nohup.out is unwritable, try $HOME/nohup.out.
+
+Thu May  3 22:33:32 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * who.c: Use error instead of fatal and fatal_perror.
+       (print_headings): Print headings in all caps, like SYSV does.
+       (list_entries): New function for -q to make it like SYSV -q.
+       (valid_entries): Function removed.
+
+Mon Apr  2 01:27:23 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * id.c (main): Don't strip off leading path from program name.
+       Revise a couple of error messages.
+
+       * whoami.c (main): Use geteuid, not getuid, for Unix compatibility.
+
+Tue Mar 20 14:28:25 1990  David J. MacKenzie  (djm at pogo.ai.mit.edu)
+
+       * tee.c (main): Pass list of files and its size as args to tee
+       rather than as global vars.  Exit with return value of tee
+       instead of always 0.
+       (tee): Use unbuffered I/O instead of stdio, for POSIX.
+       Return an error status.
+       (xwrite): New function.
+
+Tue Mar 13 00:38:13 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * who.c (who_am_i): Print heading before checking to see
+       whether there is an entry for the tty on stdin, for
+       consistency with the who function.
+       (main): Use argv[optind], not argv[1], as alternate file.
+
+Fri Mar  9 15:49:04 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * who.c: Rename UTMP to UTMP_FILE for compatibility with SysV
+       utmp.h.  Include some additional header files.
+       (main): Recognize some options from SysVr3 who. Call usage.
+       Set new global var `program_name' from argv[0].
+       (usage): New function.
+       (who): If -q given, only print count of users logged on.
+       (print_entry): New function to format an entry on the output;
+       make format more like that of the Unix who programs.
+       (print_heading): New function to print a line describing each
+       output field.
+       (who, who_am_i): Call print_entry and print_heading.
+       (valid_entries): New function to return count of nonempty
+       entries in utmp.
+       (search_entries): Compare with utmp tty field instead of
+       username field.  Don't assume null termination in utmp field.
+       (who_am_i): Print the entry for the tty on stdin rather than
+       the first entry found for the uid.  If hostname is not
+       available, use a null one instead of "<unknown>".
+       Don't hardcode max hostname length.
+       (idle_string): New function to format idle time field.
+       (fatal, fatal_perror): Use program_name instead of hardcoded "who"
+       in error messages.
+
+Tue Mar  6 00:59:03 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * printenv.c (main): Allow multiple variables to be specified.
+       (barf): Function removed.
+
+Sat Jan 20 18:41:48 1990  Jim Kingdon  (kingdon at geech)
+
+       * expr.c (nextarg): Do not pass *args to strcmp if NULL.
+
+Mon Dec 18 09:57:20 1989  David J. MacKenzie  (djm at hobbes.ai.mit.edu)
+
+       * printenv.c (main): Simplify error messages.
+
+Sat Dec 16 15:15:50 1989  David J. MacKenzie  (djm at hobbes.ai.mit.edu)
+
+       * expr.c: Indent to regularize spacing.
+       (cmpv, arithf): Change '#define foo (args)' to '#define foo(args)'
+       so they compile.
+       (docolon): Remove unused vars.
+       (multiply): Rename from times to avoid libc conflict.
+       (error): Include program name in message.
+       (xmalloc): Rename from Malloc.
+       (re_compiled): Rename from re_comp to avoid libc conflict.
+
+       * basename.c: Fix some weird indentation.
+       (main): Print a clearer usage message.
+       Use a simpler method for removing suffix, if given.
+       (fatal): Function no longer used; removed.
+       
+       * sleep.c: (main): Rename `time' to `seconds'.  Print usage
+       message if given no args.
+       Exit with status 0 instead of falling off end.
+       (error): Print to stderr, not stdout.
+
+       * tee.c: (main): Use getopt_long instead of custom parser,
+       and adjust usage message.
+       Use list of filenames in argv rather than making a copy.
+       (tee): New function created from the second half of main.
+       Fix bug where it tried to fclose a loop index instead of a stream.
+       (xmalloc): Ok to return 0 if 0 bytes requested.
+       (xrealloc): Unused function removed.
+
+       * whoami.c: Canonicalize usage message and fix error message.
+
+       * who.c: Declare some functions.
+       (fatal_perror): New function for printing errors after system
+       calls.
+       Global: Use it when appropriate.
+       (xmalloc): Return char *, not int.
+       (read_utmp): Ok if utmp file is empty.
+       Include filename in error messages.
+       (scan_entries): Adjust columns to line up better, particularly
+       when there are users with 8 character long usernames logged in.
+
+Sat Oct 28 13:20:43 1989  David J. MacKenzie  (djm at spiff)
+
+       * uname.c: Added long options.
+       global: changed the word `part' to the word `element'
+       (more precise).
+       (program_name, long_options): New variables.
+       (main): Support long options.
+       (usage): Add long options summary to message.
+\f
+Local Variables:
+mode: indented-text
+left-margin: 8
+version-control: never
+End:
diff --git a/old/sh-utils/NEWS b/old/sh-utils/NEWS
new file mode 100644 (file)
index 0000000..ee859dd
--- /dev/null
@@ -0,0 +1,14 @@
+Major changes in release 1.8:
+* add echo command
+* fix some incorrect warnings in pathchk
+* look at the right utmp file on 386BSD
+* date doesn't dump core on some systems now
+\f
+Major changes in release 1.7:
+* add su, who, true, false commands
+* add more tests to expr
+* fix printf program handling of \ escapes
+* printf can re-use format string for multiple groups of arguments
+* printf catches numeric conversion errors with an ANSI C library
+* stty nl and -nl were backwards
+* date can format an arbitrary date without setting it
diff --git a/src/basename.c b/src/basename.c
new file mode 100644 (file)
index 0000000..767dbaf
--- /dev/null
@@ -0,0 +1,78 @@
+/* basename -- strip directory and suffix from filenames
+   Copyright (C) 1990, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Usage: basename name [suffix]
+   NAME is a pathname; SUFFIX is a suffix to strip from it.
+
+   basename /usr/foo/lossage/functions.l
+   => functions.l
+   basename /usr/foo/lossage/functions.l .l
+   => functions
+   basename functions.lisp p
+   => functions.lis */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "system.h"
+
+char *basename ();
+void remove_suffix ();
+void strip_trailing_slashes ();
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  char *name;
+
+  if (argc == 1 || argc > 3)
+    {
+      fprintf (stderr, "Usage: %s name [suffix]\n", argv[0]);
+      exit (1);
+    }
+
+  strip_trailing_slashes (argv[1]);
+
+  name = basename (argv[1]);
+
+  if (argc == 3)
+    remove_suffix (name, argv[2]);
+
+  puts (name);
+
+  exit (0);
+}
+
+/* Remove SUFFIX from the end of NAME if it is there, unless NAME
+   consists entirely of SUFFIX. */
+
+void
+remove_suffix (name, suffix)
+     register char *name, *suffix;
+{
+  register char *np, *sp;
+
+  np = name + strlen (name);
+  sp = suffix + strlen (suffix);
+
+  while (np > name && sp > suffix)
+    if (*--np != *--sp)
+      return;
+  if (np > name)
+    *np = '\0';
+}
diff --git a/src/date.c b/src/date.c
new file mode 100644 (file)
index 0000000..bb4c581
--- /dev/null
@@ -0,0 +1,190 @@
+/* date - print or set the system date and time
+   Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Options:
+   -d DATESTR  Display the date DATESTR.
+   -s DATESTR  Set the date to DATESTR.
+   -u          Display or set the date in universal instead of local time.
+   +FORMAT     Specify custom date output format, described below.
+   MMDDhhmm[[CC]YY][.ss]       Set the date in the format described below.
+
+   If one non-option argument is given, it is used as the date to which
+   to set the system clock, and must have the format:
+   MM  month (01..12)
+   DD  day in month (01..31)
+   hh  hour (00..23)
+   mm  minute (00..59)
+   CC  first 2 digits of year (optional, defaults to current) (00..99)
+   YY  last 2 digits of year (optional, defaults to current) (00..99)
+   ss  second (00..61)
+
+   If a non-option argument that starts with a `+' is specified, it
+   is used to control the format in which the date is printed; it
+   can contain any of the `%' substitutions allowed by the strftime
+   function.  A newline is always added at the end of the output.
+
+   David MacKenzie <djm@gnu.ai.mit.edu> */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+
+/* This is portable and avoids bringing in all of the ctype stuff. */
+#define isdigit(c) ((c) >= '0' && (c) <= '9')
+
+#ifdef TM_IN_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+
+#ifndef STDC_HEADERS
+time_t mktime ();
+size_t strftime ();
+time_t time ();
+#endif
+
+int putenv ();
+int stime ();
+
+char *xrealloc ();
+time_t get_date ();
+time_t posixtime ();
+void error ();
+void show_date ();
+void usage ();
+
+/* putenv string to use Universal Coordinated Time.
+   POSIX.2 says it should be "TZ=UCT0" or "TZ=GMT0". */
+#ifndef TZ_UCT
+#if defined(hpux) || defined(__hpux__) || defined(ultrix) || defined(__ultrix__) || defined(USG)
+#define TZ_UCT "TZ=GMT0"
+#else
+#define TZ_UCT "TZ="
+#endif
+#endif
+
+/* The name this program was run with, for error messages. */
+char *program_name;
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int optc;
+  char *datestr = NULL;
+  time_t when;
+  int set_date = 0;
+  int universal_time = 0;
+
+  program_name = argv[0];
+
+  while ((optc = getopt (argc, argv, "d:s:u")) != EOF)
+    switch (optc)
+      {
+      case 'd':
+       datestr = optarg;
+       break;
+      case 's':
+       datestr = optarg;
+       set_date = 1;
+       break;
+      case 'u':
+       universal_time = 1;
+       break;
+      default:
+       usage ();
+      }
+
+  if (argc - optind > 1)
+    usage ();
+
+  if (universal_time && putenv (TZ_UCT) != 0)
+    error (1, 0, "virtual memory exhausted");
+
+  time (&when);
+
+  if (datestr)
+    when = get_date (datestr, NULL);
+
+  if (argc - optind == 1 && argv[optind][0] != '+')
+    {
+      when = posixtime (argv[optind]);
+      set_date = 1;
+    }
+
+  if (when == -1)
+    error (1, 0, "invalid date");
+
+  if (set_date && stime (&when) == -1)
+    error (0, errno, "cannot set date");
+
+  if (argc - optind == 1 && argv[optind][0] == '+')
+    show_date (argv[optind] + 1, when);
+  else
+    show_date ((char *) NULL, when);
+
+  exit (0);
+}
+
+/* Display the date and/or time in WHEN according to the format specified
+   in FORMAT, followed by a newline.  If FORMAT is NULL, use the
+   standard output format (ctime style but with a timezone inserted). */
+
+void
+show_date (format, when)
+     char *format;
+     time_t when;
+{
+  struct tm *tm;
+  char *out = NULL;
+  size_t out_length = 0;
+
+  tm = localtime (&when);
+
+  if (format == NULL)
+    /* Print the date in the default format.  Vanilla ANSI C strftime
+       doesn't support %e, but POSIX requires it.  If you don't use
+       a GNU strftime, make sure yours supports %e.  */
+    format = "%a %b %e %H:%M:%S %Z %Y";
+  else if (*format == '\0')
+    {
+      printf ("\n");
+      return;
+    }
+
+  do
+    {
+      out_length += 200;
+      out = (char *) xrealloc (out, out_length);
+    }
+  while (strftime (out, out_length, format, tm) == 0);
+
+  printf ("%s\n", out);
+  free (out);
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-u] [-d datestr] [-s datestr] [+FORMAT] [MMDDhhmm[[CC]YY][.ss]]\n",
+          program_name);
+  exit (1);
+}
diff --git a/src/dirname.c b/src/dirname.c
new file mode 100644 (file)
index 0000000..0561235
--- /dev/null
@@ -0,0 +1,57 @@
+/* dirname -- strip filename suffix from pathname
+   Copyright (C) 1990, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Written by David MacKenzie and Jim Meyering. */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "system.h"
+
+void strip_trailing_slashes ();
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  register char *path;
+  register char *slash;
+
+  if (argc != 2)
+    {
+      fprintf (stderr, "Usage: %s path\n", argv[0]);
+      exit (1);
+    }
+
+  path = argv[1];
+  strip_trailing_slashes (path);
+
+  slash = rindex (path, '/');
+  if (slash == NULL)
+    path = ".";
+  else
+    {
+      /* Remove any trailing slashes and final element. */
+      while (slash > path && *slash == '/')
+       --slash;
+      slash[1] = 0;
+    }
+  puts (path);
+
+  exit (0);
+}
+
diff --git a/src/echo.c b/src/echo.c
new file mode 100644 (file)
index 0000000..cae62ce
--- /dev/null
@@ -0,0 +1,179 @@
+/* echo.c, taken from Bash.
+Copyright (C) 1987, 1989, 1991, 1992 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2, or (at your option) any later
+version.
+
+Bash is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License along
+with Bash; see the file COPYING.  If not, write to the Free Software
+Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "system.h"
+
+/* echo [-neE] [arg ...]
+Output the ARGs.  If -n is specified, the trailing newline is
+suppressed.  If the -e option is given, interpretation of the
+following backslash-escaped characters is turned on:
+       \a      alert (bell)
+       \b      backspace
+       \c      suppress trailing newline
+       \f      form feed
+       \n      new line
+       \r      carriage return
+       \t      horizontal tab
+       \v      vertical tab
+       \\      backslash
+       \num    the character whose ASCII code is NUM (octal).
+
+You can explicitly turn off the interpretation of the above characters
+on System V systems with the -E option.
+*/
+
+#define V9_ECHO
+
+#if defined (V9_ECHO)
+#  if defined (USG)
+#    define VALID_ECHO_OPTIONS "neE"
+#  else
+#    define VALID_ECHO_OPTIONS "ne"
+#  endif /* !USG */
+#else /* !V9_ECHO */
+#  define VALID_ECHO_OPTIONS "n"
+#endif /* !V9_ECHO */
+
+/* Print the words in LIST to standard output.  If the first word is
+   `-n', then don't print a trailing newline.  We also support the
+   echo syntax from Version 9 unix systems. */
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int display_return = 1, do_v9 = 0;
+
+/* System V machines already have a /bin/sh with a v9 behaviour.  We
+   use the identical behaviour for these machines so that the
+   existing system shell scripts won't barf. */
+#if defined (V9_ECHO) && defined (USG)
+  do_v9 = 1;
+#endif
+
+  --argc;
+  ++argv;
+
+  while (argc > 0 && *argv[0] == '-')
+    {
+      register char *temp;
+      register int i;
+
+      /* If it appears that we are handling options, then make sure that
+        all of the options specified are actually valid.  Otherwise, the
+        string should just be echoed. */
+      temp = argv[0] + 1;
+
+      for (i = 0; temp[i]; i++)
+       {
+         if (rindex (VALID_ECHO_OPTIONS, temp[i]) == 0)
+           goto just_echo;
+       }
+
+      if (!*temp)
+       goto just_echo;
+
+      /* All of the options in TEMP are valid options to ECHO.
+        Handle them. */
+      while (*temp)
+       {
+         if (*temp == 'n')
+           display_return = 0;
+#if defined (V9_ECHO)
+         else if (*temp == 'e')
+           do_v9 = 1;
+#if defined (USG)
+         else if (*temp == 'E')
+           do_v9 = 0;
+#endif /* USG */
+#endif /* V9_ECHO */
+         else
+           goto just_echo;
+
+         temp++;
+       }
+      argc--;
+      argv++;
+    }
+
+just_echo:
+
+  if (argc > 0)
+    {
+#if defined (V9_ECHO)
+      if (do_v9)
+       {
+         while (argc > 0)
+           {
+             register char *s = argv[0];
+             register int c;
+
+             while (c = *s++)
+               {
+                 if (c == '\\' && *s)
+                   {
+                     switch (c = *s++)
+                       {
+                       case 'a': c = '\007'; break;
+                       case 'b': c = '\b'; break;
+                       case 'c': display_return = 0; continue;
+                       case 'f': c = '\f'; break;
+                       case 'n': c = '\n'; break;
+                       case 'r': c = '\r'; break;
+                       case 't': c = '\t'; break;
+                       case 'v': c = (int) 0x0B; break;
+                       case '0': case '1': case '2': case '3':
+                       case '4': case '5': case '6': case '7':
+                         c -= '0';
+                         if (*s >= '0' && *s <= '7')
+                           c = c * 8 + (*s++ - '0');
+                         if (*s >= '0' && *s <= '7')
+                           c = c * 8 + (*s++ - '0');
+                         break;
+                       case '\\': break;
+                       default:  putchar ('\\'); break;
+                       }
+                   }
+                 putchar(c);
+               }
+             argc--;
+             argv++;
+             if (argc > 0)
+               putchar(' ');
+           }
+       }
+      else
+#endif /* V9_ECHO */
+       {
+         while (argc > 0)
+           {
+             fputs (argv[0], stdout);
+             argc--;
+             argv++;
+             if (argc > 0)
+               putchar (' ');
+           }
+       }
+    }
+  if (display_return)
+    printf ("\n");
+  exit (0);
+}
diff --git a/src/env.c b/src/env.c
new file mode 100644 (file)
index 0000000..b322dee
--- /dev/null
+++ b/src/env.c
@@ -0,0 +1,168 @@
+/* env - run a program in a modified environment
+   Copyright (C) 1986, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Richard Mlynarik and David MacKenzie */
+
+/* Options:
+   -
+   -i
+   --ignore-environment
+       Construct a new environment from scratch; normally the
+       environment is inherited from the parent process, except as
+       modified by other options.
+
+   -u variable
+   --unset=variable
+       Unset variable VARIABLE (remove it from the environment).
+       If VARIABLE was not set, does nothing.
+
+   variable=value (an arg containing a "=" character)
+       Set the environment variable VARIABLE to value VALUE.  VALUE
+       may be of zero length ("variable=").  Setting a variable to a
+       zero-length value is different from unsetting it.
+
+   --
+       Indicate that the following argument is the program
+       to invoke.  This is necessary when the program's name
+       begins with "-" or contains a "=".
+
+   The first remaining argument specifies a program to invoke;
+   it is searched for according to the specification of the PATH
+   environment variable.  Any arguments following that are
+   passed as arguments to that program.
+
+   If no command name is specified following the environment
+   specifications, the resulting environment is printed.
+   This is like specifying a command name of "printenv".
+
+   Examples:
+
+   If the environment passed to "env" is
+       { LOGNAME=rms EDITOR=emacs PATH=.:/gnubin:/hacks }
+
+   env - foo
+       runs "foo" in a null environment.
+
+   env foo
+       runs "foo" in the environment
+       { LOGNAME=rms EDITOR=emacs PATH=.:/gnubin:/hacks }
+
+   env DISPLAY=gnu:0 nemacs
+       runs "nemacs" in the envionment
+       { LOGNAME=rms EDITOR=emacs PATH=.:/gnubin:/hacks DISPLAY=gnu:0 }
+
+   env - LOGNAME=foo /hacks/hack bar baz
+       runs the "hack" program on arguments "bar" and "baz" in an
+       environment in which the only variable is "LOGNAME".  Note that
+       the "-" option clears out the PATH variable, so one should be
+       careful to specify in which directory to find the program to
+       call.
+
+   env -u EDITOR LOGNAME=foo PATH=/energy -- e=mc2 bar baz
+       runs the program "/energy/e=mc2" with environment
+       { LOGNAME=foo PATH=/energy }
+*/
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+
+int putenv ();
+void error ();
+void usage ();
+
+extern char **environ;
+
+/* The name by which this program was run. */
+char *program_name;
+
+struct option longopts[] =
+{
+  {"ignore-environment", 0, NULL, 'i'},
+  {"unset", 1, NULL, 'u'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv, envp)
+     register int argc;
+     register char **argv;
+     char **envp;
+{
+  char *dummy_environ[1];
+  int optc;
+  int ignore_environment = 0;
+
+  program_name = argv[0];
+
+  while ((optc = getopt_long (argc, argv, "+iu:", longopts, (int *) 0)) != EOF)
+    {
+      switch (optc)
+       {
+       case 'i':
+         ignore_environment = 1;
+         break;
+       case 'u':
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (optind != argc && !strcmp (argv[optind], "-"))
+    ignore_environment = 1;
+  
+  environ = dummy_environ;
+  environ[0] = NULL;
+
+  if (!ignore_environment)
+    for (; *envp; envp++)
+      putenv (*envp);
+
+  optind = 0;                  /* Force GNU getopt to re-initialize. */
+  while ((optc = getopt_long (argc, argv, "+iu:", longopts, (int *) 0)) != EOF)
+    if (optc == 'u')
+      putenv (optarg);         /* Requires GNU putenv. */
+
+  if (optind != argc && !strcmp (argv[optind], "-"))
+    ++optind;
+
+  while (optind < argc && index (argv[optind], '='))
+    putenv (argv[optind++]);
+
+  /* If no program is specified, print the environment and exit. */
+  if (optind == argc)
+    {
+      while (*environ)
+       puts (*environ++);
+      exit (0);
+    }
+
+  execvp (argv[optind], &argv[optind]);
+  error (errno == ENOENT ? 127 : 126, errno, "%s", argv[optind]);
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-] [-i] [-u name] [--ignore-environment] [--unset=name]\n\
+       [name=value]... [command [args...]]\n",
+          program_name);
+  exit (2);
+}
diff --git a/src/expr.c b/src/expr.c
new file mode 100644 (file)
index 0000000..db95869
--- /dev/null
@@ -0,0 +1,672 @@
+/* expr -- evaluate expressions.
+   Copyright (C) 1986, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Author: Mike Parker.
+
+   This program evaluates expressions.  Each token (operator, operand,
+   parenthesis) of the expression must be a seperate argument.  The
+   parser used is a reasonably general one, though any incarnation of
+   it is language-specific.  It is especially nice for expressions.
+
+   No parse tree is needed; a new node is evaluated immediately.
+   One function can handle multiple operators all of equal precedence,
+   provided they all associate ((x op x) op x).
+
+   Define EVAL_TRACE to print an evaluation trace.  */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <regex.h>
+#include "system.h"
+
+#if !__STDC__
+#define const
+#endif
+
+#define NEW(type) ((type *) xmalloc (sizeof (type)))
+#define OLD(x) free ((char *) x)
+
+/* The kinds of value we can have.  */
+enum valtype
+{
+  integer,
+  string
+};
+typedef enum valtype TYPE;
+
+/* A value is.... */
+struct valinfo
+{
+  TYPE type;                   /* Which kind. */
+  union
+  {                            /* The value itself. */
+    int i;
+    char *s;
+  } u;
+};
+typedef struct valinfo VALUE;
+
+/* The arguments given to the program, minus the program name.  */
+char **args;
+
+/* The name this program was run with. */
+char *program_name;
+
+VALUE *docolon ();
+VALUE *eval ();
+VALUE *int_value ();
+VALUE *str_value ();
+char *xstrdup ();
+char *strstr ();
+char *xmalloc ();
+int isstring ();
+int nextarg ();
+int nomoreargs ();
+int null ();
+int toarith ();
+void error ();
+void freev ();
+void printv ();
+void tostring ();
+void trace ();
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  VALUE *v;
+
+  program_name = argv[0];
+
+  if (argc == 1)
+    {
+      fprintf (stderr, "Usage: %s expression...\n", argv[0]);
+      exit (1);
+    }
+  args = argv + 1;
+
+  v = eval ();
+  if (!nomoreargs ())
+    error (2, 0, "syntax error");
+  printv (v);
+
+  exit (null (v));
+}
+
+/* Return a VALUE for I.  */
+
+VALUE *
+int_value (i)
+     int i;
+{
+  VALUE *v;
+
+  v = NEW (VALUE);
+  v->type = integer;
+  v->u.i = i;
+  return v;
+}
+
+/* Return a VALUE for S.  */
+
+VALUE *
+str_value (s)
+     char *s;
+{
+  VALUE *v;
+
+  v = NEW (VALUE);
+  v->type = string;
+  v->u.s = xstrdup (s);
+  return v;
+}
+
+/* Free VALUE V, including structure components.  */
+
+void
+freev (v)
+     VALUE *v;
+{
+  if (v->type == string)
+    free (v->u.s);
+  OLD (v);
+}
+
+/* Print VALUE V.  */
+
+void
+printv (v)
+     VALUE *v;
+{
+  switch (v->type)
+    {
+    case integer:
+      printf ("%d\n", v->u.i);
+      break;
+    case string:
+      printf ("%s\n", v->u.s);
+      break;
+    }
+}
+
+/* Return nonzero if V is a null-string or zero-number.  */
+
+int
+null (v)
+     VALUE *v;
+{
+  switch (v->type)
+    {
+    case integer:
+      return v->u.i == 0;
+    case string:
+      return v->u.s[0] == '\0';
+    }
+}
+
+/* Return nonzero if V is a string value.  */
+
+int
+isstring (v)
+     VALUE *v;
+{
+  return v->type == string;
+}
+
+/* Coerce V to a string value (can't fail).  */
+
+void
+tostring (v)
+     VALUE *v;
+{
+  char *temp;
+
+  switch (v->type)
+    {
+    case integer:
+      temp = xmalloc (4 * (sizeof (int) / sizeof (char)));
+      sprintf (temp, "%d", v->u.i);
+      v->u.s = temp;
+      v->type = string;
+      break;
+    case string:
+      break;
+    }
+}
+
+/* Coerce V to an integer value.  Return 1 on success, 0 on failure.  */
+
+int
+toarith (v)
+     VALUE *v;
+{
+  int i;
+  int neg;
+  char *cp;
+
+  switch (v->type)
+    {
+    case integer:
+      return 1;
+    case string:
+      i = 0;
+      cp = v->u.s;
+      neg = (*cp == '-');
+      if (neg)
+       cp++;
+      for (; *cp; cp++)
+       {
+         if (isdigit (*cp))
+           i = i * 10 + *cp - '0';
+         else
+           return 0;
+       }
+      free (v->u.s);
+      v->u.i = i * (neg ? -1 : 1);
+      v->type = integer;
+      return 1;
+    }
+}
+
+/* Return nonzero if the next token matches STR exactly.
+   STR must not be NULL.  */
+
+int
+nextarg (str)
+     char *str;
+{
+  if (*args == NULL)
+    return 0;
+  return strcmp (*args, str) == 0;
+}
+
+/* Return nonzero if there no more tokens.  */
+
+int
+nomoreargs ()
+{
+  return *args == 0;
+}
+
+/* The comparison operator handling functions.  */
+
+#define cmpf(name, rel)                                \
+int name (l, r) VALUE *l; VALUE *r;            \
+{                                              \
+  if (isstring (l) || isstring (r))            \
+    {                                          \
+       tostring (l);                           \
+       tostring (r);                           \
+       return strcmp (l->u.s, r->u.s) rel 0;   \
+    }                                          \
+ else                                          \
+   return l->u.i rel r->u.i;                   \
+}
+
+cmpf (less_than, <)
+cmpf (less_equal, <=)
+cmpf (equal, ==)
+cmpf (not_equal, !=)
+cmpf (greater_equal, >=)
+cmpf (greater_than, >)
+
+#undef cmpf
+
+/* The arithmetic operator handling functions.  */
+
+#define arithf(name, op)                       \
+int name (l, r) VALUE *l; VALUE *r;            \
+{                                              \
+  if (!toarith (l) || !toarith (r))            \
+    error (2, 0, "non-numeric argument");      \
+  return l->u.i op r->u.i;                     \
+}
+
+arithf (plus, +)
+arithf (minus, -)
+arithf (multiply, *)
+arithf (divide, /)
+arithf (mod, %)
+
+#undef arithf
+
+#ifdef EVAL_TRACE
+/* Print evaluation trace and args remaining.  */
+
+void
+trace (fxn)
+     char *fxn;
+{
+  char **a;
+
+  printf ("%s:", fxn);
+  for (a = args; *a; a++)
+    printf (" %s", *a);
+  putchar ('\n');
+}
+#endif
+
+/* Do the : operator.
+   SV is the VALUE for the lhs (the string),
+   PV is the VALUE for the rhs (the pattern).  */
+
+VALUE *
+docolon (sv, pv)
+     VALUE *sv;
+     VALUE *pv;
+{
+  VALUE *v;
+  const char *errmsg;
+  struct re_pattern_buffer re_buffer;
+  struct re_registers re_regs;
+  int len;
+
+  tostring (sv);
+  tostring (pv);
+
+  len = strlen (pv->u.s);
+  re_buffer.allocated = 2 * len;
+  re_buffer.buffer = (unsigned char *) xmalloc (re_buffer.allocated);
+  re_buffer.translate = 0;
+  errmsg = re_compile_pattern (pv->u.s, len, &re_buffer);
+  if (errmsg)
+    error (2, 0, "%s", errmsg);
+
+  len = re_match (&re_buffer, sv->u.s, strlen (sv->u.s), 0, &re_regs);
+  if (len >= 0)
+    {
+      /* Were \(...\) used? */
+      if (re_buffer.re_nsub > 0)/* was (re_regs.start[1] >= 0) */
+       {
+         sv->u.s[re_regs.end[1]] = '\0';
+         v = str_value (sv->u.s + re_regs.start[1]);
+       }
+      else
+       v = int_value (len);
+    }
+  else
+    {
+      /* Match failed -- return the right kind of null.  */
+      if (strstr (pv->u.s, "\\("))
+       v = str_value ("");
+      else
+       v = int_value (0);
+    }
+  free (re_buffer.buffer);
+  return v;
+}
+
+/* Handle bare operands and ( expr ) syntax.  */
+
+VALUE *
+eval7 ()
+{
+  VALUE *v;
+
+#ifdef EVAL_TRACE
+  trace ("eval7");
+#endif
+  if (nomoreargs ())
+    error (2, 0, "syntax error");
+  else if (nextarg ("("))
+    {
+      args++;
+      v = eval ();
+      if (!nextarg (")"))
+       error (2, 0, "syntax error");
+      args++;
+      return v;
+    }
+  else if (nextarg (")"))
+    error (2, 0, "syntax error");
+  else
+    return str_value (*args++);
+}
+
+/* Handle match, substr, index, and length keywords.  */
+
+VALUE *
+eval6 ()
+{
+  VALUE *l;
+  VALUE *r;
+  VALUE *v;
+  VALUE *i1;
+  VALUE *i2;
+
+#ifdef EVAL_TRACE
+  trace ("eval6");
+#endif
+  if (nextarg ("length"))
+    {
+      args++;
+      r = eval6 ();
+      tostring (r);
+      v = int_value (strlen (r->u.s));
+      freev (r);
+      return v;
+    }
+  else if (nextarg ("match"))
+    {
+      args++;
+      l = eval6 ();
+      r = eval6 ();
+      v = docolon (l, r);
+      freev (l);
+      freev (r);
+      return v;
+    }
+  else if (nextarg ("index"))
+    {
+      args++;
+      l = eval6 ();
+      r = eval6 ();
+      tostring (l);
+      tostring (r);
+      v = int_value (strcspn (l->u.s, r->u.s) + 1);
+      if (v->u.i == strlen (l->u.s) + 1)
+       v->u.i = 0;
+      freev (l);
+      freev (r);
+      return v;
+    }
+  else if (nextarg ("substr"))
+    {
+      args++;
+      l = eval6 ();
+      i1 = eval6 ();
+      i2 = eval6 ();
+      tostring (l);
+      if (!toarith (i1) || !toarith (i2)
+         || i1->u.i > strlen (l->u.s)
+         || i1->u.i <= 0 || i2->u.i <= 0)
+       v = str_value ("");
+      else
+       {
+         v = NEW (VALUE);
+         v->type = string;
+         v->u.s = strncpy ((char *) xmalloc (i2->u.i + 1),
+                           l->u.s + i1->u.i - 1, i2->u.i);
+       }
+      freev (l);
+      freev (i1);
+      freev (i2);
+      return v;
+    }
+  else
+    return eval7 ();
+}
+
+/* Handle : operator (pattern matching).
+   Calls docolon to do the real work.  */
+
+VALUE *
+eval5 ()
+{
+  VALUE *l;
+  VALUE *r;
+  VALUE *v;
+
+#ifdef EVAL_TRACE
+  trace ("eval5");
+#endif
+  l = eval6 ();
+  while (1)
+    {
+      if (nextarg (":"))
+       {
+         args++;
+         r = eval6 ();
+         v = docolon (l, r);
+         freev (l);
+         freev (r);
+         l = v;
+       }
+      else
+       return l;
+    }
+}
+
+/* Handle *, /, % operators.  */
+
+VALUE *
+eval4 ()
+{
+  VALUE *l;
+  VALUE *r;
+  int (*fxn) ();
+  int val;
+
+#ifdef EVAL_TRACE
+  trace ("eval4");
+#endif
+  l = eval5 ();
+  while (1)
+    {
+      if (nextarg ("*"))
+       fxn = multiply;
+      else if (nextarg ("/"))
+       fxn = divide;
+      else if (nextarg ("%"))
+       fxn = mod;
+      else
+       return l;
+      args++;
+      r = eval5 ();
+      val = (*fxn) (l, r);
+      freev (l);
+      freev (r);
+      l = int_value (val);
+    }
+}
+
+/* Handle +, - operators.  */
+
+VALUE *
+eval3 ()
+{
+  VALUE *l;
+  VALUE *r;
+  int (*fxn) ();
+  int val;
+
+#ifdef EVAL_TRACE
+  trace ("eval3");
+#endif
+  l = eval4 ();
+  while (1)
+    {
+      if (nextarg ("+"))
+       fxn = plus;
+      else if (nextarg ("-"))
+       fxn = minus;
+      else
+       return l;
+      args++;
+      r = eval4 ();
+      val = (*fxn) (l, r);
+      freev (l);
+      freev (r);
+      l = int_value (val);
+    }
+}
+
+/* Handle comparisons.  */
+
+VALUE *
+eval2 ()
+{
+  VALUE *l;
+  VALUE *r;
+  int (*fxn) ();
+  int val;
+
+#ifdef EVAL_TRACE
+  trace ("eval2");
+#endif
+  l = eval3 ();
+  while (1)
+    {
+      if (nextarg ("<"))
+       fxn = less_than;
+      else if (nextarg ("<="))
+       fxn = less_equal;
+      else if (nextarg ("=") || nextarg ("=="))
+       fxn = equal;
+      else if (nextarg ("!="))
+       fxn = not_equal;
+      else if (nextarg (">="))
+       fxn = greater_equal;
+      else if (nextarg (">"))
+       fxn = greater_than;
+      else
+       return l;
+      args++;
+      r = eval3 ();
+      toarith (l);
+      toarith (r);
+      val = (*fxn) (l, r);
+      freev (l);
+      freev (r);
+      l = int_value (val);
+    }
+}
+
+/* Handle &.  */
+
+VALUE *
+eval1 ()
+{
+  VALUE *l;
+  VALUE *r;
+
+#ifdef EVAL_TRACE
+  trace ("eval1");
+#endif
+  l = eval2 ();
+  while (1)
+    {
+      if (nextarg ("&"))
+       {
+         args++;
+         r = eval2 ();
+         if (null (l) || null (r))
+           {
+             freev (l);
+             freev (r);
+             l = int_value (0);
+           }
+         else
+           freev (r);
+       }
+      else
+       return l;
+    }
+}
+
+/* Handle |.  */
+
+VALUE *
+eval ()
+{
+  VALUE *l;
+  VALUE *r;
+
+#ifdef EVAL_TRACE
+  trace ("eval");
+#endif
+  l = eval1 ();
+  while (1)
+    {
+      if (nextarg ("|"))
+       {
+         args++;
+         r = eval1 ();
+         if (null (l))
+           {
+             freev (l);
+             l = r;
+           }
+         else
+           freev (r);
+       }
+      else
+       return l;
+    }
+}
diff --git a/src/groups.sh b/src/groups.sh
new file mode 100755 (executable)
index 0000000..7d84058
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+# groups -- print the groups a user is in
+# Copyright (C) 1991 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# Written by David MacKenzie <djm@gnu.ai.mit.edu>.
+
+# Make sure we get GNU id, if possible; also allow
+# it to be somewhere else in PATH if not installed yet.
+PATH=@bindir@:$PATH
+
+if [ $# -eq 0 ]; then
+  id -Gn
+else
+  for name in "$@"; do
+    echo $name : `id -Gn $name`
+  done
+fi
diff --git a/src/id.c b/src/id.c
new file mode 100644 (file)
index 0000000..beddc13
--- /dev/null
+++ b/src/id.c
@@ -0,0 +1,346 @@
+/* id -- print real and effective UIDs and GIDs
+   Copyright (C) 1989, 1991 Free Software Foundation.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Written by Arnold Robbins, arnold@audiofax.com.
+   Major rewrite by David MacKenzie, djm@gnu.ai.mit.edu. */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include "system.h"
+
+#ifdef _POSIX_VERSION
+#include <limits.h>
+#if !defined(NGROUPS_MAX) || NGROUPS_MAX < 1
+#define NGROUPS_MAX sysconf (_SC_NGROUPS_MAX)
+#endif /* !NGROUPS_MAX */
+
+/* Even though SunOS 4, Ultrix 4, and 386BSD are mostly POSIX.1 compliant,
+   their getgroups system call (except in the `System V' environment, which
+   is troublesome in other ways) fills in an array of int, not gid_t
+   (which is `short' on those systems).  Kludge, kludge.  */
+
+#if !defined(sun) && !defined(ultrix) && !defined(__386BSD__)
+#define GETGROUPS_T gid_t
+#else /* sun or ultrix or 386BSD */
+#define GETGROUPS_T int
+#endif /* sun or ultrix or 386BSD */
+#else /* not _POSIX_VERSION */
+struct passwd *getpwuid ();
+struct group *getgrgid ();
+uid_t getuid ();
+gid_t getgid ();
+uid_t geteuid ();
+gid_t getegid ();
+#include <sys/param.h>
+#if !defined(NGROUPS_MAX) && defined(NGROUPS)
+#define NGROUPS_MAX NGROUPS
+#endif /* not NGROUPS_MAX and NGROUPS */
+#define GETGROUPS_T int
+#endif /* not _POSIX_VERSION */
+
+char *xmalloc ();
+int getugroups ();
+void error ();
+void print_user ();
+void print_group ();
+void print_group_list ();
+void print_full_info ();
+void usage ();
+
+/* The name this program was run with. */
+char *program_name;
+
+/* If nonzero, output only the group ID(s). -g */
+int just_group = 0;
+
+/* If nonzero, output user/group name instead of ID number. -n */
+int use_name = 0;
+
+/* If nonzero, output real UID/GID instead of default effective UID/GID. -r */
+int use_real = 0;
+
+/* If nonzero, output only the user ID(s). -u */
+int just_user = 0;
+
+/* If nonzero, output only the supplementary groups. -G */
+int just_group_list = 0;
+
+/* The real and effective IDs of the user to print. */
+uid_t ruid, euid;
+gid_t rgid, egid;
+
+/* The number of errors encountered so far. */
+int problems = 0;
+
+struct option longopts[] =
+{
+  {"group", 0, NULL, 'g'},
+  {"name", 0, NULL, 'n'},
+  {"real", 0, NULL, 'r'},
+  {"user", 0, NULL, 'u'},
+  {"groups", 0, NULL, 'G'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int optc;
+
+  program_name = argv[0];
+
+  while ((optc = getopt_long (argc, argv, "gnruG", longopts, (int *) 0))
+        != EOF)
+    {
+      switch (optc)
+       {
+       case 'g':
+         just_group = 1;
+         break;
+       case 'n':
+         use_name = 1;
+         break;
+       case 'r':
+         use_real = 1;
+         break;
+       case 'u':
+         just_user = 1;
+         break;
+       case 'G':
+         just_group_list = 1;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (just_user + just_group + just_group_list > 1)
+    error (1, 0, "cannot print only user and only group");
+
+  if (just_user + just_group + just_group_list == 0 && (use_real || use_name))
+    error (1, 0, "cannot print only names or real IDs in default format");
+
+  if (argc - optind > 1)
+    usage ();
+
+  if (argc - optind == 1)
+    {
+      struct passwd *pwd = getpwnam (argv[optind]);
+      if (pwd == NULL)
+       error (1, 0, "%s: No such user", argv[optind]);
+      ruid = euid = pwd->pw_uid;
+      rgid = egid = pwd->pw_gid;
+    }
+  else
+    {
+      euid = geteuid ();
+      ruid = getuid ();
+      egid = getegid ();
+      rgid = getgid ();
+    }
+
+  if (just_user)
+    print_user (use_real ? ruid : euid);
+  else if (just_group)
+    print_group (use_real ? rgid : egid);
+  else if (just_group_list)
+    print_group_list (argv[optind]);
+  else
+    print_full_info (argv[optind]);
+  putchar ('\n');
+
+  exit (problems != 0);
+}
+
+/* Print the name or value of user ID UID. */
+
+void
+print_user (uid)
+     int uid;
+{
+  struct passwd *pwd = NULL;
+
+  if (use_name)
+    {
+      pwd = getpwuid (uid);
+      if (pwd == NULL)
+       problems++;
+    }
+
+  if (pwd == NULL)
+    printf ("%u", (unsigned) uid);
+  else
+    printf ("%s", pwd->pw_name);
+}
+
+/* Print the name or value of group ID GID. */
+
+void
+print_group (gid)
+     int gid;
+{
+  struct group *grp = NULL;
+
+  if (use_name)
+    {
+      grp = getgrgid (gid);
+      if (grp == NULL)
+       problems++;
+    }
+
+  if (grp == NULL)
+    printf ("%u", (unsigned) gid);
+  else
+    printf ("%s", grp->gr_name);
+}
+
+/* Print all of the distinct groups the user is in . */
+
+void
+print_group_list (username)
+     char *username;
+{
+  print_group (rgid);
+  if (egid != rgid)
+    {
+      putchar (' ');
+      print_group (egid);
+    }
+
+#ifdef NGROUPS_MAX
+  {
+    int ngroups;
+    GETGROUPS_T *groups;
+    register int i;
+
+    groups = (GETGROUPS_T *) xmalloc (NGROUPS_MAX * sizeof (GETGROUPS_T));
+    if (username == 0)
+      ngroups = getgroups (NGROUPS_MAX, groups);
+    else
+      ngroups = getugroups (NGROUPS_MAX, groups, username);
+    if (ngroups < 0)
+      {
+       error (0, errno, "cannot get supplemental group list");
+       problems++;
+       free (groups);
+       return;
+      }
+
+    for (i = 0; i < ngroups; i++)
+      if (groups[i] != rgid && groups[i] != egid)
+       {
+         putchar (' ');
+         print_group (groups[i]);
+       }
+    free (groups);
+  }
+#endif
+}
+
+/* Print all of the info about the user's user and group IDs. */
+
+void
+print_full_info (username)
+     char *username;
+{
+  struct passwd *pwd;
+  struct group *grp;
+
+  printf ("uid=%u", (unsigned) ruid);
+  pwd = getpwuid (ruid);
+  if (pwd == NULL)
+    problems++;
+  else
+    printf ("(%s)", pwd->pw_name);
+  
+  printf (" gid=%u", (unsigned) rgid);
+  grp = getgrgid (rgid);
+  if (grp == NULL)
+    problems++;
+  else
+    printf ("(%s)", grp->gr_name);
+  
+  if (euid != ruid)
+    {
+      printf (" euid=%u", (unsigned) euid);
+      pwd = getpwuid (euid);
+      if (pwd == NULL)
+       problems++;
+      else
+       printf ("(%s)", pwd->pw_name);
+    }
+  
+  if (egid != rgid)
+    {
+      printf (" egid=%u", (unsigned) egid);
+      grp = getgrgid (egid);
+      if (grp == NULL)
+       problems++;
+      else
+       printf ("(%s)", grp->gr_name);
+    }
+
+#ifdef NGROUPS_MAX
+  {
+    int ngroups;
+    GETGROUPS_T *groups;
+    register int i;
+
+    groups = (GETGROUPS_T *) xmalloc (NGROUPS_MAX * sizeof (GETGROUPS_T));
+    if (username == 0)
+      ngroups = getgroups (NGROUPS_MAX, groups);
+    else
+      ngroups = getugroups (NGROUPS_MAX, groups, username);
+    if (ngroups < 0)
+      {
+       error (0, errno, "cannot get supplemental group list");
+       problems++;
+       free (groups);
+       return;
+      }
+
+    if (ngroups > 0)
+      fputs (" groups=", stdout);
+    for (i = 0; i < ngroups; i++)
+      {
+       if (i > 0)
+         putchar (',');
+       printf ("%u", (unsigned) groups[i]);
+       grp = getgrgid (groups[i]);
+       if (grp == NULL)
+         problems++;
+       else
+         printf ("(%s)", grp->gr_name);
+      }
+    free (groups);
+  }
+#endif
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-gnruG] [--group] [--name] [--real] [--user] [--groups] [username]\n",
+          program_name);
+  exit (1);
+}
diff --git a/src/logname.c b/src/logname.c
new file mode 100644 (file)
index 0000000..39d2137
--- /dev/null
@@ -0,0 +1,43 @@
+/* logname -- print user's login name
+   Copyright (C) 1990, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "system.h"
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  register char *cp;
+
+  if (argc != 1)
+    {
+      fprintf (stderr, "Usage: %s\n", argv[0]);
+      exit (1);
+    }
+
+  cp = getlogin ();
+  if (cp)
+    {
+      puts (cp);
+      exit (0);
+    }
+  fprintf (stderr,"%s: no login name\n", argv[0]);
+  exit (1);
+}
diff --git a/src/nice.c b/src/nice.c
new file mode 100644 (file)
index 0000000..4911fcc
--- /dev/null
@@ -0,0 +1,150 @@
+/* nice -- run a program with modified scheduling priority
+   Copyright (C) 1990, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* David MacKenzie <djm@ai.mit.edu> */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#ifndef NICE_PRIORITY
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+#include "system.h"
+
+int isinteger ();
+void error ();
+void usage ();
+
+/* The name this program was run with. */
+char *program_name;
+
+struct option longopts[] =
+{
+  {"adjustment", 1, NULL, 'n'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int current_priority;
+  int adjustment = 0;
+  int minusflag = 0;
+  int adjustment_given = 0;
+  int optc;
+
+  program_name = argv[0];
+
+  while ((optc = getopt_long (argc, argv, "+0123456789-n:", longopts,
+                             (int *) 0)) != EOF)
+    {
+      switch (optc)
+       {
+       case '?':
+         usage ();
+         
+       case 'n':
+         if (!isinteger (optarg))
+           error (1, 0, "invalid priority `%s'", optarg);
+         adjustment = atoi (optarg);
+         adjustment_given = 1;
+         break;
+
+       case '-':
+         minusflag = 1;
+         break;
+
+       default:
+         adjustment = adjustment * 10 + optc - '0';
+         adjustment_given = 1;
+       }
+    }
+
+  if (minusflag)
+    adjustment = -adjustment;
+  if (!adjustment_given)
+    adjustment = 10;
+
+  if (optind == argc)
+    {
+      if (adjustment_given)
+       usage ();
+      /* No command given; print the priority. */
+      errno = 0;
+#ifndef NICE_PRIORITY
+      current_priority = getpriority (PRIO_PROCESS, 0);
+#else
+      current_priority = nice (0);
+#endif
+      if (current_priority == -1 && errno != 0)
+       error (1, errno, "cannot get priority");
+      printf ("%d\n", current_priority);
+      exit (0);
+    }
+
+  errno = 0;
+#ifndef NICE_PRIORITY
+  current_priority = getpriority (PRIO_PROCESS, 0);
+#else
+  current_priority = nice (0);
+#endif
+  if (current_priority == -1 && errno != 0)
+    error (1, errno, "cannot get priority");
+
+#ifndef NICE_PRIORITY
+  if (setpriority (PRIO_PROCESS, 0, current_priority + adjustment))
+#else
+  if (nice (adjustment) == -1)
+#endif
+    error (1, errno, "cannot set priority");
+
+  execvp (argv[optind], &argv[optind]);
+  error (errno == ENOENT ? 127 : 126, errno, "%s", argv[optind]);
+}
+
+/* Return nonzero if S represents a (possibly signed) decimal integer,
+   zero if not. */
+
+int
+isinteger (s)
+     char *s;
+{
+  if (*s == '-')
+    ++s;
+  if (*s == 0)
+    return 0;
+  while (*s)
+    {
+      if (*s < '0' || *s > '9')
+       return 0;
+      ++s;
+    }
+  return 1;
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-n adjustment] [-adjustment] [--adjustment=adjustment]\n\
+       [command [arg...]]\n",
+          program_name);
+  exit (1);
+}
diff --git a/src/nohup.sh b/src/nohup.sh
new file mode 100755 (executable)
index 0000000..00908b5
--- /dev/null
@@ -0,0 +1,48 @@
+#!/bin/sh
+# nohup -- run a command immume to hangups, with output to a non-tty
+# Copyright (C) 1991 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# Written by David MacKenzie <djm@gnu.ai.mit.edu>.
+
+# Make sure we get GNU nice, if possible; also allow
+# it to be somewhere else in PATH if not installed yet.
+PATH=@bindir@:$PATH
+
+if [ $# -eq 0 ]; then
+  echo "Usage: nohup command [arg...]" 2>&1
+  exit 1
+fi
+
+trap "" 1
+oldmask=`umask`; umask 077
+# Only redirect the output if the user didn't already do it.
+if [ -t 1 ]; then
+  # If we cannot write to the current directory, use the home directory.
+  if cat /dev/null >> nohup.out; then
+    echo "nohup: appending output to \`nohup.out'" 2>&1
+    umask $oldmask
+    exec nice -5 "$@" >> nohup.out 2>&1
+  else
+    cat /dev/null >> $HOME/nohup.out
+    echo "nohup: appending output to \`$HOME/nohup.out'" 2>&1
+    umask $oldmask
+    exec nice -5 "$@" >> $HOME/nohup.out 2>&1
+  fi
+else
+  umask $oldmask
+  exec nice -5 "$@"
+fi
diff --git a/src/pathchk.c b/src/pathchk.c
new file mode 100644 (file)
index 0000000..a8db2b2
--- /dev/null
@@ -0,0 +1,332 @@
+/* pathchk -- check whether pathnames are valid or portable
+   Copyright (C) 1991, 1992 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Usage: pathchk [-p] [--portability] path...
+
+   For each PATH, print a message if any of these conditions are false:
+   * all existing leading directories in PATH have search (execute) permission
+   * strlen (PATH) <= PATH_MAX
+   * strlen (each_directory_in_PATH) <= NAME_MAX
+
+   Exit status:
+   0                   All PATH names passed all of the tests.
+   1                   An error occurred.
+
+   Options:
+   -p, --portability   Instead of performing length checks on the
+                       underlying filesystem, test the length of the
+                       pathname and its components against the POSIX.1
+                       minimum limits for portability, _POSIX_NAME_MAX
+                       and _POSIX_PATH_MAX in 2.9.2.  Also check that
+                       the pathname contains no characters not in the
+                       portable filename character set.
+
+   David MacKenzie <djm@gnu.ai.mit.edu>
+   and Jim Meyering <meyering@cs.utexas.edu> */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+
+#ifdef _POSIX_VERSION
+#include <limits.h>
+#ifndef PATH_MAX
+#define PATH_MAX_FOR(p) pathconf ((p), _PC_PATH_MAX)
+#endif /* not PATH_MAX */
+#ifndef NAME_MAX
+#define NAME_MAX_FOR(p) pathconf ((p), _PC_NAME_MAX);
+#endif /* not NAME_MAX */
+
+#else /* not _POSIX_VERSION */
+
+#include <sys/param.h>
+#ifndef PATH_MAX
+#ifdef MAXPATHLEN
+#define PATH_MAX MAXPATHLEN
+#else /* not MAXPATHLEN */
+#define PATH_MAX _POSIX_PATH_MAX
+#endif /* not MAXPATHLEN */
+#endif /* not PATH_MAX */
+
+#ifndef NAME_MAX
+#ifdef MAXNAMLEN
+#define NAME_MAX MAXNAMLEN
+#else /* not MAXNAMLEN */
+#define NAME_MAX _POSIX_NAME_MAX
+#endif /* not MAXNAMLEN */
+#endif /* not NAME_MAX */
+
+#endif /* not _POSIX_VERSION */
+
+#ifndef _POSIX_PATH_MAX
+#define _POSIX_PATH_MAX 255
+#endif
+#ifndef _POSIX_NAME_MAX
+#define _POSIX_NAME_MAX 14
+#endif
+
+#ifndef PATH_MAX_FOR
+#define PATH_MAX_FOR(p) PATH_MAX
+#endif
+#ifndef NAME_MAX_FOR
+#define NAME_MAX_FOR(p) NAME_MAX
+#endif
+
+char *xstrdup();
+int validate_path ();
+void error ();
+void usage ();
+
+/* The name this program was run with. */
+char *program_name;
+
+struct option longopts[] =
+{
+  {"portability", 0, NULL, 'p'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int exit_status = 0;
+  int check_portability = 0;
+  int optc;
+
+  program_name = argv[0];
+
+  while ((optc = getopt_long (argc, argv, "p", longopts, (int *) 0)) != EOF)
+    {
+      switch (optc)
+       {
+       case 'p':
+         check_portability = 1;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (optind == argc)
+    usage ();
+
+  for (; optind < argc; ++optind)
+    exit_status |= validate_path (argv[optind], check_portability);
+
+  exit (exit_status);
+}
+
+/* Each element is nonzero if the corresponding ASCII character is
+   in the POSIX portable character set, and zero if it is not.
+   In addition, the entry for `/' is nonzero to simplify checking. */
+char portable_chars[] =
+{
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, /* 32-47 */
+  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 48-63 */
+  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-79 */
+  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 80-95 */
+  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 96-111 */
+  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 112-127 */
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* If PATH contains only portable characters, return 1, else 0.  */
+
+int
+portable_chars_only (path)
+     char *path;
+{
+  char *p;
+
+  for (p = path; *p; ++p)
+    if (portable_chars[*p] == 0)
+      {
+       error (0, 0, "path `%s' contains nonportable character `%c'",
+              path, *p);
+       return 0;
+      }
+  return 1;
+}
+
+/* Return 1 if PATH is a usable leading directory, 0 if not,
+   2 if it doesn't exist.  */
+
+int
+dir_ok (path)
+     char *path;
+{
+  struct stat stats;
+
+  if (stat (path, &stats))
+    return 2;
+
+  if (!S_ISDIR (stats.st_mode))
+    {
+      error (0, 0, "`%s' is not a directory", path);
+      return 0;
+    }
+
+  /* Use access to test for search permission because
+     testing permission bits of st_mode can lose with new
+     access control mechanisms.  Of course, access loses if you're
+     running setuid. */
+  if (access (path, X_OK) != 0)
+    {
+      if (errno == EACCES)
+       error (0, 0, "directory `%s' is not searchable", path);
+      else
+       error (0, errno, "%s", path);
+      return 0;
+    }
+
+  return 1;
+}
+
+/* Make sure that
+   strlen (PATH) <= PATH_MAX
+   && strlen (each-existing-directory-in-PATH) <= NAME_MAX
+
+   If PORTABILITY is nonzero, compare against _POSIX_PATH_MAX and
+   _POSIX_NAME_MAX instead, and make sure that PATH contains no
+   characters not in the POSIX portable filename character set, which
+   consists of A-Z, a-z, 0-9, ., _, -.
+
+   Make sure that all leading directories along PATH that exist have
+   `x' permission.
+
+   Return 0 if all of these tests are successful, 1 if any fail. */
+
+int
+validate_path (path, portability)
+     char *path;
+     int portability;
+{
+  int path_max;
+  int last_elem;               /* Nonzero if checking last element of path. */
+  int exists;                  /* 2 if the path element exists.  */
+  char *slash;
+  char *parent;                        /* Last existing leading directory so far.  */
+
+  if (portability && !portable_chars_only (path))
+    return 1;
+
+  if (*path == '\0')
+    return 0;
+
+  /* Figure out the parent of the first element in PATH.  */
+  parent = xstrdup (*path == '/' ? "/" : ".");
+
+  slash = path;
+  last_elem = 0;
+  while (1)
+    {
+      int name_max;
+      int length;              /* Length of partial path being checked. */
+      char *start;             /* Start of path element being checked. */
+
+      /* Find the end of this element of the path.
+        Then chop off the rest of the path after this element. */
+      while (*slash == '/')
+       slash++;
+      start = slash;
+      slash = index (slash, '/');
+      if (slash != NULL)
+       *slash = '\0';
+      else
+       {
+         last_elem = 1;
+         slash = index (start, '\0');
+       }
+
+      if (!last_elem)
+       {
+         exists = dir_ok (path);
+         if (dir_ok == 0)
+           {
+             free (parent);
+             return 1;
+           }
+       }
+
+      length = slash - start;
+      /* Since we know that `parent' is a directory, it's ok to call
+        pathconf with it as the argument.  (If `parent' isn't a directory
+        or doesn't exist, the behavior of pathconf is undefined.)
+        But if `parent' is a directory and is on a remote file system,
+        it's likely that pathconf can't give us a reasonable value
+        and will return -1.  (NFS and tempfs are not POSIX . . .)
+        In that case, we have no choice but to assume the pessimal
+        POSIX minimums.  */
+      name_max = portability ? _POSIX_NAME_MAX : NAME_MAX_FOR (parent);
+      if (name_max < 0)
+       name_max = _POSIX_NAME_MAX;
+      if (length > name_max)
+       {
+         error (0, 0, "name `%s' has length %d; exceeds limit of %d",
+                start, length, name_max);
+         free (parent);
+         return 1;
+       }
+
+      if (last_elem)
+       break;
+
+      if (exists == 1)
+       {
+         free (parent);
+         parent = xstrdup (path);
+       }
+
+      *slash++ = '/';
+    }
+
+  /* `parent' is now the last existing leading directory in the whole path,
+     so it's ok to call pathconf with it as the argument.  */
+  path_max = portability ? _POSIX_PATH_MAX : PATH_MAX_FOR (parent);
+  if (path_max < 0)
+    path_max = _POSIX_PATH_MAX;
+  free (parent);
+  if (strlen (path) > path_max)
+    {
+      error (0, 0, "path `%s' has length %d; exceeds limit of %d",
+            path, strlen (path), path_max);
+      return 1;
+    }
+
+  return 0;
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-p] [--portability] path...\n",
+          program_name);
+  exit (1);
+}
diff --git a/src/printenv.c b/src/printenv.c
new file mode 100644 (file)
index 0000000..ac4511c
--- /dev/null
@@ -0,0 +1,69 @@
+/* printenv -- print all or part of environment
+   Copyright (C) 1989, 1991 Free Software Foundation.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Usage: printenv [variable...]
+
+   If no arguments are given, print the entire environment.
+   If one or more variable names are given, print the value of
+   each one that is set, and nothing for ones that are not set.
+
+   Exit status:
+   0 if all variables specified were found
+   1 if not
+
+   David MacKenzie and Richard Mlynarik */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "system.h"
+
+extern char **environ;
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  char **env;
+  char *ep, *ap;
+  int i;
+  int matches = 0;
+
+  if (argc == 1)
+    {
+      for (env = environ; *env; ++env)
+       puts (*env);
+      exit (0);
+    }
+
+  for (i = 1; i < argc; ++i)
+    {
+      for (env = environ; *env; ++env)
+       {
+         ep = *env;
+         ap = argv[i];
+         while (*ep != '\0' && *ap != '\0' && *ep++ == *ap++)
+           if (*ep == '=' && *ap == '\0')
+             {
+               puts (ep + 1);
+               ++matches;
+               break;
+             }
+       }
+    }
+  exit (matches != argc - 1);
+}
diff --git a/src/printf.c b/src/printf.c
new file mode 100644 (file)
index 0000000..b546ef6
--- /dev/null
@@ -0,0 +1,481 @@
+/* printf - format and print data
+   Copyright (C) 1990, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Usage: printf format [argument...]
+
+   A front end to the printf function that lets it be used from the shell.
+
+   Backslash escapes:
+
+   \" = double quote
+   \\ = backslash
+   \a = alert (bell)
+   \b = backspace
+   \c = produce no further output
+   \f = form feed
+   \n = new line
+   \r = carriage return
+   \t = horizontal tab
+   \v = vertical tab
+   \0ooo = octal number (ooo is 0 to 3 digits)
+   \xhhh = hexadecimal number (hhh is 1 to 3 digits)
+
+   Additional directive:
+
+   %b = print an argument string, interpreting backslash escapes
+
+   The `format' argument is re-used as many times as necessary
+   to convert all of the given arguments.
+
+   David MacKenzie <djm@gnu.ai.mit.edu> */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include "system.h"
+
+#ifndef STDC_HEADERS
+double strtod ();
+long strtol ();
+unsigned long strtoul ();
+#endif
+
+#define isodigit(c) ((c) >= '0' && (c) <= '7')
+#define hextobin(c) ((c)>='a'&&(c)<='f' ? (c)-'a'+10 : (c)>='A'&&(c)<='F' ? (c)-'A'+10 : (c)-'0')
+#define octtobin(c) ((c) - '0')
+
+char *xmalloc ();
+double xstrtod ();
+int print_esc ();
+int print_formatted ();
+long xstrtol ();
+unsigned long xstrtoul ();
+void error ();
+void print_direc ();
+void print_esc_char ();
+void print_esc_string ();
+void verify ();
+
+/* The name this program was run with. */
+char *program_name;
+
+/* The value to return to the calling program.  */
+int exit_status;
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  char *format;
+  int args_used;
+
+  program_name = argv[0];
+  exit_status = 0;
+
+  if (argc == 1)
+    {
+      fprintf (stderr, "Usage: %s format [argument...]\n", program_name);
+      exit (1);
+    }
+
+  format = argv[1];
+  argc -= 2;
+  argv += 2;
+
+  do
+    {
+      args_used = print_formatted (format, argc, argv);
+      argc -= args_used;
+      argv += args_used;
+    }
+  while (args_used > 0 && argc > 0);
+
+  exit (exit_status);
+}
+
+/* Print the text in FORMAT, using ARGV (with ARGC elements) for
+   arguments to any `%' directives.
+   Return the number of elements of ARGV used.  */
+
+int
+print_formatted (format, argc, argv)
+     char *format;
+     int argc;
+     char **argv;
+{
+  int save_argc = argc;                /* Preserve original value.  */
+  char *f;                     /* Pointer into `format'.  */
+  char *direc_start;           /* Start of % directive.  */
+  int direc_length;            /* Length of % directive.  */
+  int field_width;             /* Arg to first '*', or -1 if none.  */
+  int precision;               /* Arg to second '*', or -1 if none.  */
+
+  for (f = format; *f; ++f)
+    {
+      switch (*f)
+       {
+       case '%':
+         direc_start = f++;
+         direc_length = 1;
+         field_width = precision = -1;
+         if (*f == '%')
+           {
+             putchar ('%');
+             break;
+           }
+         if (*f == 'b')
+           {
+             if (argc > 0)
+               {
+                 print_esc_string (*argv);
+                 ++argv;
+                 --argc;
+               }
+             break;
+           }
+         if (index ("-+ #", *f))
+           {
+             ++f;
+             ++direc_length;
+           }
+         if (*f == '*')
+           {
+             ++f;
+             ++direc_length;
+             if (argc > 0)
+               {
+                 field_width = xstrtoul (*argv);
+                 ++argv;
+                 --argc;
+               }
+             else
+               field_width = 0;
+           }
+         else
+           while (isdigit (*f))
+             {
+               ++f;
+               ++direc_length;
+             }
+         if (*f == '.')
+           {
+             ++f;
+             ++direc_length;
+             if (*f == '*')
+               {
+                 ++f;
+                 ++direc_length;
+                 if (argc > 0)
+                   {
+                     precision = xstrtoul (*argv);
+                     ++argv;
+                     --argc;
+                   }
+                 else
+                   precision = 0;
+               }
+             else
+               while (isdigit (*f))
+                 {
+                   ++f;
+                   ++direc_length;
+                 }
+           }
+         if (*f == 'l' || *f == 'L' || *f == 'h')
+           {
+             ++f;
+             ++direc_length;
+           }
+         if (!index ("diouxXfeEgGcs", *f))
+           error (1, 0, "%%%c: invalid directive", *f);
+         ++direc_length;
+         if (argc > 0)
+           {
+             print_direc (direc_start, direc_length, field_width,
+                          precision, *argv);
+             ++argv;
+             --argc;
+           }
+         else
+           print_direc (direc_start, direc_length, field_width,
+                        precision, "");
+         break;
+
+       case '\\':
+         f += print_esc (f);
+         break;
+
+       default:
+         putchar (*f);
+       }
+    }
+
+  return save_argc - argc;
+}
+
+/* Print a \ escape sequence starting at ESCSTART.
+   Return the number of characters in the escape sequence
+   besides the backslash. */
+
+int
+print_esc (escstart)
+     char *escstart;
+{
+  register char *p = escstart + 1;
+  int esc_value = 0;           /* Value of \nnn escape. */
+  int esc_length;              /* Length of \nnn escape. */
+
+  /* \0ooo and \xhhh escapes have maximum length of 3 chars. */
+  if (*p == 'x')
+    {
+      for (esc_length = 0, ++p;
+          esc_length < 3 && isxdigit (*p);
+          ++esc_length, ++p)
+       esc_value = esc_value * 16 + hextobin (*p);
+      if (esc_length == 0)
+       error (1, 0, "missing hexadecimal number in escape");
+      putchar (esc_value);
+    }
+  else if (*p == '0')
+    {
+      for (esc_length = 0, ++p;
+          esc_length < 3 && isodigit (*p);
+          ++esc_length, ++p)
+       esc_value = esc_value * 8 + octtobin (*p);
+      putchar (esc_value);
+    }
+  else if (index ("\"\\abcfnrtv", *p))
+    print_esc_char (*p++);
+  else
+    error (1, 0, "\\%c: invalid escape", *p);
+  return p - escstart - 1;
+}
+
+/* Output a single-character \ escape.  */
+
+void
+print_esc_char (c)
+     char c;
+{
+  switch (c)
+    {
+    case 'a':                  /* Alert. */
+      putchar (7);
+      break;
+    case 'b':                  /* Backspace. */
+      putchar (8);
+      break;
+    case 'c':                  /* Cancel the rest of the output. */
+      exit (0);
+      break;
+    case 'f':                  /* Form feed. */
+      putchar (12);
+      break;
+    case 'n':                  /* New line. */
+      putchar (10);
+      break;
+    case 'r':                  /* Carriage return. */
+      putchar (13);
+      break;
+    case 't':                  /* Horizontal tab. */
+      putchar (9);
+      break;
+    case 'v':                  /* Vertical tab. */
+      putchar (11);
+      break;
+    default:
+      putchar (c);
+      break;
+    }
+}
+
+/* Print string STR, evaluating \ escapes. */
+
+void
+print_esc_string (str)
+     char *str;
+{
+  for (; *str; str++)
+    if (*str == '\\')
+      str += print_esc (str);
+    else
+      putchar (*str);
+}
+
+/* Output a % directive.  START is the start of the directive,
+   LENGTH is its length, and ARGUMENT is its argument.
+   If FIELD_WIDTH or PRECISION is non-negative, they are args for
+   '*' values in those fields. */
+
+void
+print_direc (start, length, field_width, precision, argument)
+     char *start;
+     int length;
+     int field_width;
+     int precision;
+     char *argument;
+{
+  char *p;             /* Null-terminated copy of % directive. */
+
+  p = xmalloc ((unsigned) (length + 1));
+  strncpy (p, start, length);
+  p[length] = 0;
+
+  switch (p[length - 1])
+    {
+    case 'd':
+    case 'i':
+      if (field_width < 0)
+       {
+         if (precision < 0)
+           printf (p, xstrtol (argument));
+         else
+           printf (p, precision, xstrtol (argument));
+       }
+      else
+       {
+         if (precision < 0)
+           printf (p, field_width, xstrtol (argument));
+         else
+           printf (p, field_width, precision, xstrtol (argument));
+       }
+      break;
+
+    case 'o':
+    case 'u':
+    case 'x':
+    case 'X':
+      if (field_width < 0)
+       {
+         if (precision < 0)
+           printf (p, xstrtoul (argument));
+         else
+           printf (p, precision, xstrtoul (argument));
+       }
+      else
+       {
+         if (precision < 0)
+           printf (p, field_width, xstrtoul (argument));
+         else
+           printf (p, field_width, precision, xstrtoul (argument));
+       }
+      break;
+
+    case 'f':
+    case 'e':
+    case 'E':
+    case 'g':
+    case 'G':
+      if (field_width < 0)
+       {
+         if (precision < 0)
+           printf (p, xstrtod (argument));
+         else
+           printf (p, precision, xstrtod (argument));
+       }
+      else
+       {
+         if (precision < 0)
+           printf (p, field_width, xstrtod (argument));
+         else
+           printf (p, field_width, precision, xstrtod (argument));
+       }
+      break;
+
+    case 'c':
+      printf (p, *argument);
+      break;
+
+    case 's':
+      if (field_width < 0)
+       {
+         if (precision < 0)
+           printf (p, argument);
+         else
+           printf (p, precision, argument);
+       }
+      else
+       {
+         if (precision < 0)
+           printf (p, field_width, argument);
+         else
+           printf (p, field_width, precision, argument);
+       }
+      break;
+    }
+
+  free (p);
+}
+
+unsigned long
+xstrtoul (s)
+     char *s;
+{
+  char *end;
+  unsigned long val;
+
+  errno = 0;
+  val = strtoul (s, &end, 0);
+  verify (s, end);
+  return val;
+}
+
+long
+xstrtol (s)
+     char *s;
+{
+  char *end;
+  long val;
+
+  errno = 0;
+  val = strtol (s, &end, 0);
+  verify (s, end);
+  return val;
+}
+
+double
+xstrtod (s)
+     char *s;
+{
+  char *end;
+  double val;
+
+  errno = 0;
+  val = strtod (s, &end);
+  verify (s, end);
+  return val;
+}
+
+void
+verify (s, end)
+     char *s, *end;
+{
+  if (errno)
+    {
+      error (0, errno, "%s", s);
+      exit_status = 1;
+    }
+  else if (*end)
+    {
+      if (s == end)
+       error (0, 0, "%s: expected a numeric value", s);
+      else
+       error (0, 0, "%s: value not completely converted", s);
+      exit_status = 1;
+    }
+}
diff --git a/src/sleep.c b/src/sleep.c
new file mode 100644 (file)
index 0000000..7212ba5
--- /dev/null
@@ -0,0 +1,84 @@
+/* sleep - delay for a specified amount of time.
+   Copyright (C) 1984, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "system.h"
+
+long argdecode ();
+void error ();
+
+/* The name by which this program was run. */
+char *program_name;
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int i;
+  unsigned seconds = 0;
+
+  program_name = argv[0];
+
+  if (argc == 1)
+    {
+      fprintf (stderr, "Usage: %s number[smhd]...\n", argv[0]);
+      exit (1);
+    }
+
+  for (i = 1; i < argc; i++)
+    seconds += argdecode (argv[i]);
+
+  sleep (seconds);
+
+  exit (0);
+}
+
+long
+argdecode (s)
+     char *s;
+{
+  long value;
+  register char *p = s;
+  register char c;
+
+  value = 0;
+  while ((c = *p++) >= '0' && c <= '9')
+    value = value * 10 + c - '0';
+
+  switch (c)
+    {
+    case 's':
+      break;
+    case 'm':
+      value *= 60;
+      break;
+    case 'h':
+      value *= 60 * 60;
+      break;
+    case 'd':
+      value *= 60 * 60 * 24;
+      break;
+    default:
+      p--;
+    }
+
+  if (*p)
+    error (1, 0, "invalid time interval `%s'", s);
+  return value;
+}
diff --git a/src/stty.c b/src/stty.c
new file mode 100644 (file)
index 0000000..5349af0
--- /dev/null
@@ -0,0 +1,1241 @@
+/* stty -- change and print terminal line settings
+   Copyright (C) 1990, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Usage: stty [-ag] [--all] [--save] [setting...]
+
+   Options:
+   -a, --all   Write all current settings to stdout in human-readable form.
+   -g, --save  Write all current settings to stdout in stty-readable form.
+
+   If no args are given, write to stdout the baud rate and settings that
+   have been changed from their defaults.  Mode reading and changes
+   are done on stdin.
+
+   David MacKenzie <djm@gnu.ai.mit.edu> */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <termios.h>
+#ifdef _AIX
+#include <sys/ioctl.h>         /* Needed to get window size. */
+#endif
+#ifdef WINSIZE_IN_PTEM
+#include <sys/stream.h>
+#include <sys/ptem.h>
+#endif
+#include <getopt.h>
+#ifdef __STDC__
+#include <stdarg.h>
+#define VA_START(args, lastarg) va_start(args, lastarg)
+#else
+#include <varargs.h>
+#define VA_START(args, lastarg) va_start(args)
+#endif
+#include "system.h"
+
+#if defined(GWINSZ_BROKEN)     /* Such as for SCO UNIX 3.2.2. */
+#undef TIOCGWINSZ
+#endif
+
+#ifndef _POSIX_VDISABLE
+#define _POSIX_VDISABLE ((unsigned char) 0)
+#endif
+
+#define        Control(c) ((c) & 0x1f)
+/* Canonical values for control characters. */
+#ifndef CINTR
+#define        CINTR Control ('c')
+#endif
+#ifndef CQUIT
+#define        CQUIT 28
+#endif
+#ifndef CERASE
+#define        CERASE 127
+#endif
+#ifndef CKILL
+#define        CKILL Control ('u')
+#endif
+#ifndef CEOF
+#define        CEOF Control ('d')
+#endif
+#ifndef CEOL
+#define        CEOL _POSIX_VDISABLE
+#endif
+#ifndef CSTART
+#define        CSTART Control ('q')
+#endif
+#ifndef CSTOP
+#define        CSTOP Control ('s')
+#endif
+#ifndef CSUSP
+#define        CSUSP Control ('z')
+#endif
+#if defined(VEOL2) && !defined(CEOL2)
+#define        CEOL2 _POSIX_VDISABLE
+#endif
+#if defined(VSWTCH) && !defined(CSWTCH)
+#define        CSWTCH _POSIX_VDISABLE
+#endif
+#if defined(VDSUSP) && !defined (CDSUSP)
+#define        CDSUSP Control ('y')
+#endif
+#if !defined(VREPRINT) && defined(VRPRNT) /* Irix 4.0.5 */
+#define VREPRINT VRPRNT
+#endif
+#if defined(VREPRINT) && !defined(CRPRNT)
+#define        CRPRNT Control ('r')
+#endif
+#if defined(VWERASE) && !defined(CWERASE)
+#define        CWERASE Control ('w')
+#endif
+#if defined(VLNEXT) && !defined(CLNEXT)
+#define        CLNEXT Control ('v')
+#endif
+
+char *visible ();
+unsigned long baud_to_value ();
+int recover_mode ();
+int screen_columns ();
+int set_mode ();
+long integer_arg ();
+speed_t string_to_baud ();
+tcflag_t *mode_type_flag ();
+void display_all ();
+void display_changed ();
+void display_recoverable ();
+void display_settings ();
+void display_speed ();
+void display_window_size ();
+void error ();
+void sane_mode ();
+void set_control_char ();
+void set_speed ();
+void set_window_size ();
+
+/* Which speeds to set.  */
+enum speed_setting
+{
+  input_speed, output_speed, both_speeds
+};
+
+/* What to output and how.  */
+enum output_type
+{
+  changed, all, recoverable    /* Default, -a, -g.  */
+};
+
+/* Which member(s) of `struct termios' a mode uses.  */
+enum mode_type
+{
+  control, input, output, local, combination
+};
+
+/* Flags for `struct mode_info'. */
+#define SANE_SET 1             /* Set in `sane' mode. */
+#define SANE_UNSET 2           /* Unset in `sane' mode. */
+#define REV 4                  /* Can be turned off by prepending `-'. */
+#define OMIT 8                 /* Don't display value. */
+
+/* Each mode.  */
+struct mode_info
+{
+  char *name;                  /* Name given on command line.  */
+  enum mode_type type;         /* Which structure element to change. */
+  char flags;                  /* Setting and display options.  */
+  unsigned long bits;          /* Bits to set for this mode.  */
+  unsigned long mask;          /* Other bits to turn off for this mode.  */
+};
+
+struct mode_info mode_info[] =
+{
+  {"parenb", control, REV, PARENB, 0},
+  {"parodd", control, REV, PARODD, 0},
+  {"cs5", control, 0, CS5, CSIZE},
+  {"cs6", control, 0, CS6, CSIZE},
+  {"cs7", control, 0, CS7, CSIZE},
+  {"cs8", control, 0, CS8, CSIZE},
+  {"hupcl", control, REV, HUPCL, 0},
+  {"hup", control, REV | OMIT, HUPCL, 0},
+  {"cstopb", control, REV, CSTOPB, 0},
+  {"cread", control, SANE_SET | REV, CREAD, 0},
+  {"clocal", control, REV, CLOCAL, 0},
+#ifdef CRTSCTS
+  {"crtscts", control, REV, CRTSCTS, 0},
+#endif
+
+  {"ignbrk", input, SANE_UNSET | REV, IGNBRK, 0},
+  {"brkint", input, SANE_SET | REV, BRKINT, 0},
+  {"ignpar", input, REV, IGNPAR, 0},
+  {"parmrk", input, REV, PARMRK, 0},
+  {"inpck", input, REV, INPCK, 0},
+  {"istrip", input, REV, ISTRIP, 0},
+  {"inlcr", input, SANE_UNSET | REV, INLCR, 0},
+  {"igncr", input, SANE_UNSET | REV, IGNCR, 0},
+  {"icrnl", input, SANE_SET | REV, ICRNL, 0},
+  {"ixon", input, REV, IXON, 0},
+  {"ixoff", input, SANE_UNSET | REV, IXOFF, 0},
+  {"tandem", input, REV | OMIT, IXOFF, 0},
+#ifdef IUCLC
+  {"iuclc", input, SANE_UNSET | REV, IUCLC, 0},
+#endif
+#ifdef IXANY
+  {"ixany", input, SANE_UNSET | REV, IXANY, 0},
+#endif
+#ifdef IMAXBEL
+  {"imaxbel", input, SANE_SET | REV, IMAXBEL, 0},
+#endif
+
+  {"opost", output, SANE_SET | REV, OPOST, 0},
+#ifdef OLCUC
+  {"olcuc", output, SANE_UNSET | REV, OLCUC, 0},
+#endif
+#ifdef OCRNL
+  {"ocrnl", output, SANE_UNSET | REV, OCRNL, 0},
+#endif
+#ifdef ONLCR
+  {"onlcr", output, SANE_SET | REV, ONLCR, 0},
+#endif
+#ifdef ONOCR
+  {"onocr", output, SANE_UNSET | REV, ONOCR, 0},
+#endif
+#ifdef ONLRET
+  {"onlret", output, SANE_UNSET | REV, ONLRET, 0},
+#endif
+#ifdef OFILL
+  {"ofill", output, SANE_UNSET | REV, OFILL, 0},
+#endif
+#ifdef OFDEL
+  {"ofdel", output, SANE_UNSET | REV, OFDEL, 0},
+#endif
+#ifdef NLDLY
+  {"nl1", output, SANE_UNSET, NL1, NLDLY},
+  {"nl0", output, SANE_SET, NL0, NLDLY},
+#endif
+#ifdef CRDLY
+  {"cr3", output, SANE_UNSET, CR3, CRDLY},
+  {"cr2", output, SANE_UNSET, CR2, CRDLY},
+  {"cr1", output, SANE_UNSET, CR1, CRDLY},
+  {"cr0", output, SANE_SET, CR0, CRDLY},
+#endif
+#ifdef TABDLY
+  {"tab3", output, SANE_UNSET, TAB3, TABDLY},
+  {"tab2", output, SANE_UNSET, TAB2, TABDLY},
+  {"tab1", output, SANE_UNSET, TAB1, TABDLY},
+  {"tab0", output, SANE_SET, TAB0, TABDLY},
+#endif
+#ifdef BSDLY
+  {"bs1", output, SANE_UNSET, BS1, BSDLY},
+  {"bs0", output, SANE_SET, BS0, BSDLY},
+#endif
+#ifdef VTDLY
+  {"vt1", output, SANE_UNSET, VT1, VTDLY},
+  {"vt0", output, SANE_SET, VT0, VTDLY},
+#endif
+#ifdef FFDLY
+  {"ff1", output, SANE_UNSET, FF1, FFDLY},
+  {"ff0", output, SANE_SET, FF0, FFDLY},
+#endif
+
+  {"isig", local, SANE_SET | REV, ISIG, 0},
+  {"icanon", local, SANE_SET | REV, ICANON, 0},
+#ifdef IEXTEN
+  {"iexten", local, SANE_SET | REV, IEXTEN, 0},
+#endif
+  {"echo", local, SANE_SET | REV, ECHO, 0},
+  {"echoe", local, SANE_SET | REV, ECHOE, 0},
+  {"crterase", local, REV | OMIT, ECHOE, 0},
+  {"echok", local, SANE_SET | REV, ECHOK, 0},
+  {"echonl", local, SANE_UNSET | REV, ECHONL, 0},
+  {"noflsh", local, SANE_UNSET | REV, NOFLSH, 0},
+#ifdef XCASE
+  {"xcase", local, SANE_UNSET | REV, XCASE, 0},
+#endif
+#ifdef TOSTOP
+  {"tostop", local, SANE_UNSET | REV, TOSTOP, 0},
+#endif
+#ifdef ECHOPRT
+  {"echoprt", local, SANE_UNSET | REV, ECHOPRT, 0},
+  {"prterase", local, REV | OMIT, ECHOPRT, 0},
+#endif
+#ifdef ECHOCTL
+  {"echoctl", local, SANE_SET | REV, ECHOCTL, 0},
+  {"ctlecho", local, REV | OMIT, ECHOCTL, 0},
+#endif
+#ifdef ECHOKE
+  {"echoke", local, SANE_SET | REV, ECHOKE, 0},
+  {"crtkill", local, REV | OMIT, ECHOKE, 0},
+#endif
+
+  {"evenp", combination, REV | OMIT, 0, 0},
+  {"parity", combination, REV | OMIT, 0, 0},
+  {"oddp", combination, REV | OMIT, 0, 0},
+  {"nl", combination, REV | OMIT, 0, 0},
+  {"ek", combination, OMIT, 0, 0},
+  {"sane", combination, OMIT, 0, 0},
+  {"cooked", combination, REV | OMIT, 0, 0},
+  {"raw", combination, REV | OMIT, 0, 0},
+  {"pass8", combination, REV | OMIT, 0, 0},
+  {"litout", combination, REV | OMIT, 0, 0},
+  {"cbreak", combination, REV | OMIT, 0, 0},
+#ifdef IXANY
+  {"decctlq", combination, REV | OMIT, 0, 0},
+#endif
+#ifdef TABDLY
+  {"tabs", combination, REV | OMIT, 0, 0},
+#endif
+#if defined(XCASE) && defined(IUCLC) && defined(OLCUC)
+  {"lcase", combination, REV | OMIT, 0, 0},
+  {"LCASE", combination, REV | OMIT, 0, 0},
+#endif
+  {"crt", combination, OMIT, 0, 0},
+  {"dec", combination, OMIT, 0, 0},
+
+  {NULL, control, 0, 0, 0}
+};
+
+/* Control character settings.  */
+struct control_info
+{
+  char *name;                  /* Name given on command line.  */
+  unsigned char saneval;       /* Value to set for `stty sane'.  */
+  int offset;                  /* Offset in c_cc.  */
+};
+
+/* Control characters. */
+
+struct control_info control_info[] =
+{
+  {"intr", CINTR, VINTR},
+  {"quit", CQUIT, VQUIT},
+  {"erase", CERASE, VERASE},
+  {"kill", CKILL, VKILL},
+  {"eof", CEOF, VEOF},
+  {"eol", CEOL, VEOL},
+#ifdef VEOL2
+  {"eol2", CEOL2, VEOL2},
+#endif
+#ifdef VSWTCH
+  {"swtch", CSWTCH, VSWTCH},
+#endif
+  {"start", CSTART, VSTART},
+  {"stop", CSTOP, VSTOP},
+  {"susp", CSUSP, VSUSP},
+#ifdef VDSUSP
+  {"dsusp", CDSUSP, VDSUSP},
+#endif
+#ifdef VREPRINT
+  {"rprnt", CRPRNT, VREPRINT},
+#endif
+#ifdef VWERASE
+  {"werase", CWERASE, VWERASE},
+#endif
+#ifdef VLNEXT
+  {"lnext", CLNEXT, VLNEXT},
+#endif
+
+  /* These must be last because of the display routines. */
+  {"min", 1, VMIN},
+  {"time", 0, VTIME},
+  {NULL, 0, 0}
+};
+
+/* The width of the screen, for output wrapping. */
+int max_col;
+
+/* Current position, to know when to wrap. */
+int current_col;
+
+struct option longopts[] =
+{
+  {"all", 0, NULL, 'a'},
+  {"save", 0, NULL, 'g'},
+  {NULL, 0, NULL, 0}
+};
+
+/* The name this program was run with. */
+char *program_name;
+
+/* Print format string MESSAGE and optional args.
+   Wrap to next line first if it won't fit.
+   Print a space first unless MESSAGE will start a new line. */
+
+/* VARARGS */
+void
+#ifdef __STDC__
+wrapf (char *message, ...)
+#else
+wrapf (message, va_alist)
+     char *message;
+     va_dcl
+#endif
+{
+  va_list args;
+  char buf[1024];              /* Plenty long for our needs. */
+  int buflen;
+
+  VA_START (args, message);
+  vsprintf (buf, message, args);
+  va_end (args);
+  buflen = strlen (buf);
+  if (current_col + buflen >= max_col)
+    {
+      putchar ('\n');
+      current_col = 0;
+    }
+  if (current_col > 0)
+    {
+      putchar (' ');
+      current_col++;
+    }
+  fputs (buf, stdout);
+  current_col += buflen;
+}
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  struct termios mode;
+  enum output_type output_type = changed;
+  int optc;
+
+  program_name = argv[0];
+  opterr = 0;
+
+  while ((optc = getopt_long (argc, argv, "ag", longopts, (int *) 0)) != EOF)
+    {
+      if (optc == 'a')
+       output_type = all;
+      else if (optc == 'g')
+       output_type = recoverable;
+      else
+       break;
+    }
+
+  if (tcgetattr (0, &mode))
+    error (1, errno, "standard input");
+
+  max_col = screen_columns ();
+  current_col = 0;
+
+  if (optind == argc)
+    {
+      if (optc == '?')
+       error (1, 0, "invalid argument `%s'", argv[--optind]);
+      display_settings (output_type, &mode);
+      exit (0);
+    }
+
+  while (optind < argc)
+    {
+      int match_found = 0;
+      int reversed = 0;
+      int i;
+
+      if (argv[optind][0] == '-')
+       {
+         ++argv[optind];
+         reversed = 1;
+       }
+      for (i = 0; mode_info[i].name != NULL; ++i)
+       {
+         if (!strcmp (argv[optind], mode_info[i].name))
+           {
+             match_found = set_mode (&mode_info[i], reversed, &mode);
+             break;
+           }
+       }
+      if (match_found == 0 && reversed)
+       error (1, 0, "invalid argument `%s'", --argv[optind]);
+      if (match_found == 0)
+       {
+         for (i = 0; control_info[i].name != NULL; ++i)
+           {
+             if (!strcmp (argv[optind], control_info[i].name))
+               {
+                 if (optind == argc - 1)
+                   error (1, 0, "missing argument to `%s'", argv[optind]);
+                 match_found = 1;
+                 ++optind;
+                 set_control_char (&control_info[i], argv[optind], &mode);
+                 break;
+               }
+           }
+       }
+      if (match_found == 0)
+       {
+         if (!strcmp (argv[optind], "ispeed"))
+           {
+             if (optind == argc - 1)
+               error (1, 0, "missing argument to `%s'", argv[optind]);
+             ++optind;
+             set_speed (input_speed, argv[optind], &mode);
+           }
+         else if (!strcmp (argv[optind], "ospeed"))
+           {
+             if (optind == argc - 1)
+               error (1, 0, "missing argument to `%s'", argv[optind]);
+             ++optind;
+             set_speed (output_speed, argv[optind], &mode);
+           }
+#ifdef TIOCGWINSZ
+         else if (!strcmp (argv[optind], "rows"))
+           {
+             if (optind == argc - 1)
+               error (1, 0, "missing argument to `%s'", argv[optind]);
+             ++optind;
+             set_window_size ((int) integer_arg (argv[optind]), -1);
+           }
+         else if (!strcmp (argv[optind], "cols")
+                  || !strcmp (argv[optind], "columns"))
+           {
+             if (optind == argc - 1)
+               error (1, 0, "missing argument to `%s'", argv[optind]);
+             ++optind;
+             set_window_size (-1, (int) integer_arg (argv[optind]));
+           }
+         else if (!strcmp (argv[optind], "size"))
+           display_window_size (0);
+#endif
+#ifdef HAVE_C_LINE
+         else if (!strcmp (argv[optind], "line"))
+           {
+             if (optind == argc - 1)
+               error (1, 0, "missing argument to `%s'", argv[optind]);
+             ++optind;
+             mode.c_line = integer_arg (argv[optind]);
+           }
+#endif
+         else if (!strcmp (argv[optind], "speed"))
+           display_speed (&mode, 0);
+         else if (string_to_baud (argv[optind]) != (speed_t) -1)
+           set_speed (both_speeds, argv[optind], &mode);
+         else if (recover_mode (argv[optind], &mode) == 0)
+           error (1, 0, "invalid argument `%s'", argv[optind]);
+       }
+      optind++;
+    }
+
+  if (tcsetattr (0, TCSADRAIN, &mode))
+    error (1, errno, "standard input");
+
+  exit (0);
+}
+
+/* Return 0 if not applied because not reversible; otherwise return 1. */
+
+int
+set_mode (info, reversed, mode)
+     struct mode_info *info;
+     int reversed;
+     struct termios *mode;
+{
+  tcflag_t *bitsp;
+
+  if (reversed && (info->flags & REV) == 0)
+    return 0;
+
+  bitsp = mode_type_flag (info->type, mode);
+
+  if (bitsp == NULL)
+    {
+      /* Combination mode. */
+      if (!strcmp (info->name, "evenp") || !strcmp (info->name, "parity"))
+       {
+         if (reversed)
+           mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
+         else
+           mode->c_cflag = (mode->c_cflag & ~PARODD & ~CSIZE) | PARENB | CS7;
+       }
+      else if (!strcmp (info->name, "oddp"))
+       {
+         if (reversed)
+           mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
+         else
+           mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARODD | PARENB;
+       }
+      else if (!strcmp (info->name, "nl"))
+       {
+         if (reversed)
+           {
+             mode->c_iflag = mode->c_iflag | ICRNL & ~INLCR & ~IGNCR;
+             mode->c_oflag = mode->c_oflag
+#ifdef ONLCR
+               | ONLCR
+#endif
+#ifdef OCRNL
+                 & ~OCRNL
+#endif
+#ifdef ONLRET
+                 & ~ONLRET
+#endif
+                   ;
+           }
+         else
+           {
+             mode->c_iflag = mode->c_iflag & ~ICRNL;
+#ifdef ONLCR
+             mode->c_oflag = mode->c_oflag & ~ONLCR;
+#endif
+           }
+       }
+      else if (!strcmp (info->name, "ek"))
+       {
+         mode->c_cc[VERASE] = CERASE;
+         mode->c_cc[VKILL] = CKILL;
+       }
+      else if (!strcmp (info->name, "sane"))
+       sane_mode (mode);
+      else if (!strcmp (info->name, "cbreak"))
+       {
+         if (reversed)
+           mode->c_lflag |= ICANON;
+         else
+           mode->c_lflag &= ~ICANON;
+       }
+      else if (!strcmp (info->name, "pass8"))
+       {
+         if (reversed)
+           {
+             mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB;
+             mode->c_iflag |= ISTRIP;
+           }
+         else
+           {
+             mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
+             mode->c_iflag &= ~ISTRIP;
+           }
+       }
+      else if (!strcmp (info->name, "litout"))
+       {
+         if (reversed)
+           {
+             mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB;
+             mode->c_iflag |= ISTRIP;
+             mode->c_oflag |= OPOST;
+           }
+         else
+           {
+             mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8;
+             mode->c_iflag &= ~ISTRIP;
+             mode->c_oflag &= ~OPOST;
+           }
+       }
+      else if (!strcmp (info->name, "raw") || !strcmp (info->name, "cooked"))
+       {
+         if ((info->name[0] == 'r' && reversed)
+             || (info->name[0] == 'c' && !reversed))
+           {
+             /* Cooked mode. */
+             mode->c_iflag |= BRKINT | IGNPAR | ISTRIP | ICRNL | IXON;
+             mode->c_oflag |= OPOST;
+             mode->c_lflag |= ISIG | ICANON;
+#if VMIN == VEOF
+             mode->c_cc[VEOF] = CEOF;
+#endif
+#if VTIME == VEOL
+             mode->c_cc[VEOL] = CEOL;
+#endif
+           }
+         else
+           {
+             /* Raw mode. */
+             mode->c_iflag = 0;
+             mode->c_oflag &= ~OPOST;
+             mode->c_lflag &= ~(ISIG | ICANON
+#ifdef XCASE
+                                | XCASE
+#endif
+                                );
+             mode->c_cc[VMIN] = 1;
+             mode->c_cc[VTIME] = 0;
+           }
+       }
+#ifdef IXANY
+      else if (!strcmp (info->name, "decctlq"))
+       {
+         if (reversed)
+           mode->c_iflag |= IXANY;
+         else
+           mode->c_iflag &= ~IXANY;
+       }
+#endif
+#ifdef TABDLY
+      else if (!strcmp (info->name, "tabs"))
+       {
+         if (reversed)
+           mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB3;
+         else
+           mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB0;
+       }
+#endif
+#if defined(XCASE) && defined(IUCLC) && defined(OLCUC)
+      else if (!strcmp (info->name, "lcase")
+              || !strcmp (info->name, "LCASE"))
+       {
+         if (reversed)
+           {
+             mode->c_lflag &= ~XCASE;
+             mode->c_iflag &= ~IUCLC;
+             mode->c_oflag &= ~OLCUC;
+           }
+         else
+           {
+             mode->c_lflag |= XCASE;
+             mode->c_iflag |= IUCLC;
+             mode->c_oflag |= OLCUC;
+           }
+       }
+#endif
+      else if (!strcmp (info->name, "crt"))
+       mode->c_lflag |= ECHOE
+#ifdef ECHOCTL
+         | ECHOCTL
+#endif
+#ifdef ECHOKE
+           | ECHOKE
+#endif
+             ;
+      else if (!strcmp (info->name, "dec"))
+       {
+         mode->c_cc[VINTR] = 3; /* ^C */
+         mode->c_cc[VERASE] = 127; /* DEL */
+         mode->c_cc[VKILL] = 21; /* ^U */
+         mode->c_lflag |= ECHOE
+#ifdef ECHOCTL
+           | ECHOCTL
+#endif
+#ifdef ECHOKE
+             | ECHOKE
+#endif
+               ;
+#ifdef IXANY
+         mode->c_iflag &= ~IXANY;
+#endif
+       }
+    }
+  else if (reversed)
+    *bitsp = *bitsp & ~info->mask & ~info->bits;
+  else
+    *bitsp = (*bitsp & ~info->mask) | info->bits;
+
+  return 1;
+}
+
+void
+set_control_char (info, arg, mode)
+     struct control_info *info;
+     char *arg;
+     struct termios *mode;
+{
+  unsigned char value;
+
+  if (!strcmp (info->name, "min") || !strcmp (info->name, "time"))
+    value = integer_arg (arg);
+  else if (arg[0] == '\0' || arg[1] == '\0')
+    value = arg[0];
+  else if (!strcmp (arg, "^-") || !strcmp (arg, "undef"))
+    value = _POSIX_VDISABLE;
+  else if (arg[0] == '^' && arg[1] != '\0')    /* Ignore any trailing junk. */
+    {
+      if (arg[1] == '?')
+       value = 127;
+      else
+       value = arg[1] & ~0140; /* Non-letters get weird results. */
+    }
+  else
+    value = integer_arg (arg);
+  mode->c_cc[info->offset] = value;
+}
+
+void
+set_speed (type, arg, mode)
+     enum speed_setting type;
+     char *arg;
+     struct termios *mode;
+{
+  speed_t baud;
+
+  baud = string_to_baud (arg);
+  if (type == input_speed || type == both_speeds)
+    cfsetispeed (mode, baud);
+  if (type == output_speed || type == both_speeds)
+    cfsetospeed (mode, baud);
+}
+
+#ifdef TIOCGWINSZ
+void
+set_window_size (rows, cols)
+     int rows, cols;
+{
+  struct winsize win;
+
+  if (ioctl (0, TIOCGWINSZ, (char *) &win))
+    error (1, errno, "standard input");
+  if (rows >= 0)
+    win.ws_row = rows;
+  if (cols >= 0)
+    win.ws_col = cols;
+  if (ioctl (0, TIOCSWINSZ, (char *) &win))
+    error (1, errno, "standard input");
+}
+
+void
+display_window_size (fancy)
+     int fancy;
+{
+  struct winsize win;
+
+  if (ioctl (0, TIOCGWINSZ, (char *) &win))
+    error (1, errno, "standard input");
+  wrapf (fancy ? "rows %d; columns %d;" : "%d %d\n", win.ws_row, win.ws_col);
+  if (!fancy)
+    current_col = 0;
+}
+#endif
+
+int
+screen_columns ()
+{
+#ifdef TIOCGWINSZ
+  struct winsize win;
+
+  if (ioctl (0, TIOCGWINSZ, (char *) &win))
+    error (1, errno, "standard input");
+  if (win.ws_col > 0)
+    return win.ws_col;
+#endif
+  if (getenv ("COLUMNS"))
+    return atoi (getenv ("COLUMNS"));
+  return 80;
+}
+
+tcflag_t *
+mode_type_flag (type, mode)
+     enum mode_type type;
+     struct termios *mode;
+{
+  switch (type)
+    {
+    case control:
+      return &mode->c_cflag;
+
+    case input:
+      return &mode->c_iflag;
+
+    case output:
+      return &mode->c_oflag;
+
+    case local:
+      return &mode->c_lflag;
+
+    case combination:
+      return NULL;
+    }
+}
+
+void
+display_settings (output_type, mode)
+     enum output_type output_type;
+     struct termios *mode;
+{
+  switch (output_type)
+    {
+    case changed:
+      display_changed (mode);
+      break;
+
+    case all:
+      display_all (mode);
+      break;
+
+    case recoverable:
+      display_recoverable (mode);
+      break;
+    }
+}
+
+void
+display_changed (mode)
+     struct termios *mode;
+{
+  int i;
+  int empty_line;
+  tcflag_t *bitsp;
+  unsigned long mask;
+  enum mode_type prev_type = control;
+
+  display_speed (mode, 1);
+#ifdef HAVE_C_LINE
+  wrapf ("line = %d;", mode->c_line);
+#endif
+  putchar ('\n');
+  current_col = 0;
+
+  empty_line = 1;
+  for (i = 0; strcmp (control_info[i].name, "min"); ++i)
+    {
+      if (mode->c_cc[control_info[i].offset] == control_info[i].saneval)
+       continue;
+      empty_line = 0;
+      wrapf ("%s = %s;", control_info[i].name,
+            visible (mode->c_cc[control_info[i].offset]));
+    }
+  if ((mode->c_lflag & ICANON) == 0)
+    {
+      wrapf ("min = %d; time = %d;\n", (int) mode->c_cc[VMIN],
+            (int) mode->c_cc[VTIME]);
+    }
+  else if (empty_line == 0)
+    putchar ('\n');
+  current_col = 0;
+
+  empty_line = 1;
+  for (i = 0; mode_info[i].name != NULL; ++i)
+    {
+      if (mode_info[i].flags & OMIT)
+       continue;
+      if (mode_info[i].type != prev_type)
+       {
+         if (empty_line == 0)
+           {
+             putchar ('\n');
+             current_col = 0;
+             empty_line = 1;
+           }
+         prev_type = mode_info[i].type;
+       }
+
+      bitsp = mode_type_flag (mode_info[i].type, mode);
+      mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits;
+      if ((*bitsp & mask) == mode_info[i].bits)
+       {
+         if (mode_info[i].flags & SANE_UNSET)
+           {
+             wrapf ("%s", mode_info[i].name);
+             empty_line = 0;
+           }
+       }
+      else if ((mode_info[i].flags & (SANE_SET | REV)) == (SANE_SET | REV))
+       {
+         wrapf ("-%s", mode_info[i].name);
+         empty_line = 0;
+       }
+    }
+  if (empty_line == 0)
+    putchar ('\n');
+  current_col = 0;
+}
+
+void
+display_all (mode)
+     struct termios *mode;
+{
+  int i;
+  tcflag_t *bitsp;
+  unsigned long mask;
+  enum mode_type prev_type = control;
+
+  display_speed (mode, 1);
+#ifdef TIOCGWINSZ
+  display_window_size (1);
+#endif
+#ifdef HAVE_C_LINE
+  wrapf ("line = %d;", mode->c_line);
+#endif
+  putchar ('\n');
+  current_col = 0;
+
+  for (i = 0; strcmp (control_info[i].name, "min"); ++i)
+    {
+      wrapf ("%s = %s;", control_info[i].name,
+            visible (mode->c_cc[control_info[i].offset]));
+    }
+  wrapf ("min = %d; time = %d;\n", mode->c_cc[VMIN], mode->c_cc[VTIME]);
+  current_col = 0;
+
+  for (i = 0; mode_info[i].name != NULL; ++i)
+    {
+      if (mode_info[i].flags & OMIT)
+       continue;
+      if (mode_info[i].type != prev_type)
+       {
+         putchar ('\n');
+         current_col = 0;
+         prev_type = mode_info[i].type;
+       }
+
+      bitsp = mode_type_flag (mode_info[i].type, mode);
+      mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits;
+      if ((*bitsp & mask) == mode_info[i].bits)
+       wrapf ("%s", mode_info[i].name);
+      else if (mode_info[i].flags & REV)
+       wrapf ("-%s", mode_info[i].name);
+    }
+  putchar ('\n');
+  current_col = 0;
+}
+
+void
+display_speed (mode, fancy)
+     struct termios *mode;
+     int fancy;
+{
+  if (cfgetispeed (mode) == 0 || cfgetispeed (mode) == cfgetospeed (mode))
+    wrapf (fancy ? "speed %lu baud;" : "%lu\n",
+          baud_to_value (cfgetospeed (mode)));
+  else
+    wrapf (fancy ? "ispeed %lu baud; ospeed %lu baud;" : "%lu %lu\n",
+          baud_to_value (cfgetispeed (mode)),
+          baud_to_value (cfgetospeed (mode)));
+  if (!fancy)
+    current_col = 0;
+}
+
+void
+display_recoverable (mode)
+     struct termios *mode;
+{
+  int i;
+
+  printf ("%lx:%lx:%lx:%lx",
+         (unsigned long) mode->c_iflag, (unsigned long) mode->c_oflag,
+         (unsigned long) mode->c_cflag, (unsigned long) mode->c_lflag);
+  for (i = 0; i < NCCS; ++i)
+    printf (":%x", (unsigned int) mode->c_cc[i]);
+  putchar ('\n');
+}
+
+int
+recover_mode (arg, mode)
+     char *arg;
+     struct termios *mode;
+{
+  int i, n;
+  unsigned int chr;
+  unsigned long iflag, oflag, cflag, lflag;
+
+  /* Scan into temporaries since it is too much trouble to figure out
+     the right format for `tcflag_t'.  */
+  if (sscanf (arg, "%lx:%lx:%lx:%lx%n",
+             &iflag, &oflag, &cflag, &lflag, &n) != 4)
+    return 0;
+  mode->c_iflag = iflag;
+  mode->c_oflag = oflag;
+  mode->c_cflag = cflag;
+  mode->c_lflag = lflag;
+  arg += n;
+  for (i = 0; i < NCCS; ++i)
+    {
+      if (sscanf (arg, ":%x%n", &chr, &n) != 1)
+       return 0;
+      mode->c_cc[i] = chr;
+      arg += n;
+    }
+  return 1;
+}
+
+struct speed_map
+{
+  char *string;                        /* ASCII representation. */
+  speed_t speed;               /* Internal form. */
+  unsigned long value;         /* Numeric value. */
+};
+
+struct speed_map speeds[] =
+{
+  {"0", B0, 0},
+  {"50", B50, 50},
+  {"75", B75, 75},
+  {"110", B110, 110},
+  {"134", B134, 134},
+  {"134.5", B134, 134},
+  {"150", B150, 150},
+  {"200", B200, 200},
+  {"300", B300, 300},
+  {"600", B600, 600},
+  {"1200", B1200, 1200},
+  {"1800", B1800, 1800},
+  {"2400", B2400, 2400},
+  {"4800", B4800, 4800},
+  {"9600", B9600, 9600},
+  {"19200", B19200, 19200},
+  {"38400", B38400, 38400},
+  {"exta", B19200, 19200},
+  {"extb", B38400, 38400},
+#ifdef B57600
+  {"57600", B57600, 57600},
+#endif
+#ifdef B115200
+  {"115200", B115200, 115200},
+#endif
+  {NULL, 0, 0}
+};
+
+speed_t
+string_to_baud (arg)
+     char *arg;
+{
+  int i;
+
+  for (i = 0; speeds[i].string != NULL; ++i)
+    if (!strcmp (arg, speeds[i].string))
+      return speeds[i].speed;
+  return (speed_t) -1;
+}
+
+unsigned long
+baud_to_value (speed)
+     speed_t speed;
+{
+  int i;
+
+  for (i = 0; speeds[i].string != NULL; ++i)
+    if (speed == speeds[i].speed)
+      return speeds[i].value;
+  return 0;
+}
+
+void
+sane_mode (mode)
+     struct termios *mode;
+{
+  int i;
+  tcflag_t *bitsp;
+
+  for (i = 0; control_info[i].name; ++i)
+    {
+#if VMIN == VEOF
+      if (!strcmp (control_info[i].name, "min"))
+       break;
+#endif
+      mode->c_cc[control_info[i].offset] = control_info[i].saneval;
+    }
+
+  for (i = 0; mode_info[i].name != NULL; ++i)
+    {
+      if (mode_info[i].flags & SANE_SET)
+       {
+         bitsp = mode_type_flag (mode_info[i].type, mode);
+         *bitsp = (*bitsp & ~mode_info[i].mask) | mode_info[i].bits;
+       }
+      else if (mode_info[i].flags & SANE_UNSET)
+       {
+         bitsp = mode_type_flag (mode_info[i].type, mode);
+         *bitsp = *bitsp & ~mode_info[i].mask & ~mode_info[i].bits;
+       }
+    }
+}
+
+/* Return a string that is the printable representation of character CH.  */
+/* Adapted from `cat' by Torbjorn Granlund.  */
+
+char *
+visible (ch)
+     unsigned char ch;
+{
+  static char buf[10];
+  char *bpout = buf;
+
+  if (ch == _POSIX_VDISABLE)
+    return "<undef>";
+
+  if (ch >= 32)
+    {
+      if (ch < 127)
+       *bpout++ = ch;
+      else if (ch == 127)
+       {
+         *bpout++ = '^';
+         *bpout++ = '?';
+       }
+      else
+       {
+         *bpout++ = 'M',
+           *bpout++ = '-';
+         if (ch >= 128 + 32)
+           {
+             if (ch < 128 + 127)
+               *bpout++ = ch - 128;
+             else
+               {
+                 *bpout++ = '^';
+                 *bpout++ = '?';
+               }
+           }
+         else
+           {
+             *bpout++ = '^';
+             *bpout++ = ch - 128 + 64;
+           }
+       }
+    }
+  else
+    {
+      *bpout++ = '^';
+      *bpout++ = ch + 64;
+    }
+  *bpout = '\0';
+  return buf;
+}
+
+/* Parse string S as an integer, using decimal radix by default,
+   but allowing octal and hex numbers as in C.  */
+/* From `od' by Richard Stallman.  */
+
+long
+integer_arg (s)
+     char *s;
+{
+  long value;
+  int radix = 10;
+  char *p = s;
+  int c;
+
+  if (*p != '0')
+    radix = 10;
+  else if (*++p == 'x')
+    {
+      radix = 16;
+      p++;
+    }
+  else
+    radix = 8;
+
+  value = 0;
+  while (((c = *p++) >= '0' && c <= '9')
+        || (radix == 16 && (c & ~40) >= 'A' && (c & ~40) <= 'Z'))
+    {
+      value *= radix;
+      if (c >= '0' && c <= '9')
+       value += c - '0';
+      else
+       value += (c & ~40) - 'A';
+    }
+
+  if (c == 'b')
+    value *= 512;
+  else if (c == 'B')
+    value *= 1024;
+  else
+    p--;
+
+  if (*p)
+    error (1, 0, "invalid integer argument `%s'", s);
+  return value;
+}
diff --git a/src/su.c b/src/su.c
new file mode 100644 (file)
index 0000000..dc29063
--- /dev/null
+++ b/src/su.c
@@ -0,0 +1,519 @@
+/* su for GNU.  Run a shell with substitute user and group IDs.
+   Copyright (C) 1992 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Run a shell with the real and effective UID and GID and groups
+   of USER, default `root'.
+
+   The shell run is taken from USER's password entry, /bin/sh if
+   none is specified there.  If the account has a password, su
+   prompts for a password unless run by a user with real UID 0.
+
+   Does not change the current directory.
+   Sets `HOME' and `SHELL' from the password entry for USER, and if
+   USER is not root, sets `USER' and `LOGNAME' to USER.
+   The subshell is not a login shell.
+
+   If one or more ARGs are given, they are passed as additional
+   arguments to the subshell.
+
+   Does not handle /bin/sh or other shells specially
+   (setting argv[0] to "-su", passing -c only to certain shells, etc.).
+   I don't see the point in doing that, and it's ugly.
+
+   This program intentionally does not support a "wheel group" that
+   restricts who can su to UID 0 accounts.  RMS considers that to
+   be fascist.
+
+   Options:
+   -, -l, --login      Make the subshell a login shell.
+                       Unset all environment variables except
+                       TERM, HOME and SHELL (set as above), and USER
+                       and LOGNAME (set unconditionally as above), and
+                       set PATH to a default value.
+                       Change to USER's home directory.
+                       Prepend "-" to the shell's name.
+   -c, --commmand=COMMAND
+                       Pass COMMAND to the subshell with a -c option
+                       instead of starting an interactive shell.
+   -f, --fast          Pass the -f option to the subshell.
+   -m, -p, --preserve-environment
+                       Do not change HOME, USER, LOGNAME, SHELL.
+                       Run $SHELL instead of USER's shell from /etc/passwd
+                       unless not the superuser and USER's shell is
+                       restricted.
+                       Overridden by --login and --shell.
+   -s, --shell=shell   Run SHELL instead of USER's shell from /etc/passwd
+                       unless not the superuser and USER's shell is
+                       restricted.
+
+   Compile-time options:
+   -DSYSLOG_SUCCESS    Log successful su's (by default, to root) with syslog.
+   -DSYSLOG_FAILURE    Log failed su's (by default, to root) with syslog.
+
+   -DSYSLOG_NON_ROOT   Log all su's, not just those to root (UID 0).
+   Never logs attempted su's to nonexistent accounts.
+
+   Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include "system.h"
+
+#ifdef HAVE_SYSLOG_H
+#include <syslog.h>
+void log_su ();
+#else
+#ifdef SYSLOG_SUCCESS
+#undef SYSLOG_SUCCESS
+#endif
+#ifdef SYSLOG_FAILURE
+#undef SYSLOG_FAILURE
+#endif
+#ifdef SYSLOG_NON_ROOT
+#undef SYSLOG_NON_ROOT
+#endif
+#endif
+
+#ifdef _POSIX_VERSION
+#include <limits.h>
+#ifdef NGROUPS_MAX
+#undef NGROUPS_MAX
+#endif
+#define NGROUPS_MAX sysconf (_SC_NGROUPS_MAX)
+#else /* not _POSIX_VERSION */
+struct passwd *getpwuid ();
+struct group *getgrgid ();
+uid_t getuid ();
+#include <sys/param.h>
+#if !defined(NGROUPS_MAX) && defined(NGROUPS)
+#define NGROUPS_MAX NGROUPS
+#endif
+#endif /* not _POSIX_VERSION */
+
+#ifdef _POSIX_SOURCE
+#define endgrent()
+#define endpwent()
+#endif
+
+#ifdef HAVE_SHADOW_H
+#include <shadow.h>
+#endif
+
+/* The default PATH for simulated logins to non-superuser accounts.  */
+#define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
+
+/* The default PATH for simulated logins to superuser accounts.  */
+#define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
+
+/* The shell to run if none is given in the user's passwd entry.  */
+#define DEFAULT_SHELL "/bin/sh"
+
+/* The user to become if none is specified.  */
+#define DEFAULT_USER "root"
+
+char *crypt ();
+char *getpass ();
+char *getusershell ();
+void endusershell ();
+void setusershell ();
+
+char *basename ();
+char *concat ();
+char *xmalloc ();
+char *xrealloc ();
+int correct_password ();
+int elements ();
+int restricted_shell ();
+void change_identity ();
+void error ();
+void modify_environment ();
+void run_shell ();
+void usage ();
+void xputenv ();
+
+extern char **environ;
+
+/* The name this program was run with.  */
+char *program_name;
+
+/* If nonzero, pass the `-f' option to the subshell.  */
+int fast_startup;
+
+/* If nonzero, simulate a login instead of just starting a shell.  */
+int simulate_login;
+
+/* If nonzero, change some environment vars to indicate the user su'd to.  */
+int change_environment;
+
+struct option longopts[] =
+{
+  {"command", 1, 0, 'c'},
+  {"fast", 0, &fast_startup, 1},
+  {"login", 0, &simulate_login, 1},
+  {"preserve-environment", 0, &change_environment, 0},
+  {"shell", 1, 0, 's'},
+  {0, 0, 0, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int optc;
+  char *new_user = DEFAULT_USER;
+  char *command = 0;
+  char **additional_args = 0;
+  char *shell = 0;
+  struct passwd *pw;
+
+  program_name = argv[0];
+  fast_startup = 0;
+  simulate_login = 0;
+  change_environment = 1;
+
+  while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, (int *) 0))
+        != EOF)
+    {
+      switch (optc)
+       {
+       case 0:
+         break;
+       case 'c':
+         command = optarg;
+         break;
+       case 'f':
+         fast_startup = 1;
+         break;
+       case 'l':
+         simulate_login = 1;
+         break;
+       case 'm':
+       case 'p':
+         change_environment = 0;
+         break;
+       case 's':
+         shell = optarg;
+         break;
+       default:
+         usage ();
+       }
+    }
+  if (optind < argc && !strcmp (argv[optind], "-"))
+    {
+      simulate_login = 1;
+      ++optind;
+    }
+  if (optind < argc)
+    new_user = argv[optind++];
+  if (optind < argc)
+    additional_args = argv + optind;
+
+  pw = getpwnam (new_user);
+  if (pw == 0)
+    error (1, 0, "user %s does not exist", new_user);
+  endpwent ();
+  if (!correct_password (pw))
+    {
+#ifdef SYSLOG_FAILURE
+      log_su (pw, 0);
+#endif
+      error (1, 0, "incorrect password");
+    }
+#ifdef SYSLOG_SUCCESS
+  else
+    {
+      log_su (pw, 1);
+    }
+#endif
+
+  if (pw->pw_shell == 0 || pw->pw_shell[0] == 0)
+    pw->pw_shell = DEFAULT_SHELL;
+  if (shell == 0 && change_environment == 0)
+    shell = getenv ("SHELL");
+  if (shell != 0 && getuid () && restricted_shell (pw->pw_shell))
+    {
+      /* The user being su'd to has a nonstandard shell, and so is
+        probably a uucp account or has restricted access.  Don't
+        compromise the account by allowing access with a standard
+        shell.  */
+      error (0, 0, "using restricted shell %s", pw->pw_shell);
+      shell = 0;
+    }
+  if (shell == 0)
+    shell = pw->pw_shell;
+  shell = strcpy (xmalloc (strlen (shell) + 1), shell);
+  modify_environment (pw, shell);
+
+  change_identity (pw);
+  if (simulate_login && chdir (pw->pw_dir))
+    error (0, errno, "warning: cannot change directory to %s", pw->pw_dir);
+  run_shell (shell, command, additional_args);
+}
+
+/* Ask the user for a password.
+   Return 1 if the user gives the correct password for entry PW,
+   0 if not.  Return 1 without asking for a password if run by UID 0
+   or if PW has an empty password.  */
+
+int
+correct_password (pw)
+     struct passwd *pw;
+{
+  char *unencrypted, *encrypted, *correct;
+#ifdef HAVE_SHADOW_H
+  /* Shadow passwd stuff for SVR3 and maybe other systems.  */
+  struct spwd *sp = getspnam (pw->pw_name);
+
+  endspent ();
+  if (sp)
+    correct = sp->sp_pwdp;
+  else
+#endif
+  correct = pw->pw_passwd;
+
+  if (getuid () == 0 || correct == 0 || correct[0] == '\0')
+    return 1;
+
+  unencrypted = getpass ("Password:");
+  encrypted = crypt (unencrypted, correct);
+  bzero (unencrypted, strlen (unencrypted));
+  return strcmp (encrypted, correct) == 0;
+}
+
+/* Update `environ' for the new shell based on PW, with SHELL being
+   the value for the SHELL environment variable.  */
+
+void
+modify_environment (pw, shell)
+     struct passwd *pw;
+     char *shell;
+{
+  char *term;
+
+  if (simulate_login)
+    {
+      /* Leave TERM unchanged.  Set HOME, SHELL, USER, LOGNAME, PATH.
+         Unset all other environment variables.  */
+      term = getenv ("TERM");
+      environ = (char **) xmalloc (2 * sizeof (char *));
+      environ[0] = 0;
+      if (term)
+       xputenv (concat ("TERM", "=", term));
+      xputenv (concat ("HOME", "=", pw->pw_dir));
+      xputenv (concat ("SHELL", "=", shell));
+      xputenv (concat ("USER", "=", pw->pw_name));
+      xputenv (concat ("LOGNAME", "=", pw->pw_name));
+      xputenv (concat ("PATH", "=", pw->pw_uid
+                      ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH));
+    }
+  else
+    {
+      /* Set HOME, SHELL, and if not becoming a super-user,
+        USER and LOGNAME.  */
+      if (change_environment)
+       {
+         xputenv (concat ("HOME", "=", pw->pw_dir));
+         xputenv (concat ("SHELL", "=", shell));
+         if (pw->pw_uid)
+           {
+             xputenv (concat ("USER", "=", pw->pw_name));
+             xputenv (concat ("LOGNAME", "=", pw->pw_name));
+           }
+       }
+    }
+}
+
+/* Become the user and group(s) specified by PW.  */
+
+void
+change_identity (pw)
+     struct passwd *pw;
+{
+#ifdef NGROUPS_MAX
+  errno = 0;
+  if (initgroups (pw->pw_name, pw->pw_gid) == -1)
+    error (1, errno, "cannot set groups");
+  endgrent ();
+#endif
+  if (setgid (pw->pw_gid))
+    error (1, errno, "cannot set group id");
+  if (setuid (pw->pw_uid))
+    error (1, errno, "cannot set user id");
+}
+
+/* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
+   If COMMAND is nonzero, pass it to the shell with the -c option.
+   If ADDITIONAL_ARGS is nonzero, pass it to the shell as more
+   arguments.  */
+
+void
+run_shell (shell, command, additional_args)
+     char *shell;
+     char *command;
+     char **additional_args;
+{
+  char **args;
+  int argno = 1;
+
+  if (additional_args)
+    args = (char **) xmalloc (sizeof (char *)
+                             * (10 + elements (additional_args)));
+  else
+    args = (char **) xmalloc (sizeof (char *) * 10);
+  if (simulate_login)
+    {
+      args[0] = xmalloc (strlen (shell) + 2);
+      args[0][0] = '-';
+      strcpy (args[0] + 1, basename (shell));
+    }
+  else
+    args[0] = basename (shell);
+  if (fast_startup)
+    args[argno++] = "-f";
+  if (command)
+    {
+      args[argno++] = "-c";
+      args[argno++] = command;
+    }
+  if (additional_args)
+    for (; *additional_args; ++additional_args)
+      args[argno++] = *additional_args;
+  args[argno] = 0;
+  execv (shell, args);
+  error (1, errno, "cannot run %s", shell);
+}
+
+#if defined (SYSLOG_SUCCESS) || defined (SYSLOG_FAILURE)
+/* Log the fact that someone has run su to the user given by PW;
+   if SUCCESSFUL is nonzero, they gave the correct password, etc.  */
+
+void
+log_su (pw, successful)
+     struct passwd *pw;
+     int successful;
+{
+  char *new_user, *old_user, *tty;
+
+#ifndef SYSLOG_NON_ROOT
+  if (pw->pw_uid)
+    return;
+#endif
+  new_user = pw->pw_name;
+  /* The utmp entry (via getlogin) is probably the best way to identify
+     the user, especially if someone su's from a su-shell.  */
+  old_user = getlogin ();
+  if (old_user == 0)
+    old_user = "";
+  tty = ttyname (2);
+  if (tty == 0)
+    tty = "";
+  /* 4.2BSD openlog doesn't have the third parameter.  */
+  openlog (basename (program_name), 0
+#ifdef LOG_AUTH
+          , LOG_AUTH
+#endif
+          );
+  syslog (LOG_NOTICE,
+#ifdef SYSLOG_NON_ROOT
+         "%s(to %s) %s on %s",
+#else
+         "%s%s on %s",
+#endif
+         successful ? "" : "FAILED SU ",
+#ifdef SYSLOG_NON_ROOT
+         new_user,
+#endif
+         old_user, tty);
+  closelog ();
+}
+#endif
+
+/* Return 1 if SHELL is a restricted shell (one not returned by
+   getusershell), else 0, meaning it is a standard shell.  */
+
+int
+restricted_shell (shell)
+     char *shell;
+{
+  char *line;
+
+  setusershell ();
+  while (line = getusershell ())
+    {
+      if (*line != '#' && strcmp (line, shell) == 0)
+       {
+         endusershell ();
+         return 0;
+       }
+    }
+  endusershell ();
+  return 1;
+}
+
+/* Return the number of elements in ARR, a null-terminated array.  */
+
+int
+elements (arr)
+     char **arr;
+{
+  int n = 0;
+
+  for (n = 0; *arr; ++arr)
+    ++n;
+  return n;
+}
+
+/* Add VAL to the environment, checking for out of memory errors.  */
+
+void
+xputenv (val)
+     char *val;
+{
+  if (putenv (val))
+    error (1, 0, "virtual memory exhausted");
+}
+
+/* Return a newly-allocated string whose contents concatenate
+   those of S1, S2, S3.  */
+
+char *
+concat (s1, s2, s3)
+     char *s1, *s2, *s3;
+{
+  int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
+  char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
+
+  strcpy (result, s1);
+  strcpy (result + len1, s2);
+  strcpy (result + len1 + len2, s3);
+  result[len1 + len2 + len3] = 0;
+
+  return result;
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-flmp] [-c command] [-s shell] [--login] [--fast]\n\
+       [--preserve-environment] [--command=command] [--shell=shell] [-]\n\
+       [user [arg...]]\n", program_name);
+  exit (1);
+}
diff --git a/src/tee.c b/src/tee.c
new file mode 100644 (file)
index 0000000..721014b
--- /dev/null
+++ b/src/tee.c
@@ -0,0 +1,155 @@
+/* tee - read from standard input and write to standard output and files.
+   Copyright (C) 1985, 1990, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Mike Parker, Richard M. Stallman, and David MacKenzie */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <getopt.h>
+#include "system.h"
+
+char *xmalloc ();
+int tee ();
+void error ();
+void xwrite ();
+
+/* If nonzero, append to output files rather than truncating them. */
+int append;
+
+/* If nonzero, ignore interrupts. */
+int ignore_interrupts;
+
+/* The name that this program was run with. */
+char *program_name;
+
+struct option long_options[] =
+{
+  {"append", 0, NULL, 'a'},
+  {"ignore-interrupts", 0, NULL, 'i'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int errs;
+  int optc;
+       
+  program_name = argv[0];
+  append = 0;
+  ignore_interrupts = 0;
+
+  while ((optc = getopt_long (argc, argv, "ai", long_options, (int *) 0))
+        != EOF)
+    {
+      switch (optc)
+       {
+       case 'a':
+         append = 1;
+         break;
+       case 'i':
+         ignore_interrupts = 1;
+         break;
+       default:
+         fprintf (stderr, "\
+Usage: %s [-ai] [--append] [--ignore-interrupts] [file...]\n",
+                  program_name);
+         exit (1);
+       }
+    }
+
+  if (ignore_interrupts)
+#ifdef _POSIX_VERSION
+    {
+      struct sigaction sigact;
+
+      sigact.sa_handler = SIG_IGN;
+      sigemptyset (&sigact.sa_mask);
+      sigact.sa_flags = 0;
+      sigaction (SIGINT, &sigact, NULL);
+    }
+#else                          /* !_POSIX_VERSION */
+    signal (SIGINT, SIG_IGN);
+#endif                         /* _POSIX_VERSION */
+
+  errs = tee (argc - optind, &argv[optind]);
+  if (close (0) == -1)
+    error (1, errno, "standard input");
+  if (close (1) == -1)
+    error (1, errno, "standard output");
+  exit (errs);
+}
+
+/* Copy the standard input into each of the NFILES files in FILES
+   and into the standard output.
+   Return 0 if successful, 1 if any errors occur. */
+
+int
+tee (nfiles, files)
+     int nfiles;
+     char **files;
+{
+  int *descriptors;
+  char buffer[BUFSIZ];
+  register int bytes_read, i, ret = 0, mode;
+
+  if (nfiles)
+    descriptors = (int *) xmalloc (nfiles * sizeof (int));
+  mode = O_WRONLY | O_CREAT;
+  if (append)
+    mode |= O_APPEND;
+  else
+    mode |= O_TRUNC;
+
+  for (i = 0; i < nfiles; i++)
+    {
+      descriptors[i] = open (files[i], mode, 0666);
+      if (descriptors[i] == -1)
+       {
+         error (0, errno, "%s", files[i]);
+         ret = 1;
+       }
+    }
+
+  while ((bytes_read = read (0, buffer, sizeof buffer)) > 0)
+    {
+      xwrite (1, buffer, bytes_read);
+      for (i = 0; i < nfiles; i++)
+       if (descriptors[i] != -1)
+         xwrite (descriptors[i], buffer, bytes_read);
+    }
+  if (bytes_read == -1)
+    {
+      error (0, errno, "read error");
+      ret = 1;
+    }
+
+  for (i = 0; i < nfiles; i++)
+    if (descriptors[i] != -1 && close (descriptors[i]) == -1)
+      {
+       error (0, errno, "%s", files[i]);
+       ret = 1;
+      }
+
+  if (nfiles)
+    free (descriptors);
+
+  return ret;
+}
diff --git a/src/test.c b/src/test.c
new file mode 100644 (file)
index 0000000..a78af09
--- /dev/null
@@ -0,0 +1,1054 @@
+/* GNU test program (ksb and mjb) */
+
+/* Modified to run with the GNU shell by bfox. */
+
+/* Copyright (C) 1987, 1988, 1989, 1990, 1991 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash, the Bourne Again SHell.
+
+   Bash is free software; you can redistribute it and/or modify it under
+   the terms of the GNU General Public License as published by the Free
+   Software Foundation; either version 2, or (at your option) any later
+   version.
+
+   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or
+   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with Bash; see the file COPYING.  If not, write to the Free Software
+   Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* Define TEST_STANDALONE to get the /bin/test version.  Otherwise, you get 
+   the shell builtin version. */
+/* #define TEST_STANDALONE */
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#if !defined (TEST_STANDALONE)
+#  include "shell.h"
+#  include "posixstat.h"
+#  include "filecntl.h"
+#else /* TEST_STANDALONE */
+#  include "system.h"
+#  if !defined (S_IXUGO)
+#    define S_IXUGO 0111
+#  endif /* S_IXUGO */
+#  if defined (_POSIX_VERSION)
+#    include <limits.h>
+#  else /* !_POSIX_VERSION */
+#    include <sys/param.h>
+#  endif /* _POSIX_VERSION */
+#  if defined (NGROUPS_MAX) || defined (_SC_NGROUPS_MAX) || defined (NGROUPS)
+#    define HAVE_GETGROUPS
+#  endif /* NGROUPS_MAX || _SC_NGROUPS_MAX || NGROUPS */
+#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
+#define digit(c)  ((c) >= '0' && (c) <= '9')
+#define digit_value(c) ((c) - '0')
+char *program_name;
+#endif /* TEST_STANDALONE */
+
+#if !defined (_POSIX_VERSION)
+#  include <sys/file.h>
+#endif /* !_POSIX_VERSION */
+
+#include <errno.h>
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+#if !defined (STREQ)
+#  define STREQ(a, b) ((a)[0] == (b)[0] && strcmp (a, b) == 0)
+#endif /* !STREQ */
+
+#if !defined (member)
+#  define member(c, s) (int)((c) ? index ((s), (c)) : 0)
+#endif /* !member */
+
+#if defined (_POSIX_VERSION)
+
+/* Even though SunOS 4, Ultrix 4, and 386BSD are mostly POSIX.1 compliant,
+   their getgroups system call (except in the `System V' environment, which
+   is troublesome in other ways) fills in an array of int, not gid_t
+   (which is `short' on those systems).  Kludge, kludge.  */
+
+#if !defined(sun) && !defined(ultrix) && !defined(__386BSD__)
+#define GETGROUPS_T gid_t
+#else
+#define GETGROUPS_T int
+#endif
+#else /* !_POSIX_VERSION */
+#define GETGROUPS_T int
+#endif /* !_POSIX_VERSION */
+
+extern gid_t getgid (), getegid ();
+extern uid_t geteuid ();
+
+#if !defined (R_OK)
+#define R_OK 4
+#define W_OK 2
+#define X_OK 1
+#define F_OK 0
+#endif /* R_OK */
+
+/* The following few defines control the truth and false output of each stage.
+   TRUE and FALSE are what we use to compute the final output value.
+   SHELL_BOOLEAN is the form which returns truth or falseness in shell terms.
+   TRUTH_OR is how to do logical or with TRUE and FALSE.
+   TRUTH_AND is how to do logical and with TRUE and FALSE..
+   Default is TRUE = 1, FALSE = 0, TRUTH_OR = a | b, TRUTH_AND = a & b,
+    SHELL_BOOLEAN = (!value). */
+#define TRUE 1
+#define FALSE 0
+#define SHELL_BOOLEAN(value) (!(value))
+#define TRUTH_OR(a, b) ((a) | (b))
+#define TRUTH_AND(a, b) ((a) & (b))
+
+#if defined (TEST_STANDALONE)
+#  define test_exit(val) exit (val)
+#else
+   static jmp_buf test_exit_buf;
+   static int test_error_return = 0;
+#  define test_exit(val) test_error_return = val, longjmp (test_exit_buf, 1)
+#endif /* !TEST_STANDALONE */
+
+char *xrealloc ();
+
+static int pos;                /* The offset of the current argument in ARGV. */
+static int argc;       /* The number of arguments present in ARGV. */
+static char **argv;    /* The argument list. */
+
+static int unop ();
+static int binop ();
+static int unary_operator ();
+static int binary_operator ();
+static int two_arguments ();
+static int three_arguments ();
+static int posixtest ();
+
+static int expr ();
+static int term ();
+static int and ();
+static int or ();
+
+static void
+test_syntax_error (format, arg)
+     char *format, *arg;
+{
+  fprintf (stderr, "%s: ", argv[0]);
+  fprintf (stderr, format, arg);
+  fflush (stderr);
+  test_exit (SHELL_BOOLEAN (FALSE));
+}
+
+/* A wrapper for stat () which disallows pathnames that are empty strings. */
+static int
+test_stat (path, finfo)
+     char *path;
+     struct stat *finfo;
+{
+  if (*path == '\0')
+    {
+      errno = ENOENT;
+      return (-1);
+    }
+  return (stat (path, finfo));
+}
+
+/* Do the same thing access(2) does, but use the effective uid and gid,
+   and don't make the mistake of telling root that any file is
+   executable. */
+static int
+eaccess (path, mode)
+     char *path;
+     int mode;
+{
+  extern int group_member ();
+  struct stat st;
+  static int euid = -1;
+
+  if (test_stat (path, &st) < 0)
+    return (-1);
+
+  if (euid == -1)
+    euid = geteuid ();
+
+  if (euid == 0)
+    {
+      /* Root can read or write any file. */
+      if (mode != X_OK)
+       return (0);
+
+      /* Root can execute any file that has any one of the execute
+        bits set. */
+      if (st.st_mode & S_IXUGO)
+       return (0);
+    }
+
+  if (st.st_uid == euid)        /* owner */
+    mode <<= 6;
+  else if (group_member (st.st_gid))
+    mode <<= 3;
+
+  if (st.st_mode & mode)
+    return (0);
+
+  return (-1);
+}
+
+#if defined (HAVE_GETGROUPS)
+/* The number of groups that this user is a member of. */
+static int ngroups = 0;
+static GETGROUPS_T *group_array = (GETGROUPS_T *)NULL;
+static int default_group_array_size = 0;
+#endif /* HAVE_GETGROUPS */
+
+/* Return non-zero if GID is one that we have in our groups list. */
+int
+group_member (gid)
+     gid_t gid;
+{
+#if !defined (HAVE_GETGROUPS)
+  return ((gid == getgid ()) || (gid == getegid ()));
+#else
+  register int i;
+
+  /* getgroups () returns the number of elements that it was able to
+     place into the array.  We simply continue to call getgroups ()
+     until the number of elements placed into the array is smaller than
+     the physical size of the array. */
+
+  while (ngroups == default_group_array_size)
+    {
+      default_group_array_size += 64;
+
+      group_array = (GETGROUPS_T *)
+       xrealloc (group_array,
+                 default_group_array_size * sizeof (GETGROUPS_T));
+
+      ngroups = getgroups (default_group_array_size, group_array);
+    }
+
+  /* In case of error, the user loses. */
+  if (ngroups < 0)
+    return (0);
+
+  /* Search through the list looking for GID. */
+  for (i = 0; i < ngroups; i++)
+    if (gid == group_array[i])
+      return (1);
+
+  return (0);
+#endif /* HAVE_GETGROUPS */
+}
+
+/* Increment our position in the argument list.  Check that we're not
+   past the end of the argument list.  This check is supressed if the
+   argument is FALSE.  Made a macro for efficiency. */
+#if !defined (lint)
+#define advance(f)     (++pos, f && (pos < argc ? 0 : beyond()))
+#endif
+
+#if !defined (advance)
+static int
+advance (f)
+     int f;
+{
+  ++pos;
+
+  if (f && pos >= argc)
+    beyond ();
+}
+#endif /* advance */
+
+#define unary_advance() (advance (1),++pos)
+
+/*
+ * beyond - call when we're beyond the end of the argument list (an
+ *     error condition)
+ */
+static int
+beyond ()
+{
+  test_syntax_error ("argument expected\n", (char *)NULL);
+}
+
+/* Syntax error for when an integer argument was expected, but
+   something else was found. */
+static void
+integer_expected_error (pch)
+     char *pch;
+{
+  test_syntax_error ("integer expression expected %s\n", pch);
+}
+
+/* Return non-zero if the characters pointed to by STRING constitute a
+   valid number.  Stuff the converted number into RESULT if RESULT is
+   a non-null pointer to a long. */
+static int
+isint (string, result)
+     register char *string;
+     long *result;
+{
+  int sign;
+  long value;
+
+  sign = 1;
+  value = 0;
+
+  if (result)
+    *result = 0;
+
+  /* Skip leading whitespace characters. */
+  while (whitespace (*string))
+    string++;
+
+  if (!*string)
+    return (0);
+
+  /* We allow leading `-' or `+'. */
+  if (*string == '-' || *string == '+')
+    {
+      if (!digit (string[1]))
+       return (0);
+
+      if (*string == '-')
+       sign = -1;
+
+      string++;
+    }
+
+  while (digit (*string))
+    {
+      if (result)
+       value = (value * 10) + digit_value (*string);
+      string++;
+    }
+
+  /* Skip trailing whitespace, if any. */
+  while (whitespace (*string))
+    string++;
+
+  /* Error if not at end of string. */
+  if (*string)
+    return (0);
+
+  if (result)
+    {
+      value *= sign;
+      *result = value;
+    }
+
+  return (1);
+}
+
+/* Find the modification time of FILE, and stuff it into AGE, a pointer
+   to a long.  Return non-zero if successful, else zero. */
+static int
+age_of (filename, age)
+     char *filename;
+     long *age;
+{
+  struct stat finfo;
+
+  if (test_stat (filename, &finfo) < 0)
+    return (0);
+
+  if (age)
+    *age = finfo.st_mtime;
+
+  return (1);
+}
+
+/*
+ * term - parse a term and return 1 or 0 depending on whether the term
+ *     evaluates to true or false, respectively.
+ *
+ * term ::=
+ *     '-'('h'|'d'|'f'|'r'|'s'|'w'|'c'|'b'|'p'|'u'|'g'|'k') filename
+ *     '-'('L'|'x') filename
+ *     '-t' [ int ]
+ *     '-'('z'|'n') string
+ *     string
+ *     string ('!='|'=') string
+ *     <int> '-'(eq|ne|le|lt|ge|gt) <int>
+ *     file '-'(nt|ot|ef) file
+ *     '(' <expr> ')'
+ * int ::=
+ *     '-l' string
+ *     positive and negative integers
+ */
+static int
+term ()
+{
+  int value;
+
+  if (pos >= argc)
+    beyond ();
+
+  /* Deal with leading "not"'s. */
+  if ('!' == argv[pos][0] && '\000' == argv[pos][1])
+    {
+      value = FALSE;
+      while (pos < argc && '!' == argv[pos][0] && '\000' == argv[pos][1])
+       {
+         advance (1);
+         value ^= (TRUE);
+       }
+
+      return (value ^ (term ()));
+    }
+
+  /* A paren-bracketed argument. */  
+  if (argv[pos][0] == '(' && !argv[pos][1])
+    {
+      advance (1);
+      value = expr ();
+      if (argv[pos][0] != ')' || argv[pos][1])
+       test_syntax_error ("')' expected, found %s\n", argv[pos]);
+      advance (0);
+      return (TRUE == (value));
+    }
+
+  /* are there enough arguments left that this could be dyadic? */
+  if (((pos + 3 <= argc) && binop (argv[pos + 1])) ||
+      ((pos + 4 <= argc && STREQ (argv[pos], "-l") && binop (argv[pos + 2]))))
+    value = binary_operator ();
+
+  /* Might be a switch type argument */
+  else if ('-' == argv[pos][0] && 0 == argv[pos][2])
+    {
+      if (unop (argv[pos][1]))
+       value = unary_operator ();
+      else
+       test_syntax_error ("%s: unary operator expected\n", argv[pos]);
+    }
+  else
+    {
+      value = (argv[pos][0] != '\0');
+      advance (0);
+    }
+
+  return (value);
+}
+
+static int
+binary_operator ()
+{
+  register int op;
+  struct stat stat_buf, stat_spare;
+  long int l, r, value;
+  /* Are the left and right integer expressions of the form '-l string'? */
+  int l_is_l, r_is_l;
+
+  if (strcmp (argv[pos], "-l") == 0)
+    {
+      l_is_l = 1;
+      op = pos + 2;
+
+      /* Make sure that OP is still a valid binary operator. */
+      if ((op >= argc - 1) || (binop (argv[op]) == 0))
+       test_syntax_error ("%s: binary operator expected\n", argv[op]);
+
+      advance (0);
+    }
+  else
+    {
+      l_is_l = 0;
+      op = pos + 1;
+    }
+
+  if ((op < argc - 2) && (strcmp (argv[op + 1], "-l") == 0))
+    {
+      r_is_l = 1;
+      advance (0);
+    }
+  else
+    r_is_l = 0;
+
+  if (argv[op][0] == '-')
+    {
+      /* check for eq, nt, and stuff */
+      switch (argv[op][1])
+       {
+       default:
+         break;
+
+       case 'l':
+         if (argv[op][2] == 't' && !argv[op][3])
+           {
+             /* lt */
+             if (l_is_l)
+               l = strlen (argv[op - 1]);
+             else
+               {
+                 if (!isint (argv[op - 1], &l))
+                   integer_expected_error ("before -lt");
+               }
+
+             if (r_is_l)
+               r = strlen (argv[op + 2]);
+             else
+               {
+                 if (!isint (argv[op + 1], &r))
+                   integer_expected_error ("after -lt");
+               }
+             pos += 3;
+             return (TRUE == (l < r));
+           }
+
+         if (argv[op][2] == 'e' && !argv[op][3])
+           {
+             /* le */
+             if (l_is_l)
+               l = strlen (argv[op - 1]);
+             else
+               {
+                 if (!isint (argv[op - 1], &l))
+                   integer_expected_error ("before -le");
+               }
+             if (r_is_l)
+               r = strlen (argv[op + 2]);
+             else
+               {
+                 if (!isint (argv[op + 1], &r))
+                   integer_expected_error ("after -le");
+               }
+             pos += 3;
+             return (TRUE == (l <= r));
+           }
+         break;
+
+       case 'g':
+         if (argv[op][2] == 't' && !argv[op][3])
+           {
+             /* gt integer greater than */
+             if (l_is_l)
+               l = strlen (argv[op - 1]);
+             else
+               {
+                 if (!isint (argv[op - 1], &l))
+                   integer_expected_error ("before -gt");
+               }
+             if (r_is_l)
+               r = strlen (argv[op + 2]);
+             else
+               {
+                 if (!isint (argv[op + 1], &r))
+                   integer_expected_error ("after -gt");
+               }
+             pos += 3;
+             return (TRUE == (l > r));
+           }
+
+         if (argv[op][2] == 'e' && !argv[op][3])
+           {
+             /* ge - integer greater than or equal to */
+             if (l_is_l)
+               l = strlen (argv[op - 1]);
+             else
+               {
+                 if (!isint (argv[op - 1], &l))
+                   integer_expected_error ("before -ge");
+               }
+             if (r_is_l)
+               r = strlen (argv[op + 2]);
+             else
+               {
+                 if (!isint (argv[op + 1], &r))
+                   integer_expected_error ("after -ge");
+               }
+             pos += 3;
+             return (TRUE == (l >= r));
+           }
+         break;
+
+       case 'n':
+         if (argv[op][2] == 't' && !argv[op][3])
+           {
+             /* nt - newer than */
+             pos += 3;
+             if (l_is_l || r_is_l)
+               test_syntax_error ("-nt does not accept -l\n", (char *)NULL);
+             if (age_of (argv[op - 1], &l) && age_of (argv[op + 1], &r))
+               return (TRUE == (l > r));
+             else
+               return (FALSE);
+           }
+
+         if (argv[op][2] == 'e' && !argv[op][3])
+           {
+             /* ne - integer not equal */
+             if (l_is_l)
+               l = strlen (argv[op - 1]);
+             else
+               {
+                 if (!isint (argv[op - 1], &l))
+                   integer_expected_error ("before -ne");
+               }
+             if (r_is_l)
+               r = strlen (argv[op + 2]);
+             else
+               {
+                 if (!isint (argv[op + 1], &r))
+                   integer_expected_error ("after -ne");
+               }
+             pos += 3;
+             return (TRUE == (l != r));
+           }
+         break;
+
+       case 'e':
+         if (argv[op][2] == 'q' && !argv[op][3])
+           {
+             /* eq - integer equal */
+             if (l_is_l)
+               l = strlen (argv[op - 1]);
+             else
+               {
+                 if (!isint (argv[op - 1], &l))
+                   integer_expected_error ("before -eq");
+               }
+             if (r_is_l)
+               r = strlen (argv[op + 2]);
+             else
+               {
+                 if (!isint (argv[op + 1], &r))
+                   integer_expected_error ("after -eq");
+               }
+             pos += 3;
+             return (TRUE == (l == r));
+           }
+
+         if (argv[op][2] == 'f' && !argv[op][3])
+           {
+             /* ef - hard link? */
+             pos += 3;
+             if (l_is_l || r_is_l)
+               test_syntax_error ("-ef does not accept -l\n", (char *)NULL);
+             if (stat (argv[op - 1], &stat_buf) < 0)
+               return (FALSE);
+             if (stat (argv[op + 1], &stat_spare) < 0)
+               return (FALSE);
+             return (TRUE ==
+                     (stat_buf.st_dev == stat_spare.st_dev &&
+                      stat_buf.st_ino == stat_spare.st_ino));
+           }
+         break;
+
+       case 'o':
+         if ('t' == argv[op][2] && '\000' == argv[op][3])
+           {
+             /* ot - older than */
+             pos += 3;
+             if (l_is_l || r_is_l)
+               test_syntax_error ("-nt does not accept -l\n", (char *)NULL);
+             if (age_of (argv[op - 1], &l) && age_of (argv[op + 1], &r))
+               return (TRUE == (l < r));
+             return (FALSE);
+           }
+         break;
+       }
+      test_syntax_error ("unknown binary operator", argv[op]);
+    }
+
+  if (argv[op][0] == '=' && !argv[op][1])
+    {
+      value = (strcmp (argv[pos], argv[pos + 2]) == 0);
+      pos += 3;
+      return (TRUE == value);
+    }
+
+  if (strcmp (argv[op], "!=") == 0)
+    {
+      value = (strcmp (argv[pos], argv[pos + 2]) != 0);
+      pos += 3;
+      return (TRUE == value);
+    }
+}
+
+static int
+unary_operator ()
+{
+  long r, value;
+  struct stat stat_buf;
+
+  switch (argv[pos][1])
+    {
+    default:
+      return (FALSE);
+
+      /* All of the following unary operators use unary_advance (), which
+        checks to make sure that there is an argument, and then advances
+        pos right past it.  This means that pos - 1 is the location of the
+        argument. */
+
+    case 'a':                  /* file exists in the file system? */
+    case 'e':
+      unary_advance ();
+      value = -1 != test_stat (argv[pos - 1], &stat_buf);
+      return (TRUE == value);
+
+    case 'r':                  /* file is readable? */
+      unary_advance ();
+      value = -1 != eaccess (argv[pos - 1], R_OK);
+      return (TRUE == value);
+
+    case 'w':                  /* File is writeable? */
+      unary_advance ();
+      value = -1 != eaccess (argv[pos - 1], W_OK);
+      return (TRUE == value);
+
+    case 'x':                  /* File is executable? */
+      unary_advance ();
+      value = -1 != eaccess (argv[pos - 1], X_OK);
+      return (TRUE == value);
+
+    case 'O':                  /* File is owned by you? */
+      unary_advance ();
+      if (test_stat (argv[pos - 1], &stat_buf) < 0)
+       return (FALSE);
+
+      return (TRUE == (geteuid () == stat_buf.st_uid));
+
+    case 'G':                  /* File is owned by your group? */
+      unary_advance ();
+      if (test_stat (argv[pos - 1], &stat_buf) < 0)
+       return (FALSE);
+
+      return (TRUE == (getegid () == stat_buf.st_gid));
+
+    case 'f':                  /* File is a file? */
+      unary_advance ();
+      if (test_stat (argv[pos - 1], &stat_buf) < 0)
+       return (FALSE);
+
+      /* Under POSIX, -f is true if the given file exists
+        and is a regular file. */
+      return (TRUE == ((S_ISREG (stat_buf.st_mode)) ||
+                      (0 == (stat_buf.st_mode & S_IFMT))));
+
+    case 'd':                  /* File is a directory? */
+      unary_advance ();
+      if (test_stat (argv[pos - 1], &stat_buf) < 0)
+       return (FALSE);
+
+      return (TRUE == (S_ISDIR (stat_buf.st_mode)));
+
+    case 's':                  /* File has something in it? */
+      unary_advance ();
+      if (test_stat (argv[pos - 1], &stat_buf) < 0)
+       return (FALSE);
+
+      return (TRUE == (stat_buf.st_size > (off_t) 0));
+
+    case 'S':                  /* File is a socket? */
+#if !defined (S_ISSOCK)
+      return (FALSE);
+#else
+      unary_advance ();
+
+      if (test_stat (argv[pos - 1], &stat_buf) < 0)
+       return (FALSE);
+
+      return (TRUE == (S_ISSOCK (stat_buf.st_mode)));
+#endif                         /* S_ISSOCK */
+
+    case 'c':                  /* File is character special? */
+      unary_advance ();
+      if (test_stat (argv[pos - 1], &stat_buf) < 0)
+       return (FALSE);
+
+      return (TRUE == (S_ISCHR (stat_buf.st_mode)));
+
+    case 'b':                  /* File is block special? */
+      unary_advance ();
+      if (test_stat (argv[pos - 1], &stat_buf) < 0)
+       return (FALSE);
+
+      return (TRUE == (S_ISBLK (stat_buf.st_mode)));
+
+    case 'p':                  /* File is a named pipe? */
+      unary_advance ();
+#ifndef S_ISFIFO
+      return (FALSE);
+#else
+      if (test_stat (argv[pos - 1], &stat_buf) < 0)
+       return (FALSE);
+      return (TRUE == (S_ISFIFO (stat_buf.st_mode)));
+#endif                         /* S_ISFIFO */
+
+    case 'L':                  /* Same as -h  */
+      /*FALLTHROUGH*/
+
+    case 'h':                  /* File is a symbolic link? */
+      unary_advance ();
+#ifndef S_ISLNK
+      return (FALSE);
+#else
+      /* An empty filename is not a valid pathname. */
+      if ((argv[pos - 1][0] == '\0') ||
+         (lstat (argv[pos - 1], &stat_buf) < 0))
+       return (FALSE);
+
+      return (TRUE == (S_ISLNK (stat_buf.st_mode)));
+#endif                         /* S_IFLNK */
+
+    case 'u':                  /* File is setuid? */
+      unary_advance ();
+      if (test_stat (argv[pos - 1], &stat_buf) < 0)
+       return (FALSE);
+
+      return (TRUE == (0 != (stat_buf.st_mode & S_ISUID)));
+
+    case 'g':                  /* File is setgid? */
+      unary_advance ();
+      if (test_stat (argv[pos - 1], &stat_buf) < 0)
+       return (FALSE);
+
+      return (TRUE == (0 != (stat_buf.st_mode & S_ISGID)));
+
+    case 'k':                  /* File has sticky bit set? */
+      unary_advance ();
+      if (test_stat (argv[pos - 1], &stat_buf) < 0)
+       return (FALSE);
+#if !defined (S_ISVTX)
+      /* This is not Posix, and is not defined on some Posix systems. */
+      return (FALSE);
+#else
+      return (TRUE == (0 != (stat_buf.st_mode & S_ISVTX)));
+#endif
+
+    case 't':                  /* File (fd) is a terminal?  (fd) defaults to stdout. */
+      advance (0);
+      if (pos < argc && isint (argv[pos], &r))
+       {
+         advance (0);
+         return (TRUE == (isatty ((int) r)));
+       }
+      return (TRUE == (isatty (1)));
+
+    case 'n':                  /* True if arg has some length. */
+      unary_advance ();
+      return (TRUE == (argv[pos - 1][0] != 0));
+
+    case 'z':                  /* True if arg has no length. */
+      unary_advance ();
+      return (TRUE == (argv[pos - 1][0] == '\0'));
+    }
+}
+       
+/*
+ * and:
+ *     term
+ *     term '-a' and
+ */
+static int
+and ()
+{
+  int value;
+
+  value = term ();
+  while ((pos < argc) && strcmp (argv[pos], "-a") == 0)
+    {
+      advance (0);
+      value = TRUTH_AND (value, and ());
+    }
+  return (TRUE == value);
+}
+
+/*
+ * or:
+ *     and
+ *     and '-o' or
+ */
+static int
+or ()
+{
+  int value;
+
+  value = and ();
+
+  while ((pos < argc) && strcmp (argv[pos], "-o") == 0)
+    {
+      advance (0);
+      value = TRUTH_OR (value, or ());
+    }
+
+  return (TRUE == value);
+}
+
+/*
+ * expr:
+ *     or
+ */
+static int
+expr ()
+{
+  if (pos >= argc)
+    beyond ();
+
+  return (FALSE ^ (or ()));            /* Same with this. */
+}
+
+/* Return TRUE if S is one of the test command's binary operators. */
+static int
+binop (s)
+     char *s;
+{
+  return ((STREQ (s,   "=")) || (STREQ (s,  "!=")) || (STREQ (s, "-nt")) ||
+         (STREQ (s, "-ot")) || (STREQ (s, "-ef")) || (STREQ (s, "-eq")) ||
+         (STREQ (s, "-ne")) || (STREQ (s, "-lt")) || (STREQ (s, "-le")) ||
+         (STREQ (s, "-gt")) || (STREQ (s, "-ge")));
+}
+
+/* Return non-zero if OP is one of the test command's unary operators. */
+static int
+unop (op)
+     int op;
+{
+  return (member (op, "abcdefgkLhprsStuwxOGnz"));
+}
+
+static int
+two_arguments ()
+{
+  int value;
+
+  if (STREQ (argv[pos], "!"))
+    value = strlen (argv[pos+1]) == 0;
+  else if ((argv[pos][0] == '-') && (argv[pos][2] == '\0'))
+    {
+      if (unop (argv[pos][1]))
+       value = unary_operator ();
+      else
+       test_syntax_error ("%s: unary operator expected\n", argv[pos]);
+    }
+  else
+    beyond ();
+  return (value);
+}
+
+static int
+three_arguments ()
+{
+  int value;
+
+  if (STREQ (argv[pos], "!"))
+    {
+      advance (1);
+      value = !two_arguments ();
+    }
+  else if (binop (argv[pos+1]))
+    {
+      value = binary_operator ();
+      pos = argc;
+    }
+  else if ((STREQ (argv[pos+1], "-a")) || (STREQ (argv[pos+1], "-o")) ||
+          (argv[pos][0] == '('))
+    value = expr ();
+  else
+    test_syntax_error ("%s: binary operator expected\n", argv[pos+1]);
+  return (value);
+}
+
+/* This is an implementation of a Posix.2 proposal by David Korn. */
+static int
+posixtest ()
+{
+  int value;
+
+  switch (argc - 1)    /* one extra passed in */
+    {
+      case 0:
+       value = FALSE;
+       pos = argc;
+       break;
+
+      case 1:
+       value = strlen (argv[1]) != 0;
+       pos = argc;
+       break;
+
+      case 2:
+       value = two_arguments ();
+       pos = argc;
+       break;
+
+      case 3:
+       value = three_arguments ();
+       break;
+
+      case 4:
+       if (STREQ (argv[pos], "!"))
+         {
+           advance (1);
+           value = !three_arguments ();
+           break;
+         }
+       /* FALLTHROUGH */
+      case 5:
+      default:
+       value = expr ();
+    }
+
+  return (value);
+}
+
+/*
+ * [:
+ *     '[' expr ']'
+ * test:
+ *     test expr
+ */
+int
+#if defined (TEST_STANDALONE)
+main (margc, margv)
+#else
+test_command (margc, margv)
+#endif /* !TEST_STANDALONE */
+     int margc;
+     char **margv;
+{
+  auto int value;
+  int expr ();
+
+#if !defined (TEST_STANDALONE)
+  int code;
+
+  code = setjmp (test_exit_buf);
+
+  if (code)
+    return (test_error_return);
+#else /* TEST_STANDALONE */
+  program_name = margv[0];
+#endif /* TEST_STANDALONE */
+
+  argv = margv;
+
+  if (margv[0] && strcmp (margv[0], "[") == 0)
+    {
+      --margc;
+
+      if (margc < 2)
+       test_exit (SHELL_BOOLEAN (FALSE));
+
+      if (margv[margc] && strcmp (margv[margc], "]") != 0)
+       test_syntax_error ("missing `]'\n", (char *)NULL);
+    }
+
+  argc = margc;
+  pos = 1;
+
+  if (pos >= argc)
+    test_exit (SHELL_BOOLEAN (FALSE));
+
+  value = posixtest ();
+
+  if (pos != argc)
+    test_syntax_error ("too many arguments\n", (char *)NULL);
+
+  test_exit (SHELL_BOOLEAN (value));
+}
diff --git a/src/tty.c b/src/tty.c
new file mode 100644 (file)
index 0000000..056c7da
--- /dev/null
+++ b/src/tty.c
@@ -0,0 +1,88 @@
+/* tty -- print the path of the terminal connected to standard input
+   Copyright (C) 1990, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Displays "not a tty" if stdin is not a terminal.
+   Displays nothing if -s option is given.
+   Exit status 0 if stdin is a tty, 1 if not, 2 if usage error.
+
+ Written by David MacKenzie (djm@ai.mit.edu).  */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+
+void usage ();
+
+/* The name under which this program was run. */
+char *program_name;
+
+/* If nonzero, return an exit status but produce no output. */
+int silent;
+
+struct option longopts[] =
+{
+  {"silent", 0, NULL, 's'},
+  {"quiet", 0, NULL, 's'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  char *tty;
+  int optc;
+
+  program_name = argv[0];
+  silent = 0;
+
+  while ((optc = getopt_long (argc, argv, "s", longopts, (int *) 0)) != EOF)
+    {
+      switch (optc)
+       {
+       case 's':
+         silent = 1;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (optind != argc)
+    usage ();
+
+  tty = ttyname (0);
+  if (!silent)
+    {
+      if (tty)
+       puts (tty);
+      else
+       puts ("not a tty");
+    }
+
+  exit (tty == NULL);
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-s] [--silent] [--quiet]\n", program_name);
+  exit (2);
+}
diff --git a/src/uname.c b/src/uname.c
new file mode 100644 (file)
index 0000000..77eaae6
--- /dev/null
@@ -0,0 +1,155 @@
+/* uname -- print system information
+   Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Option              Example
+
+   -s, --sysname       SunOS
+   -n, --nodename      rocky8
+   -r, --release       4.0
+   -v, --version       
+   -m, --machine       sun
+   -a, --all           SunOS rocky8 4.0  sun
+
+   The default behavior is equivalent to `-s'.
+
+   David MacKenzie <djm@ai.mit.edu> */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <getopt.h>
+#include "system.h"
+
+void error ();
+void print_element ();
+void usage ();
+
+/* Values that are bitwise or'd into `toprint'. */
+/* Operating system name. */
+#define PRINT_SYSNAME 1
+
+/* Node name on a communications network. */
+#define PRINT_NODENAME 2
+
+/* Operating system release. */
+#define PRINT_RELEASE 4
+
+/* Operating system version. */
+#define PRINT_VERSION 8
+
+/* Machine hardware name. */
+#define PRINT_MACHINE 16
+
+/* Mask indicating which elements of the name to print. */
+unsigned char toprint;
+
+/* The name this program was run with, for error messages. */
+char *program_name;
+
+struct option long_options[] =
+{
+  {"sysname", 0, NULL, 's'},
+  {"nodename", 0, NULL, 'n'},
+  {"release", 0, NULL, 'r'},
+  {"version", 0, NULL, 'v'},
+  {"machine", 0, NULL, 'm'},
+  {"all", 0, NULL, 'a'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  struct utsname name;
+  int c;
+
+  program_name = argv[0];
+  toprint = 0;
+
+  while ((c = getopt_long (argc, argv, "snrvma", long_options, (int *) 0))
+        != EOF)
+    {
+      switch (c)
+       {
+       case 's':
+         toprint |= PRINT_SYSNAME;
+         break;
+       case 'n':
+         toprint |= PRINT_NODENAME;
+         break;
+       case 'r':
+         toprint |= PRINT_RELEASE;
+         break;
+       case 'v':
+         toprint |= PRINT_VERSION;
+         break;
+       case 'm':
+         toprint |= PRINT_MACHINE;
+         break;
+       case 'a':
+         toprint = PRINT_SYSNAME | PRINT_NODENAME | PRINT_RELEASE |
+           PRINT_VERSION | PRINT_MACHINE;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (optind != argc)
+    usage ();
+
+  if (toprint == 0)
+    toprint = PRINT_SYSNAME;
+
+  if (uname (&name) == -1)
+    error (1, errno, "cannot get system name");
+
+  print_element (PRINT_SYSNAME, name.sysname);
+  print_element (PRINT_NODENAME, name.nodename);
+  print_element (PRINT_RELEASE, name.release);
+  print_element (PRINT_VERSION, name.version);
+  print_element (PRINT_MACHINE, name.machine);
+
+  exit (0);
+}
+
+/* If the name element set in MASK is selected for printing in `toprint',
+   print ELEMENT; then print a space unless it is the last element to
+   be printed, in which case print a newline. */
+
+void
+print_element (mask, element)
+     unsigned char mask;
+     char *element;
+{
+  if (toprint & mask)
+    {
+      toprint &= ~mask;
+      printf ("%s%c", element, toprint ? ' ' : '\n');
+    }
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-snrvma] [--sysname] [--nodename] [--release] [--version]\n\
+       [--machine] [--all]\n", program_name);
+  exit (1);
+}
diff --git a/src/who.c b/src/who.c
new file mode 100644 (file)
index 0000000..d5f40dc
--- /dev/null
+++ b/src/who.c
@@ -0,0 +1,434 @@
+/* GNU's who.
+   Copyright (C) 1992 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Written by jla; revised by djm */
+
+/* Output format:
+   name [state] line time [idle] host
+   state: -T
+   name, line, time: not -q
+   idle: -u
+
+   Options:
+   -m          Same as 'who am i', for POSIX.
+   -q          Only user names and # logged on; overrides all other options.
+   -s          Name, line, time (default).
+   -i, -u      Idle hours and minutes; '.' means active in last minute;
+               'old' means idle for >24 hours.
+   -H          Print column headings at top.
+   -w, -T      -s plus mesg (+ or -, or ? if bad line). */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <utmp.h>
+#include <time.h>
+#include <getopt.h>
+#ifndef _POSIX_SOURCE
+#include <sys/param.h>
+#endif
+#include "system.h"
+
+#ifndef UTMP_FILE
+#ifdef _PATH_UTMP              /* 4.4BSD.  */
+#define UTMP_FILE _PATH_UTMP
+#else                          /* !_PATH_UTMP */
+#define UTMP_FILE "/etc/utmp"
+#endif                         /* !_PATH_UTMP */
+#endif                         /* !UTMP_FILE */
+
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 64
+#endif
+
+#define MESG_BIT 020           /* Group write bit. */
+
+char *ttyname ();
+
+char *idle_string ();
+char *xmalloc ();
+struct utmp *search_entries ();
+void error ();
+void list_entries ();
+void print_entry ();
+void print_heading ();
+void scan_entries ();
+void usage ();
+void who ();
+void who_am_i ();
+
+/* The name this program was run with. */
+char *program_name;
+
+/* If nonzero, display only a list of usernames and count of
+   the users logged on.
+   Ignored for `who am i'. */
+int short_list;
+
+/* If nonzero, display the hours:minutes since each user has touched
+   the keyboard, or "." if within the last minute, or "old" if
+   not within the last day. */
+int include_idle;
+
+/* If nonzero, display a line at the top describing each field. */
+int include_heading;
+
+/* If nonzero, display a `+' for each user if mesg y, a `-' if mesg n,
+   or a `?' if their tty cannot be statted. */
+int include_mesg;
+
+struct option longopts[] =
+{
+  {"count", 0, NULL, 'q'},
+  {"idle", 0, NULL, 'u'},
+  {"heading", 0, NULL, 'H'},
+  {"message", 0, NULL, 'T'},
+  {"mesg", 0, NULL, 'T'},
+  {"writable", 0, NULL, 'T'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int optc, longind;
+  int my_line_only = 0;
+
+  program_name = argv[0];
+
+  while ((optc = getopt_long (argc, argv, "imqsuwHT", longopts, &longind))
+        != EOF)
+    {
+      switch (optc)
+       {
+       case 'm':
+         my_line_only = 1;
+         break;
+
+       case 'q':
+         short_list = 1;
+         break;
+
+       case 's':
+         break;
+
+       case 'i':
+       case 'u':
+         include_idle = 1;
+         break;
+
+       case 'H':
+         include_heading = 1;
+         break;
+
+       case 'w':
+       case 'T':
+         include_mesg = 1;
+         break;
+
+       default:
+         usage ();
+       }
+    }
+
+  if (chdir ("/dev"))
+    error (1, errno, "cannot change directory to /dev");
+
+  switch (argc - optind)
+    {
+    case 0:                    /* who */
+      if (my_line_only)
+       who_am_i (UTMP_FILE);
+      else
+       who (UTMP_FILE);
+      break;
+
+    case 1:                    /* who <utmp file> */
+      if (my_line_only)
+       who_am_i (argv[optind]);
+      else
+       who (argv[optind]);
+      break;
+
+    case 2:                    /* who <blurf> <glop> */
+      who_am_i (UTMP_FILE);
+      break;
+
+    default:                   /* lose */
+      usage ();
+    }
+
+  exit (0);
+}
+
+static struct utmp *utmp_contents;
+
+/* Display a list of who is on the system, according to utmp file FILENAME. */
+
+void
+who (filename)
+     char *filename;
+{
+  int users;
+
+  users = read_utmp (filename);
+  if (short_list)
+    list_entries (users);
+  else
+    scan_entries (users);
+}
+
+/* Read the utmp file FILENAME into UTMP_CONTENTS and return the
+   number of entries it contains. */
+
+int
+read_utmp (filename)
+     char *filename;
+{
+  register int desc;
+  struct stat file_stats;
+
+  desc = open (filename, O_RDONLY, 0);
+  if (desc < 0)
+    error (1, errno, "%s", filename);
+
+  fstat (desc, &file_stats);
+  if (file_stats.st_size > 0)
+    utmp_contents = (struct utmp *) xmalloc ((unsigned) file_stats.st_size);
+  else
+    {
+      close (desc);
+      return 0;
+    }
+
+  /* Use < instead of != in case the utmp just grew.  */
+  if (read (desc, utmp_contents, file_stats.st_size) < file_stats.st_size)
+    error (1, errno, "%s", filename);
+
+  if (close (desc))
+    error (1, errno, "%s", filename);
+
+  return file_stats.st_size / sizeof (struct utmp);
+}
+
+/* Display a line of information about entry THIS. */
+
+void
+print_entry (this)
+     struct utmp *this;
+{
+  struct stat stats;
+  time_t last_change;
+  char mesg;
+  char line[sizeof (this->ut_line) + 1];
+
+  strncpy (line, this->ut_line, sizeof (this->ut_line));
+  line[sizeof (this->ut_line)] = 0;
+  if (stat (line, &stats) == 0)
+    {
+      mesg = (stats.st_mode & MESG_BIT) ? '+' : '-';
+      last_change = stats.st_atime;
+    }
+  else
+    {
+      mesg = '?';
+      last_change = 0;
+    }
+  
+  printf ("%-*.*s",
+         sizeof (this->ut_name), sizeof (this->ut_name),
+         this->ut_name);
+  if (include_mesg)
+    printf ("  %c  ", mesg);
+  printf (" %-*.*s",
+         sizeof (this->ut_line), sizeof (this->ut_line),
+         this->ut_line);
+  printf (" %-12.12s", ctime (&this->ut_time) + 4);
+  if (include_idle)
+    {
+      if (last_change)
+       printf (" %s", idle_string (last_change));
+      else
+       printf ("   .  ");
+    }
+#ifdef HAVE_UT_HOST
+  if (this->ut_host[0])
+    printf (" (%-.*s)", sizeof (this->ut_host), this->ut_host);
+#endif
+
+  putchar ('\n');
+}
+
+/* Print the username of each valid entry and the number of valid entries
+   in `utmp_contents', which should have N elements. */
+
+void
+list_entries (n)
+     int n;
+{
+  register struct utmp *this = utmp_contents;
+  register int entries = 0;
+
+  while (n--)
+    {
+      if (this->ut_name[0]
+#ifdef USER_PROCESS
+         && this->ut_type == USER_PROCESS
+#endif
+        )
+       {
+         printf ("%s ", this->ut_name);
+         entries++;
+       }
+      this++;
+    }
+  printf ("\n# users=%u\n", entries);
+}
+
+void
+print_heading ()
+{
+  struct utmp *ut;
+
+  printf ("%-*s ", sizeof (ut->ut_name), "USER");
+  if (include_mesg)
+    printf ("MESG ");
+  printf ("%-*s ", sizeof (ut->ut_line), "LINE");
+  printf ("LOGIN-TIME   ");
+  if (include_idle)
+    printf ("IDLE  ");
+  printf ("FROM\n");
+}
+
+/* Display `utmp_contents', which should have N entries. */
+
+void
+scan_entries (n)
+     int n;
+{
+  register struct utmp *this = utmp_contents;
+
+  if (include_heading)
+    print_heading ();
+
+  while (n--)
+    {
+      if (this->ut_name[0]
+#ifdef USER_PROCESS
+         && this->ut_type == USER_PROCESS
+#endif
+        )
+       print_entry (this);
+      this++;
+    }
+}
+
+/* Search `utmp_contents', which should have N entries, for
+   an entry with a `ut_line' field identical to LINE.
+   Return the first matching entry found, or NULL if there
+   is no matching entry. */
+
+struct utmp *
+search_entries (n, line)
+     int n;
+     char *line;
+{
+  register struct utmp *this = utmp_contents;
+
+  while (n--)
+    {
+      if (this->ut_name[0]
+#ifdef USER_PROCESS
+         && this->ut_type == USER_PROCESS
+#endif
+         && !strncmp (line, this->ut_line, sizeof (this->ut_line)))
+       return this;
+      this++;
+    }
+  return NULL;
+}
+
+/* Display the entry in utmp file FILENAME for this tty on standard input,
+   or nothing if there is no entry for it. */
+
+void
+who_am_i (filename)
+     char *filename;
+{
+  register struct utmp *utmp_entry;
+  char hostname[MAXHOSTNAMELEN + 1];
+  char *tty;
+
+  if (gethostname (hostname, MAXHOSTNAMELEN + 1))
+    *hostname = 0;
+
+  if (include_heading)
+    {
+      printf ("%*s ", strlen (hostname), " ");
+      print_heading ();
+    }
+
+  tty = ttyname (0);
+  if (tty == NULL)
+    return;
+  tty += 5;                    /* Remove "/dev/".  */
+  
+  utmp_entry = search_entries (read_utmp (filename), tty);
+  if (utmp_entry == NULL)
+    return;
+
+  printf ("%s!", hostname);
+  print_entry (utmp_entry);
+}
+
+/* Return a string representing the time between WHEN and the time
+   that this function is first run. */
+
+char *
+idle_string (when)
+     time_t when;
+{
+  static time_t now = 0;
+  static char idle[10];
+  time_t seconds_idle;
+
+  if (now == 0)
+    time (&now);
+
+  seconds_idle = now - when;
+  if (seconds_idle < 60)       /* One minute. */
+    return "  .  ";
+  if (seconds_idle < (24 * 60 * 60)) /* One day. */
+    {
+      sprintf (idle, "%02d:%02d",
+              seconds_idle / (60 * 60),
+              (seconds_idle % (60 * 60)) / 60);
+      return idle;
+    }
+  return " old ";
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-imqsuwHT] [--count] [--idle] [--heading] [--message] [--mesg]\n\
+       [--writable] [file] [am i]\n",
+          program_name);
+  exit (1);
+}
diff --git a/src/whoami.c b/src/whoami.c
new file mode 100644 (file)
index 0000000..4a12406
--- /dev/null
@@ -0,0 +1,49 @@
+/* whoami -- print effective userid
+   Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Equivalent to `id -un'. */
+/* Written by Richard Mlynarik. */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include "system.h"
+
+void
+main (argc, argv)
+     int argc;
+     char *argv[];
+{
+  register struct passwd *pw;
+  register uid_t uid;
+
+  if (argc != 1)
+    {
+      fprintf (stderr, "Usage: %s\n", argv[0]);
+      exit (1);
+    }
+
+  uid = geteuid ();
+  pw = getpwuid (uid);
+  if (pw)
+    {
+      puts (pw->pw_name);
+      exit (0);
+    }
+  fprintf (stderr,"%s: cannot find username for UID %u\n", argv[0], uid);
+  exit (1);
+}
diff --git a/src/yes.c b/src/yes.c
new file mode 100644 (file)
index 0000000..71eabb0
--- /dev/null
+++ b/src/yes.c
@@ -0,0 +1,39 @@
+/* yes - output a string repeatedly until killed
+   Copyright (C) 1991 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* David MacKenzie <djm@ai.mit.edu> */
+
+#include <stdio.h>
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int i;
+
+  if (argc == 1)
+    while (1)
+      puts ("y");
+
+  while (1)
+    for (i = 1; i < argc; i++)
+      {
+       fputs (argv[i], stdout);
+       putchar (i == argc - 1 ? '\n' : ' ');
+      }
+}