]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
Initial revision
authorJim Meyering <jim@meyering.net>
Sat, 31 Oct 1992 20:42:48 +0000 (20:42 +0000)
committerJim Meyering <jim@meyering.net>
Sat, 31 Oct 1992 20:42:48 +0000 (20:42 +0000)
51 files changed:
INSTALL [new file with mode: 0644]
lib/Makefile.in [new file with mode: 0644]
lib/argmatch.c [new file with mode: 0644]
lib/backupfile.c [new file with mode: 0644]
lib/backupfile.h [new file with mode: 0644]
lib/dirname.c [new file with mode: 0644]
lib/fileblocks.c [new file with mode: 0644]
lib/filemode.c [new file with mode: 0644]
lib/fnmatch.c [new file with mode: 0644]
lib/fnmatch.h [new file with mode: 0644]
lib/fsusage.c [new file with mode: 0644]
lib/fsusage.h [new file with mode: 0644]
lib/ftruncate.c [new file with mode: 0644]
lib/getversion.c [new file with mode: 0644]
lib/idcache.c [new file with mode: 0644]
lib/isdir.c [new file with mode: 0644]
lib/makepath.c [new file with mode: 0644]
lib/mkdir.c [new file with mode: 0644]
lib/modechange.c [new file with mode: 0644]
lib/modechange.h [new file with mode: 0644]
lib/mountlist.c [new file with mode: 0644]
lib/mountlist.h [new file with mode: 0644]
lib/rename.c [new file with mode: 0644]
lib/savedir.c [new file with mode: 0644]
lib/stpcpy.c [new file with mode: 0644]
lib/strdup.c [new file with mode: 0644]
lib/stripslash.c [new file with mode: 0644]
lib/strstr.c [new file with mode: 0644]
lib/userspec.c [new file with mode: 0644]
lib/xstrdup.c [new file with mode: 0644]
lib/yesno.c [new file with mode: 0644]
old/fileutils/ChangeLog [new file with mode: 0644]
old/fileutils/NEWS [new file with mode: 0644]
src/chgrp.c [new file with mode: 0644]
src/chmod.c [new file with mode: 0644]
src/chown.c [new file with mode: 0644]
src/cp-hash.c [new file with mode: 0644]
src/cp.c [new file with mode: 0644]
src/dd.c [new file with mode: 0644]
src/df.c [new file with mode: 0644]
src/du.c [new file with mode: 0644]
src/install.c [new file with mode: 0644]
src/ln.c [new file with mode: 0644]
src/ls.c [new file with mode: 0644]
src/mkdir.c [new file with mode: 0644]
src/mkfifo.c [new file with mode: 0644]
src/mknod.c [new file with mode: 0644]
src/mv.c [new file with mode: 0644]
src/rm.c [new file with mode: 0644]
src/rmdir.c [new file with mode: 0644]
src/touch.c [new file with mode: 0644]

diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..c59fe67
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,111 @@
+This is a generic INSTALL file for utilities distributions.
+If this package does not come with, e.g., installable documentation or
+data files, please ignore the references to them below.
+
+To compile this package:
+
+1.  Configure the package for your system.  In the directory that this
+file is in, type `./configure'.  If you're using `csh' on an old
+version of System V, you might need to type `sh configure' instead to
+prevent `csh' from trying to execute `configure' itself.
+
+The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation, and
+creates the Makefile(s) (one in each subdirectory of the source
+directory).  In some packages it creates a C header file containing
+system-dependent definitions.  It also creates a file `config.status'
+that you can run in the future to recreate the current configuration.
+
+Running `configure' takes a minute or two.  While it is running, it
+prints some messages that tell what it is doing.  If you don't want to
+see the messages, run `configure' with its standard output redirected
+to `/dev/null'; for example, `./configure >/dev/null'.
+
+To compile the package in a different directory from the one
+containing the source code, you must use a version of make that
+supports the VPATH variable, such as GNU make.  `cd' to the directory
+where you want the object files and executables to go and run
+`configure'.  `configure' automatically checks for the source code in
+the directory that `configure' is in and in `..'.  If for some reason
+`configure' is not in the source code directory that you are
+configuring, then it will report that it can't find the source code.
+In that case, run `configure' with the option `--srcdir=DIR', where
+DIR is the directory that contains the source code.
+
+By default, `make install' will install the package's files in
+/usr/local/bin, /usr/local/lib, /usr/local/man, etc.  You can specify
+an installation prefix other than /usr/local by giving `configure' the
+option `--prefix=PATH'.  Alternately, you can do so by giving a value
+for the `prefix' variable when you run `make', e.g.,
+       make prefix=/usr/gnu
+
+You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If
+you give `configure' the option `--exec_prefix=PATH' or set the
+`make' variable `exec_prefix' to PATH, the package will use PATH as
+the prefix for installing programs and libraries.  Data files and
+documentation will still use the regular prefix.  Normally, all files
+are installed using the regular prefix.
+
+You can tell `configure' to figure out the configuration for your
+system, and record it in `config.status', without actually configuring
+the package (creating `Makefile's and perhaps a configuration header
+file).  To do this, give `configure' the `--no-create' option.  Later,
+you can run `./config.status' to actually configure the package.  This
+option is useful mainly in `Makefile' rules for updating `config.status'
+and `Makefile'.  You can also give `config.status' the `--recheck'
+option, which makes it re-run `configure' with the same arguments you
+used before.  This is useful if you change `configure'.
+
+`configure' ignores any other arguments that you give it.
+
+If your system requires unusual options for compilation or linking
+that `configure' doesn't know about, you can give `configure' initial
+values for some variables by setting them in the environment.  In
+Bourne-compatible shells, you can do that on the command line like
+this:
+       CC='gcc -traditional' DEFS=-D_POSIX_SOURCE ./configure
+
+The `make' variables that you might want to override with environment
+variables when running `configure' are:
+
+(For these variables, any value given in the environment overrides the
+value that `configure' would choose:)
+CC             C compiler program.
+               Default is `cc', or `gcc' if `gcc' is in your PATH.
+INSTALL                Program to use to install files.
+               Default is `install' if you have it, `cp' otherwise.
+
+(For these variables, any value given in the environment is added to
+the value that `configure' chooses:)
+DEFS           Configuration options, in the form `-Dfoo -Dbar ...'
+LIBS           Libraries to link with, in the form `-lfoo -lbar ...'
+
+If you need to do unusual things to compile the package, we encourage
+you to figure out how `configure' could check whether to do them, and
+mail diffs or instructions to the address given in the README so we
+can include them in the next release.
+
+2.  Type `make' to compile the package.  If you want, you can override
+the `make' variables CFLAGS and LDFLAGS like this:
+
+       make CFLAGS=-O2 LDFLAGS=-s
+
+3.  If the package comes with self-tests and you want to run them,
+type `make check'.  If you're not sure whether there are any, try it;
+if `make' responds with something like
+       make: *** No way to make target `check'.  Stop.
+then the package does not come with self-tests.
+
+4.  Type `make install' to install programs, data files, and
+documentation.
+
+5.  You can remove the program binaries and object files from the
+source directory by typing `make clean'.  To also remove the
+Makefile(s), the header file containing system-dependent definitions
+(if the package uses one), and `config.status' (all the files that
+`configure' created), type `make distclean'.
+
+The file `configure.in' is used as a template to create `configure' by
+a program called `autoconf'.  You will only need it if you want to
+regenerate `configure' using a newer version of `autoconf'.
diff --git a/lib/Makefile.in b/lib/Makefile.in
new file mode 100644 (file)
index 0000000..d441c4e
--- /dev/null
@@ -0,0 +1,98 @@
+# Makefile for library files used by GNU fileutils.
+# Do not use this makefile directly, but only from `../Makefile'.
+# 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.
+
+SHELL = /bin/sh
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+SOURCES = argmatch.c backupfile.c basename.c dirname.c eaccess.c \
+error.c filemode.c fsusage.c getopt.c getopt1.c \
+getversion.c idcache.c isdir.c makepath.c \
+modechange.c mountlist.c savedir.c \
+stripslash.c xgetcwd.c xmalloc.c xstrdup.c userspec.c yesno.c \
+getdate.y posixtm.y \
+fileblocks.c fnmatch.c ftruncate.c mkdir.c mktime.c rename.c stpcpy.c \
+strdup.c strstr.c alloca.c
+
+OBJECTS = argmatch.o backupfile.o basename.o dirname.o eaccess.o \
+error.o filemode.o getopt.o getopt1.o \
+getversion.o idcache.o isdir.o makepath.o \
+modechange.o savedir.o \
+stripslash.o xgetcwd.o xmalloc.o xstrdup.o userspec.o yesno.o \
+getdate.o posixtm.o @LIBOBJS@ @ALLOCA@
+
+DISTFILES = Makefile.in backupfile.h getopt.h modechange.h \
+fnmatch.h fsusage.h mountlist.h pathmax.h system.h $(SOURCES)
+
+all: libfu.a
+
+.c.o:
+       $(CC) -c $(CFLAGS) $(CPPFLAGS) $(DEFS) -I$(srcdir) $<
+
+install: all
+
+uninstall:
+
+TAGS: $(SOURCES)
+       etags $(SOURCES)
+
+clean:
+       rm -f *.a *.o
+
+mostlyclean: clean
+
+distclean: clean
+       rm -f Makefile *.tab.c getdate.c *posixtm.c
+
+realclean: distclean
+       rm -f TAGS
+
+dist:
+       ln $(DISTFILES) ../`cat ../.fname`/lib
+
+libfu.a: $(OBJECTS)
+       rm -f $@
+       $(AR) cr $@ $(OBJECTS)
+       -$(RANLIB) $@
+
+# Since this directory contains two parsers, using bison without -y
+# is the only way to reliably do a parallel make.
+getdate.c: getdate.y
+       @echo expect 9 shift/reduce conflicts
+       -bison -o getdate.c $(srcdir)/getdate.y || yacc $(srcdir)/getdate.y
+       test ! -f y.tab.c || mv y.tab.c getdate.c
+
+# Make the rename atomic, in case sed is interrupted and later rerun.
+posixtm.c: posixtm.y
+       -bison -o posixtm.tab.c $(srcdir)/posixtm.y || yacc $(srcdir)/posixtm.y
+       test ! -f y.tab.c || mv y.tab.c posixtm.tab.c
+       sed -e 's/yy/zz/g' posixtm.tab.c > tposixtm.c
+       mv tposixtm.c posixtm.c
+       rm -f posixtm.tab.c
+
+backupfile.o getversion.o: backupfile.h
+fnmatch.o: fnmatch.h
+fsusage.o: fsusage.h
+getopt1.o: getopt.h
+modechange.o: modechange.h
+mountlist.o: mountlist.h
+xgetcwd.o: pathmax.h
+
+# Prevent GNU make v3 from overflowing arg limit on SysV.
+.NOEXPORT:
diff --git a/lib/argmatch.c b/lib/argmatch.c
new file mode 100644 (file)
index 0000000..f3f1a50
--- /dev/null
@@ -0,0 +1,83 @@
+/* argmatch.c -- find a match for a string in an array
+   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.  */
+
+/* Written by David MacKenzie <djm@ai.mit.edu> */
+
+#include <stdio.h>
+#ifdef STDC_HEADERS
+#include <string.h>
+#endif
+
+extern char *program_name;
+
+/* If ARG is an unambiguous match for an element of the
+   null-terminated array OPTLIST, return the index in OPTLIST
+   of the matched element, else -1 if it does not match any element
+   or -2 if it is ambiguous (is a prefix of more than one element).  */
+
+int
+argmatch (arg, optlist)
+     char *arg;
+     char **optlist;
+{
+  int i;                       /* Temporary index in OPTLIST.  */
+  int arglen;                  /* Length of ARG.  */
+  int matchind = -1;           /* Index of first nonexact match.  */
+  int ambiguous = 0;           /* If nonzero, multiple nonexact match(es).  */
+  
+  arglen = strlen (arg);
+  
+  /* Test all elements for either exact match or abbreviated matches.  */
+  for (i = 0; optlist[i]; i++)
+    {
+      if (!strncmp (optlist[i], arg, arglen))
+       {
+         if (strlen (optlist[i]) == arglen)
+           /* Exact match found.  */
+           return i;
+         else if (matchind == -1)
+           /* First nonexact match found.  */
+           matchind = i;
+         else
+           /* Second nonexact match found.  */
+           ambiguous = 1;
+       }
+    }
+  if (ambiguous)
+    return -2;
+  else
+    return matchind;
+}
+
+/* Error reporting for argmatch.
+   KIND is a description of the type of entity that was being matched.
+   VALUE is the invalid value that was given.
+   PROBLEM is the return value from argmatch.  */
+
+void
+invalid_arg (kind, value, problem)
+     char *kind;
+     char *value;
+     int problem;
+{
+  fprintf (stderr, "%s: ", program_name);
+  if (problem == -1)
+    fprintf (stderr, "invalid");
+  else                         /* Assume -2.  */
+    fprintf (stderr, "ambiguous");
+  fprintf (stderr, " %s `%s'\n", kind, value);
+}
diff --git a/lib/backupfile.c b/lib/backupfile.c
new file mode 100644 (file)
index 0000000..c6d7914
--- /dev/null
@@ -0,0 +1,224 @@
+/* backupfile.c -- make Emacs style backup file names
+   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.  */
+
+/* David MacKenzie <djm@ai.mit.edu>.
+   Some algorithms adapted from GNU Emacs. */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include "backupfile.h"
+#if defined(USG) || defined(STDC_HEADERS)
+#include <string.h>
+#define index strchr
+#define rindex strrchr
+#else
+#include <strings.h>
+#endif
+
+#ifdef DIRENT
+#include <dirent.h>
+#ifdef direct
+#undef direct
+#endif
+#define direct dirent
+#define NLENGTH(direct) (strlen((direct)->d_name))
+#else /* !DIRENT */
+#define NLENGTH(direct) ((direct)->d_namlen)
+#ifdef USG
+#ifdef SYSNDIR
+#include <sys/ndir.h>
+#else /* !SYSNDIR */
+#include <ndir.h>
+#endif /* !SYSNDIR */
+#else /* !USG */
+#include <sys/dir.h>
+#endif /* !USG */
+#endif /* !DIRENT */
+
+#ifdef VOID_CLOSEDIR
+/* Fake a return value. */
+#define CLOSEDIR(d) (closedir (d), 0)
+#else
+#define CLOSEDIR(d) closedir (d)
+#endif
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+char *malloc ();
+#endif
+
+#ifndef isascii
+#define ISDIGIT(c) (isdigit ((unsigned char) (c)))
+#else
+#define ISDIGIT(c) (isascii (c) && isdigit (c))
+#endif
+
+#if defined (HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+
+#if defined (_POSIX_VERSION)
+/* POSIX does not require that the d_ino field be present, and some
+   systems do not provide it. */
+#define REAL_DIR_ENTRY(dp) 1
+#else
+#define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0)
+#endif
+
+/* Which type of backup file names are generated. */
+enum backup_type backup_type = none;
+
+/* The extension added to file names to produce a simple (as opposed
+   to numbered) backup file name. */
+char *simple_backup_suffix = "~";
+
+char *basename ();
+char *dirname ();
+static char *concat ();
+char *find_backup_file_name ();
+static char *make_version_name ();
+static int max_backup_version ();
+static int version_number ();
+
+/* Return the name of the new backup file for file FILE,
+   allocated with malloc.  Return 0 if out of memory.
+   FILE must not end with a '/' unless it is the root directory.
+   Do not call this function if backup_type == none. */
+
+char *
+find_backup_file_name (file)
+     char *file;
+{
+  char *dir;
+  char *base_versions;
+  int highest_backup;
+
+  if (backup_type == simple)
+    return concat (file, simple_backup_suffix);
+  base_versions = concat (basename (file), ".~");
+  if (base_versions == 0)
+    return 0;
+  dir = dirname (file);
+  if (dir == 0)
+    {
+      free (base_versions);
+      return 0;
+    }
+  highest_backup = max_backup_version (base_versions, dir);
+  free (base_versions);
+  free (dir);
+  if (backup_type == numbered_existing && highest_backup == 0)
+    return concat (file, simple_backup_suffix);
+  return make_version_name (file, highest_backup + 1);
+}
+
+/* Return the number of the highest-numbered backup file for file
+   FILE in directory DIR.  If there are no numbered backups
+   of FILE in DIR, or an error occurs reading DIR, return 0.
+   FILE should already have ".~" appended to it. */
+
+static int
+max_backup_version (file, dir)
+     char *file, *dir;
+{
+  DIR *dirp;
+  struct direct *dp;
+  int highest_version;
+  int this_version;
+  int file_name_length;
+  
+  dirp = opendir (dir);
+  if (!dirp)
+    return 0;
+  
+  highest_version = 0;
+  file_name_length = strlen (file);
+
+  while ((dp = readdir (dirp)) != 0)
+    {
+      if (!REAL_DIR_ENTRY (dp) || NLENGTH (dp) <= file_name_length)
+       continue;
+      
+      this_version = version_number (file, dp->d_name, file_name_length);
+      if (this_version > highest_version)
+       highest_version = this_version;
+    }
+  if (CLOSEDIR (dirp))
+    return 0;
+  return highest_version;
+}
+
+/* Return a string, allocated with malloc, containing
+   "FILE.~VERSION~".  Return 0 if out of memory. */
+
+static char *
+make_version_name (file, version)
+     char *file;
+     int version;
+{
+  char *backup_name;
+
+  backup_name = malloc (strlen (file) + 16);
+  if (backup_name == 0)
+    return 0;
+  sprintf (backup_name, "%s.~%d~", file, version);
+  return backup_name;
+}
+
+/* If BACKUP is a numbered backup of BASE, return its version number;
+   otherwise return 0.  BASE_LENGTH is the length of BASE.
+   BASE should already have ".~" appended to it. */
+
+static int
+version_number (base, backup, base_length)
+     char *base;
+     char *backup;
+     int base_length;
+{
+  int version;
+  char *p;
+  
+  version = 0;
+  if (!strncmp (base, backup, base_length) && ISDIGIT (backup[base_length]))
+    {
+      for (p = &backup[base_length]; ISDIGIT (*p); ++p)
+       version = version * 10 + *p - '0';
+      if (p[0] != '~' || p[1])
+       version = 0;
+    }
+  return version;
+}
+
+/* Return the newly-allocated concatenation of STR1 and STR2.
+   If out of memory, return 0. */
+
+static char *
+concat (str1, str2)
+     char *str1, *str2;
+{
+  char *newstr;
+  char str1_length = strlen (str1);
+
+  newstr = malloc (str1_length + strlen (str2) + 1);
+  if (newstr == 0)
+    return 0;
+  strcpy (newstr, str1);
+  strcpy (newstr + str1_length, str2);
+  return newstr;
+}
diff --git a/lib/backupfile.h b/lib/backupfile.h
new file mode 100644 (file)
index 0000000..65a189e
--- /dev/null
@@ -0,0 +1,42 @@
+/* backupfile.h -- declarations for making Emacs style backup file names
+   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.  */
+
+/* When to make backup files. */
+enum backup_type
+{
+  /* Never make backups. */
+  none,
+
+  /* Make simple backups of every file. */
+  simple,
+
+  /* Make numbered backups of files that already have numbered backups,
+     and simple backups of the others. */
+  numbered_existing,
+
+  /* Make numbered backups of every file. */
+  numbered
+};
+
+extern enum backup_type backup_type;
+extern char *simple_backup_suffix;
+
+#ifdef __STDC__
+char *find_backup_file_name (char *file);
+#else
+char *find_backup_file_name ();
+#endif
diff --git a/lib/dirname.c b/lib/dirname.c
new file mode 100644 (file)
index 0000000..82deea7
--- /dev/null
@@ -0,0 +1,64 @@
+/* dirname.c -- return all but 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.  */
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+char *malloc ();
+#endif
+#if defined(USG) || defined(STDC_HEADERS)
+#include <string.h>
+#define rindex strrchr
+#else
+#include <strings.h>
+#endif
+
+/* Return the leading directories part of PATH,
+   allocated with malloc.  If out of memory, return 0.
+   Assumes that trailing slashes have already been
+   removed.  */
+
+char *
+dirname (path)
+     char *path;
+{
+  char *newpath;
+  char *slash;
+  int length;                  /* Length of result, not including NUL.  */
+
+  slash = rindex (path, '/');
+  if (slash == 0)
+    {
+      /* File is in the current directory.  */
+      path = ".";
+      length = 1;
+    }
+  else
+    {
+      /* Remove any trailing slashes from the result.  */
+      while (slash > path && *slash == '/')
+       --slash;
+
+      length = slash - path + 1;
+    }
+  newpath = malloc (length + 1);
+  if (newpath == 0)
+    return 0;
+  strncpy (newpath, path, length);
+  newpath[length] = 0;
+  return newpath;
+}
diff --git a/lib/fileblocks.c b/lib/fileblocks.c
new file mode 100644 (file)
index 0000000..23dee98
--- /dev/null
@@ -0,0 +1,60 @@
+/* Convert file size to number of blocks on System V-like machines.
+   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.  */
+
+/* Written by Brian L. Matthews, blm@6sceng.UUCP. */
+\f
+#if !defined (HAVE_ST_BLOCKS) && !defined(_POSIX_SOURCE)
+#include <sys/types.h>
+#include <sys/param.h>
+
+#ifndef NINDIR
+/* Some SysV's, like Irix, seem to lack these.  Hope they're correct. */
+/* Size of a indirect block, in bytes. */
+#define BSIZE 1024
+
+/* Number of inode pointers per indirect block. */
+#define NINDIR (BSIZE/sizeof(daddr_t))
+#endif /* !NINDIR */
+
+/* Number of direct block addresses in an inode. */
+#define NDIR   10
+
+/* Return the number of 512-byte blocks in a file of SIZE bytes. */
+
+long
+st_blocks (size)
+     long size;
+{
+  long datablks = (size + 512 - 1) / 512;
+  long indrblks = 0;
+
+  if (datablks > NDIR)
+    {
+      indrblks = (datablks - NDIR - 1) / NINDIR + 1;
+
+      if (datablks > NDIR + NINDIR)
+       {
+         indrblks += (datablks - NDIR - NINDIR - 1) / (NINDIR * NINDIR) + 1;
+
+         if (datablks > NDIR + NINDIR + NINDIR * NINDIR)
+           indrblks++;
+       }
+    }
+
+  return datablks + indrblks;
+}
+#endif
diff --git a/lib/filemode.c b/lib/filemode.c
new file mode 100644 (file)
index 0000000..451c7ac
--- /dev/null
@@ -0,0 +1,221 @@
+/* filemode.c -- make a string describing file modes
+   Copyright (C) 1985, 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.  */
+\f
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifndef S_IREAD
+#define S_IREAD S_IRUSR
+#define S_IWRITE S_IWUSR
+#define S_IEXEC S_IXUSR
+#endif
+#ifndef S_ISREG                        /* Doesn't have POSIX.1 stat stuff.  */
+#define mode_t unsigned short
+#endif
+#if !defined(S_ISBLK) && defined(S_IFBLK)
+#define        S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
+#endif
+#if !defined(S_ISCHR) && defined(S_IFCHR)
+#define        S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
+#endif
+#if !defined(S_ISDIR) && defined(S_IFDIR)
+#define        S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+#endif
+#if !defined(S_ISREG) && defined(S_IFREG)
+#define        S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+#endif
+#if !defined(S_ISFIFO) && defined(S_IFIFO)
+#define        S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
+#endif
+#if !defined(S_ISLNK) && defined(S_IFLNK)
+#define        S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+#endif
+#if !defined(S_ISSOCK) && defined(S_IFSOCK)
+#define        S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
+#endif
+#if !defined(S_ISMPB) && defined(S_IFMPB) /* V7 */
+#define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB)
+#define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC)
+#endif
+#if !defined(S_ISNWK) && defined(S_IFNWK) /* HP/UX */
+#define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK)
+#endif
+
+void mode_string ();
+static char ftypelet ();
+static void rwx ();
+static void setst ();
+
+/* filemodestring - fill in string STR with an ls-style ASCII
+   representation of the st_mode field of file stats block STATP.
+   10 characters are stored in STR; no terminating null is added.
+   The characters stored in STR are:
+
+   0   File type.  'd' for directory, 'c' for character
+       special, 'b' for block special, 'm' for multiplex,
+       'l' for symbolic link, 's' for socket, 'p' for fifo,
+       '-' for regular, '?' for any other file type
+
+   1   'r' if the owner may read, '-' otherwise.
+
+   2   'w' if the owner may write, '-' otherwise.
+
+   3   'x' if the owner may execute, 's' if the file is
+       set-user-id, '-' otherwise.
+       'S' if the file is set-user-id, but the execute
+       bit isn't set.
+
+   4   'r' if group members may read, '-' otherwise.
+
+   5   'w' if group members may write, '-' otherwise.
+
+   6   'x' if group members may execute, 's' if the file is
+       set-group-id, '-' otherwise.
+       'S' if it is set-group-id but not executable.
+
+   7   'r' if any user may read, '-' otherwise.
+
+   8   'w' if any user may write, '-' otherwise.
+
+   9   'x' if any user may execute, 't' if the file is "sticky"
+       (will be retained in swap space after execution), '-'
+       otherwise.
+       'T' if the file is sticky but not executable.  */
+
+void
+filemodestring (statp, str)
+     struct stat *statp;
+     char *str;
+{
+  mode_string (statp->st_mode, str);
+}
+
+/* Like filemodestring, but only the relevant part of the `struct stat'
+   is given as an argument.  */
+
+void
+mode_string (mode, str)
+     unsigned short mode;
+     char *str;
+{
+  str[0] = ftypelet (mode);
+  rwx ((mode & 0700) << 0, &str[1]);
+  rwx ((mode & 0070) << 3, &str[4]);
+  rwx ((mode & 0007) << 6, &str[7]);
+  setst (mode, str);
+}
+
+/* Return a character indicating the type of file described by
+   file mode BITS:
+   'd' for directories
+   'b' for block special files
+   'c' for character special files
+   'm' for multiplexor files
+   'l' for symbolic links
+   's' for sockets
+   'p' for fifos
+   '-' for regular files
+   '?' for any other file type.  */
+
+static char
+ftypelet (bits)
+     mode_t bits;
+{
+#ifdef S_ISBLK
+  if (S_ISBLK (bits))
+    return 'b';
+#endif
+  if (S_ISCHR (bits))
+    return 'c';
+  if (S_ISDIR (bits))
+    return 'd';
+  if (S_ISREG (bits))
+    return '-';
+#ifdef S_ISFIFO
+  if (S_ISFIFO (bits))
+    return 'p';
+#endif
+#ifdef S_ISLNK
+  if (S_ISLNK (bits))
+    return 'l';
+#endif
+#ifdef S_ISSOCK
+  if (S_ISSOCK (bits))
+    return 's';
+#endif
+#ifdef S_ISMPC
+  if (S_ISMPC (bits))
+    return 'm';
+#endif
+#ifdef S_ISNWK
+  if (S_ISNWK (bits))
+    return 'n';
+#endif
+  return '?';
+}
+
+/* Look at read, write, and execute bits in BITS and set
+   flags in CHARS accordingly.  */
+
+static void
+rwx (bits, chars)
+     unsigned short bits;
+     char *chars;
+{
+  chars[0] = (bits & S_IREAD) ? 'r' : '-';
+  chars[1] = (bits & S_IWRITE) ? 'w' : '-';
+  chars[2] = (bits & S_IEXEC) ? 'x' : '-';
+}
+
+/* Set the 's' and 't' flags in file attributes string CHARS,
+   according to the file mode BITS.  */
+
+static void
+setst (bits, chars)
+     unsigned short bits;
+     char *chars;
+{
+#ifdef S_ISUID
+  if (bits & S_ISUID)
+    {
+      if (chars[3] != 'x')
+       /* Set-uid, but not executable by owner.  */
+       chars[3] = 'S';
+      else
+       chars[3] = 's';
+    }
+#endif
+#ifdef S_ISGID
+  if (bits & S_ISGID)
+    {
+      if (chars[6] != 'x')
+       /* Set-gid, but not executable by group.  */
+       chars[6] = 'S';
+      else
+       chars[6] = 's';
+    }
+#endif
+#ifdef S_ISVTX
+  if (bits & S_ISVTX)
+    {
+      if (chars[9] != 'x')
+       /* Sticky, but not executable by others.  */
+       chars[9] = 'T';
+      else
+       chars[9] = 't';
+    }
+#endif
+}
diff --git a/lib/fnmatch.c b/lib/fnmatch.c
new file mode 100644 (file)
index 0000000..525e6a8
--- /dev/null
@@ -0,0 +1,173 @@
+/* Copyright (C) 1991, 1992 Free Software Foundation, Inc.
+
+This 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.
+
+This 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 this 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 "fnmatch.h"
+
+#if !defined(__GNU_LIBRARY__) && !defined(STDC_HEADERS)
+extern int errno;
+#endif
+
+#if !__STDC__
+#define const
+#endif
+
+/* Match STRING against the filename pattern PATTERN, returning zero if
+   it matches, nonzero if not.  */
+int
+fnmatch (pattern, string, flags)
+     const char *pattern;
+     const char *string;
+     int flags;
+{
+  register const char *p = pattern, *n = string;
+  register char c;
+
+  if ((flags & ~__FNM_FLAGS) != 0)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  while ((c = *p++) != '\0')
+    {
+      switch (c)
+       {
+       case '?':
+         if (*n == '\0')
+           return FNM_NOMATCH;
+         else if ((flags & FNM_PATHNAME) && *n == '/')
+           return FNM_NOMATCH;
+         else if ((flags & FNM_PERIOD) && *n == '.' &&
+                  (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/')))
+           return FNM_NOMATCH;
+         break;
+
+       case '\\':
+         if (!(flags & FNM_NOESCAPE))
+           c = *p++;
+         if (*n != c)
+           return FNM_NOMATCH;
+         break;
+
+       case '*':
+         if ((flags & FNM_PERIOD) && *n == '.' &&
+             (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/')))
+           return FNM_NOMATCH;
+
+         for (c = *p++; c == '?' || c == '*'; c = *p++, ++n)
+           if (((flags & FNM_PATHNAME) && *n == '/') ||
+               (c == '?' && *n == '\0'))
+             return FNM_NOMATCH;
+
+         if (c == '\0')
+           return 0;
+
+         {
+           char c1 = (!(flags & FNM_NOESCAPE) && c == '\\') ? *p : c;
+           for (--p; *n != '\0'; ++n)
+             if ((c == '[' || *n == c1) &&
+                 fnmatch (p, n, flags & ~FNM_PERIOD) == 0)
+               return 0;
+           return FNM_NOMATCH;
+         }
+
+       case '[':
+         {
+           /* Nonzero if the sense of the character class is inverted.  */
+           register int not;
+
+           if (*n == '\0')
+             return FNM_NOMATCH;
+
+           if ((flags & FNM_PERIOD) && *n == '.' &&
+               (n == string || ((flags & FNM_PATHNAME) && n[-1] == '/')))
+             return FNM_NOMATCH;
+
+           not = (*p == '!' || *p == '^');
+           if (not)
+             ++p;
+
+           c = *p++;
+           for (;;)
+             {
+               register char cstart = c, cend = c;
+
+               if (!(flags & FNM_NOESCAPE) && c == '\\')
+                 cstart = cend = *p++;
+
+               if (c == '\0')
+                 /* [ (unterminated) loses.  */
+                 return FNM_NOMATCH;
+
+               c = *p++;
+
+               if ((flags & FNM_PATHNAME) && c == '/')
+                 /* [/] can never match.  */
+                 return FNM_NOMATCH;
+
+               if (c == '-' && *p != ']')
+                 {
+                   cend = *p++;
+                   if (!(flags & FNM_NOESCAPE) && cend == '\\')
+                     cend = *p++;
+                   if (cend == '\0')
+                     return FNM_NOMATCH;
+                   c = *p++;
+                 }
+
+               if (*n >= cstart && *n <= cend)
+                 goto matched;
+
+               if (c == ']')
+                 break;
+             }
+           if (!not)
+             return FNM_NOMATCH;
+           break;
+
+         matched:;
+           /* Skip the rest of the [...] that already matched.  */
+           while (c != ']')
+             {
+               if (c == '\0')
+                 /* [... (unterminated) loses.  */
+                 return FNM_NOMATCH;
+
+               c = *p++;
+               if (!(flags & FNM_NOESCAPE) && c == '\\')
+                 /* 1003.2d11 is unclear if this is right.  %%% */
+                 ++p;
+             }
+           if (not)
+             return FNM_NOMATCH;
+         }
+         break;
+
+       default:
+         if (c != *n)
+           return FNM_NOMATCH;
+       }
+
+      ++n;
+    }
+
+  if (*n == '\0' || ((flags & FNM_TARPATH) && *n == '/'))
+    return 0;
+
+  return FNM_NOMATCH;
+}
diff --git a/lib/fnmatch.h b/lib/fnmatch.h
new file mode 100644 (file)
index 0000000..0b3588a
--- /dev/null
@@ -0,0 +1,61 @@
+/* Copyright (C) 1991, 1992 Free Software Foundation, Inc.
+
+This 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.
+
+This 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 this library; see the file COPYING.LIB.  If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA.  */
+
+#ifndef        _FNMATCH_H
+
+#define        _FNMATCH_H      1
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#if defined (__cplusplus) || (defined (__STDC__) && __STDC__)
+#undef __P
+#define        __P(args)       args
+#else /* Not C++ or ANSI C.  */
+#undef __P
+#define        __P(args)       ()
+#undef const
+#define        const
+#endif /* C++ or ANSI C.  */
+
+/* Bits set in the FLAGS argument to `fnmatch'.  */
+#define        FNM_PATHNAME    (1 << 0)/* No wildcard can ever match `/'.  */
+#define        FNM_NOESCAPE    (1 << 1)/* Backslashes don't quote special chars.  */
+#define        FNM_PERIOD      (1 << 2)/* Leading `.' is matched only explicitly.  */
+#define        FNM_TARPATH     (1 << 4)/* Ignore `/...' after a match.  */
+#define        __FNM_FLAGS     (FNM_PATHNAME|FNM_NOESCAPE|FNM_PERIOD|FNM_TARPATH)
+
+#if !defined (_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 2 || defined (_BSD_SOURCE)
+#define        FNM_FILE_NAME   FNM_PATHNAME
+#endif
+
+/* Value returned by `fnmatch' if STRING does not match PATTERN.  */
+#define        FNM_NOMATCH     1
+
+/* Match STRING against the filename pattern PATTERN,
+   returning zero if it matches, FNM_NOMATCH if not.  */
+  extern int fnmatch __P ((const char *__pattern, const char *__string,
+                          int __flags));
+
+#ifdef __cplusplus
+}
+
+#endif
+
+#endif /* fnmatch.h */
diff --git a/lib/fsusage.c b/lib/fsusage.c
new file mode 100644 (file)
index 0000000..b434913
--- /dev/null
@@ -0,0 +1,198 @@
+/* fsusage.c -- return space usage of mounted filesystems
+   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.  */
+
+#include <sys/types.h>
+#include "fsusage.h"
+
+int statfs ();
+
+#if defined(STAT_STATFS2_BSIZE) && !defined(_IBMR2) /* 4.3BSD, SunOS 4, HP-UX, AIX PS/2.  */
+#include <sys/vfs.h>
+#endif
+
+#ifdef STAT_STATFS2_FSIZE      /* 4.4BSD.  */
+#include <sys/mount.h>
+#endif
+
+#ifdef STAT_STATFS2_FS_DATA    /* Ultrix.  */
+#include <sys/param.h>
+#include <sys/mount.h>
+#endif
+
+#ifdef STAT_READ               /* SVR2.  */
+#include <sys/param.h>
+#include <sys/filsys.h>
+#include <fcntl.h>
+#endif
+
+#if defined(STAT_STATFS4) || (defined(_AIX) && defined(_IBMR2)) /* SVR3, Dynix, Irix, AIX RS6000.  */
+#include <sys/statfs.h>
+#endif
+
+#if defined(_AIX) && defined(_I386) /* AIX PS/2.  */
+#include <sys/stat.h>
+#include <sys/dustat.h>
+#endif
+
+#ifdef STAT_STATVFS            /* SVR4.  */
+#include <sys/statvfs.h>
+int statvfs ();
+#endif
+
+/* Return the number of TOSIZE-byte blocks used by
+   BLOCKS FROMSIZE-byte blocks, rounding up.  */
+
+static long
+adjust_blocks (blocks, fromsize, tosize)
+     long blocks;
+     int fromsize, tosize;
+{
+  if (fromsize == tosize)      /* E.g., from 512 to 512.  */
+    return blocks;
+  else if (fromsize > tosize)  /* E.g., from 2048 to 512.  */
+    return blocks * (fromsize / tosize);
+  else                         /* E.g., from 256 to 512.  */
+    return (blocks + 1) / (tosize / fromsize);
+}
+
+/* Fill in the fields of FSP with information about space usage for
+   the filesystem on which PATH resides.
+   DISK is the device on which PATH is mounted, for space-getting
+   methods that need to know it.
+   Return 0 if successful, -1 if not. */
+
+int
+get_fs_usage (path, disk, fsp)
+     char *path, *disk;
+     struct fs_usage *fsp;
+{
+#ifdef STAT_STATFS2_FS_DATA    /* Ultrix.  */
+  struct fs_data fsd;
+
+  if (statfs (path, &fsd) != 1)
+    return -1;
+#define convert_blocks(b) adjust_blocks ((b), 1024, 512)
+  fsp->fsu_blocks = convert_blocks (fsd.fd_req.btot);
+  fsp->fsu_bfree = convert_blocks (fsd.fd_req.bfree);
+  fsp->fsu_bavail = convert_blocks (fsd.fd_req.bfreen);
+  fsp->fsu_files = fsd.fd_req.gtot;
+  fsp->fsu_ffree = fsd.fd_req.gfree;
+#endif
+
+#ifdef STAT_READ               /* SVR2.  */
+#ifndef SUPERBOFF
+#define SUPERBOFF (SUPERB * 512)
+#endif
+  struct filsys fsd;
+  int fd;
+
+  fd = open (disk, O_RDONLY);
+  if (fd < 0)
+    return -1;
+  lseek (fd, (long) SUPERBOFF, 0);
+  if (read (fd, (char *) &fsd, sizeof fsd) != sizeof fsd)
+    {
+      close (fd);
+      return -1;
+    }
+  close (fd);
+#define convert_blocks(b) adjust_blocks ((b), (fsd.s_type == Fs2b ? 1024 : 512), 512)
+  fsp->fsu_blocks = convert_blocks (fsd.s_fsize);
+  fsp->fsu_bfree = convert_blocks (fsd.s_tfree);
+  fsp->fsu_bavail = convert_blocks (fsd.s_tfree);
+  fsp->fsu_files = (fsd.s_isize - 2) * INOPB * (fsd.s_type == Fs2b ? 2 : 1);
+  fsp->fsu_ffree = fsd.s_tinode;
+#endif
+
+#ifdef STAT_STATFS2_BSIZE      /* 4.3BSD, SunOS 4, HP-UX, AIX.  */
+  struct statfs fsd;
+
+  if (statfs (path, &fsd) < 0)
+    return -1;
+#define convert_blocks(b) adjust_blocks ((b), fsd.f_bsize, 512)
+#endif
+
+#ifdef STAT_STATFS2_FSIZE      /* 4.4BSD.  */
+  struct statfs fsd;
+
+  if (statfs (path, &fsd) < 0)
+    return -1;
+#define convert_blocks(b) adjust_blocks ((b), fsd.f_fsize, 512)
+#endif
+
+#ifdef STAT_STATFS4            /* SVR3, Dynix, Irix.  */
+  struct statfs fsd;
+
+  if (statfs (path, &fsd, sizeof fsd, 0) < 0)
+    return -1;
+  /* Empirically, the block counts on most SVR3 and SVR3-derived
+     systems seem to always be in terms of 512-byte blocks,
+     no matter what value f_bsize has.  */
+#define convert_blocks(b) (b)
+#ifndef _SEQUENT_              /* _SEQUENT_ is DYNIX/ptx.  */
+#define f_bavail f_bfree
+#endif
+#endif
+
+#ifdef STAT_STATVFS            /* SVR4.  */
+  struct statvfs fsd;
+
+  if (statvfs (path, &fsd) < 0)
+    return -1;
+  /* f_frsize isn't guaranteed to be supported.  */
+#define convert_blocks(b) \
+  adjust_blocks ((b), fsd.f_frsize ? fsd.f_frsize : fsd.f_bsize, 512)
+#endif
+
+#if !defined(STAT_STATFS2_FS_DATA) && !defined(STAT_READ) /* !Ultrix && !SVR2.  */
+  fsp->fsu_blocks = convert_blocks (fsd.f_blocks);
+  fsp->fsu_bfree = convert_blocks (fsd.f_bfree);
+  fsp->fsu_bavail = convert_blocks (fsd.f_bavail);
+  fsp->fsu_files = fsd.f_files;
+  fsp->fsu_ffree = fsd.f_ffree;
+#endif
+
+  return 0;
+}
+
+#if defined(_AIX) && defined(_I386)
+/* AIX PS/2 does not supply statfs.  */
+
+int
+statfs (path, fsb)
+     char *path;
+     struct statfs *fsb;
+{
+  struct stat stats;
+  struct dustat fsd;
+
+  if (stat (path, &stats))
+    return -1;
+  if (dustat (stats.st_dev, 0, &fsd, sizeof (fsd)))
+    return -1;
+  fsb->f_type   = 0;
+  fsb->f_bsize  = fsd.du_bsize;
+  fsb->f_blocks = fsd.du_fsize - fsd.du_isize;
+  fsb->f_bfree  = fsd.du_tfree;
+  fsb->f_bavail = fsd.du_tfree;
+  fsb->f_files  = (fsd.du_isize - 2) * fsd.du_inopb;
+  fsb->f_ffree  = fsd.du_tinode;
+  fsb->f_fsid.val[0] = fsd.du_site;
+  fsb->f_fsid.val[1] = fsd.du_pckno;
+  return 0;
+}
+#endif /* _AIX && _I386 */
diff --git a/lib/fsusage.h b/lib/fsusage.h
new file mode 100644 (file)
index 0000000..c3779c1
--- /dev/null
@@ -0,0 +1,32 @@
+/* fsusage.h -- declarations for filesystem space usage info
+   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.  */
+
+/* Space usage statistics for a filesystem.  Blocks are 512-byte. */
+struct fs_usage
+{
+  long fsu_blocks;             /* Total blocks. */
+  long fsu_bfree;              /* Free blocks available to superuser. */
+  long fsu_bavail;             /* Free blocks available to non-superuser. */
+  long fsu_files;              /* Total file nodes. */
+  long fsu_ffree;              /* Free file nodes. */
+};
+
+#if __STDC__
+int get_fs_usage (char *path, char *disk, struct fs_usage *fsp);
+#else
+int get_fs_usage ();
+#endif
diff --git a/lib/ftruncate.c b/lib/ftruncate.c
new file mode 100644 (file)
index 0000000..17d263d
--- /dev/null
@@ -0,0 +1,72 @@
+/* ftruncate emulations that work on some System V's.
+   This file is in the public domain. */
+
+#include <sys/types.h>
+#include <fcntl.h>
+
+#ifdef F_CHSIZE
+int
+ftruncate (fd, length)
+     int fd;
+     off_t length;
+{
+  return fcntl (fd, F_CHSIZE, length);
+}
+#else
+#ifdef F_FREESP
+/* The following function was written by
+   kucharsk@Solbourne.com (William Kucharski) */
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+
+int
+ftruncate (fd, length)
+     int fd;
+     off_t length;
+{
+  struct flock fl;
+  struct stat filebuf;
+
+  if (fstat (fd, &filebuf) < 0)
+    return -1;
+
+  if (filebuf.st_size < length)
+    {
+      /* Extend file length. */
+      if (lseek (fd, (length - 1), SEEK_SET) < 0)
+       return -1;
+
+      /* Write a "0" byte. */
+      if (write (fd, "", 1) != 1)
+       return -1;
+    }
+  else
+    {
+      /* Truncate length. */
+      fl.l_whence = 0;
+      fl.l_len = 0;
+      fl.l_start = length;
+      fl.l_type = F_WRLCK;     /* Write lock on file space. */
+
+      /* This relies on the UNDOCUMENTED F_FREESP argument to
+        fcntl, which truncates the file so that it ends at the
+        position indicated by fl.l_start.
+        Will minor miracles never cease? */
+      if (fcntl (fd, F_FREESP, &fl) < 0)
+       return -1;
+    }
+
+  return 0;
+}
+#else
+int
+ftruncate (fd, length)
+     int fd;
+     off_t length;
+{
+  return chsize (fd, length);
+}
+#endif
+#endif
diff --git a/lib/getversion.c b/lib/getversion.c
new file mode 100644 (file)
index 0000000..eca2945
--- /dev/null
@@ -0,0 +1,57 @@
+/* getversion.c -- select backup filename type
+   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.  */
+
+/* Written by David MacKenzie <djm@gnu.ai.mit.edu> */
+
+#include "backupfile.h"
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#endif
+
+int argmatch ();
+void invalid_arg ();
+
+extern char *program_name;
+
+static char *backup_args[] =
+{
+  "never", "simple", "nil", "existing", "t", "numbered", 0
+};
+
+static enum backup_type backup_types[] =
+{
+  simple, simple, numbered_existing, numbered_existing, numbered, numbered
+};
+
+/* Return the type of backup indicated by VERSION.
+   Unique abbreviations are accepted. */
+
+enum backup_type
+get_version (version)
+     char *version;
+{
+  int i;
+
+  if (version == 0 || *version == 0)
+    return numbered_existing;
+  i = argmatch (version, backup_args);
+  if (i >= 0)
+    return backup_types[i];
+  invalid_arg ("version control type", version, i);
+  exit (1);
+}
diff --git a/lib/idcache.c b/lib/idcache.c
new file mode 100644 (file)
index 0000000..3e5f134
--- /dev/null
@@ -0,0 +1,206 @@
+/* idcache.c -- map user and group IDs, cached for speed
+   Copyright (C) 1985, 1988, 1989, 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.  */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+#if defined(USG) || defined(STDC_HEADERS)
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifndef _POSIX_VERSION
+struct passwd *getpwuid ();
+struct passwd *getpwnam ();
+struct group *getgrgid ();
+struct group *getgrnam ();
+#endif
+
+char *xmalloc ();
+char *xstrdup ();
+
+struct userid
+{
+  union
+    {
+      uid_t u;
+      gid_t g;
+    } id;
+  char *name;
+  struct userid *next;
+};
+
+static struct userid *user_alist;
+
+/* The members of this list have names not in the local passwd file.  */
+static struct userid *nouser_alist;
+
+/* Translate UID to a login name or a stringified number,
+   with cache.  */
+
+char *
+getuser (uid)
+     uid_t uid;
+{
+  register struct userid *tail;
+  struct passwd *pwent;
+  char usernum_string[20];
+
+  for (tail = user_alist; tail; tail = tail->next)
+    if (tail->id.u == uid)
+      return tail->name;
+
+  pwent = getpwuid (uid);
+  tail = (struct userid *) xmalloc (sizeof (struct userid));
+  tail->id.u = uid;
+  if (pwent == 0)
+    {
+      sprintf (usernum_string, "%u", (unsigned) uid);
+      tail->name = xstrdup (usernum_string);
+    }
+  else
+    tail->name = xstrdup (pwent->pw_name);
+
+  /* Add to the head of the list, so most recently used is first.  */
+  tail->next = user_alist;
+  user_alist = tail;
+  return tail->name;
+}
+
+/* Translate USER to a UID, with cache.
+   Return NULL if there is no such user.
+   (We also cache which user names have no passwd entry,
+   so we don't keep looking them up.)  */
+
+uid_t *
+getuidbyname (user)
+     char *user;
+{
+  register struct userid *tail;
+  struct passwd *pwent;
+
+  for (tail = user_alist; tail; tail = tail->next)
+    /* Avoid a function call for the most common case.  */
+    if (*tail->name == *user && !strcmp (tail->name, user))
+      return &tail->id.u;
+
+  for (tail = nouser_alist; tail; tail = tail->next)
+    /* Avoid a function call for the most common case.  */
+    if (*tail->name == *user && !strcmp (tail->name, user))
+      return 0;
+
+  pwent = getpwnam (user);
+
+  tail = (struct userid *) xmalloc (sizeof (struct userid));
+  tail->name = xstrdup (user);
+
+  /* Add to the head of the list, so most recently used is first.  */
+  if (pwent)
+    {
+      tail->id.u = pwent->pw_uid;
+      tail->next = user_alist;
+      user_alist = tail;
+      return &tail->id.u;
+    }
+
+  tail->next = nouser_alist;
+  nouser_alist = tail;
+  return 0;
+}
+
+/* Use the same struct as for userids.  */
+static struct userid *group_alist;
+static struct userid *nogroup_alist;
+
+/* Translate GID to a group name or a stringified number,
+   with cache.  */
+
+char *
+getgroup (gid)
+     gid_t gid;
+{
+  register struct userid *tail;
+  struct group *grent;
+  char groupnum_string[20];
+
+  for (tail = group_alist; tail; tail = tail->next)
+    if (tail->id.g == gid)
+      return tail->name;
+
+  grent = getgrgid (gid);
+  tail = (struct userid *) xmalloc (sizeof (struct userid));
+  tail->id.g = gid;
+  if (grent == 0)
+    {
+      sprintf (groupnum_string, "%u", (unsigned int) gid);
+      tail->name = xstrdup (groupnum_string);
+    }
+  else
+    tail->name = xstrdup (grent->gr_name);
+
+  /* Add to the head of the list, so most recently used is first.  */
+  tail->next = group_alist;
+  group_alist = tail;
+  return tail->name;
+}
+
+/* Translate GROUP to a UID, with cache.
+   Return NULL if there is no such group.
+   (We also cache which group names have no group entry,
+   so we don't keep looking them up.)  */
+
+gid_t *
+getgidbyname (group)
+     char *group;
+{
+  register struct userid *tail;
+  struct group *grent;
+
+  for (tail = group_alist; tail; tail = tail->next)
+    /* Avoid a function call for the most common case.  */
+    if (*tail->name == *group && !strcmp (tail->name, group))
+      return &tail->id.g;
+
+  for (tail = nogroup_alist; tail; tail = tail->next)
+    /* Avoid a function call for the most common case.  */
+    if (*tail->name == *group && !strcmp (tail->name, group))
+      return 0;
+
+  grent = getgrnam (group);
+
+  tail = (struct userid *) xmalloc (sizeof (struct userid));
+  tail->name = xstrdup (group);
+
+  /* Add to the head of the list, so most recently used is first.  */
+  if (grent)
+    {
+      tail->id.g = grent->gr_gid;
+      tail->next = group_alist;
+      group_alist = tail;
+      return &tail->id.g;
+    }
+  
+  tail->next = nogroup_alist;
+  nogroup_alist = tail;
+  return 0;
+}
diff --git a/lib/isdir.c b/lib/isdir.c
new file mode 100644 (file)
index 0000000..08388e1
--- /dev/null
@@ -0,0 +1,35 @@
+/* isdir.c -- determine whether a directory exists
+   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.  */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if !defined(S_ISDIR) && defined(S_IFDIR)
+#define        S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+#endif
+
+/* If PATH is an existing directory or symbolic link to a directory,
+   return nonzero, else 0.  */
+
+int
+isdir (path)
+     char *path;
+{
+  struct stat stats;
+
+  return stat (path, &stats) == 0 && S_ISDIR (stats.st_mode);
+}
diff --git a/lib/makepath.c b/lib/makepath.c
new file mode 100644 (file)
index 0000000..5c61124
--- /dev/null
@@ -0,0 +1,261 @@
+/* makepath.c -- Ensure that a directory path exists.
+   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.  */
+
+/* Written by David MacKenzie <djm@gnu.ai.mit.edu> and
+   Jim Meyering <meyering@cs.utexas.edu>.  */
+
+#ifdef __GNUC__
+#define alloca __builtin_alloca
+#else
+#ifdef HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#ifdef _AIX
+ #pragma alloca
+#else
+char *alloca ();
+#endif
+#endif
+#endif
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if !defined(S_ISDIR) && defined(S_IFDIR)
+#define        S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+#endif
+
+#ifdef STDC_HEADERS
+#include <errno.h>
+#include <stdlib.h>
+#else
+extern int errno;
+#endif
+
+#if defined(USG) || defined(STDC_HEADERS)
+#include <string.h>
+#define index strchr
+#else
+#include <strings.h>
+#endif
+
+#ifdef __MSDOS__
+typedef int uid_t;
+typedef int gid_t;
+#endif
+
+void error ();
+
+/* Ensure that the directory ARGPATH exists.
+   Remove any trailing slashes from ARGPATH before calling this function.
+
+   Make any leading directories that don't already exist, with
+   permissions PARENT_MODE.
+   If the last element of ARGPATH does not exist, create it as
+   a new directory with permissions MODE.
+   If OWNER and GROUP are non-negative, make them the UID and GID of
+   created directories.
+   If VERBOSE_FMT_STRING is nonzero, use it as a printf format
+   string for printing a message after successfully making a directory,
+   with the name of the directory that was just made as an argument.
+
+   Return 0 if ARGPATH exists as a directory with the proper
+   ownership and permissions when done, otherwise 1.  */
+
+int
+make_path (argpath, mode, parent_mode, owner, group, verbose_fmt_string)
+     char *argpath;
+     int mode;
+     int parent_mode;
+     uid_t owner;
+     gid_t group;
+     char *verbose_fmt_string;
+{
+  char *dirpath;               /* A copy we can scribble NULs on.  */
+  struct stat stats;
+  int retval = 0;
+  int oldmask = umask (0);
+
+  dirpath = alloca (strlen (argpath) + 1);
+  strcpy (dirpath, argpath);
+
+  if (stat (dirpath, &stats))
+    {
+      char *slash;
+      int tmp_mode;            /* Initial perms for leading dirs.  */
+      int re_protect;          /* Should leading dirs be unwritable? */
+      struct ptr_list
+      {
+       char *dirname_end;
+       struct ptr_list *next;
+      };
+      struct ptr_list *p, *leading_dirs = NULL;
+
+      /* If leading directories shouldn't be writable or executable,
+        or should have set[ug]id or sticky bits set and we are setting
+        their owners, we need to fix their permissions after making them.  */
+      if (((parent_mode & 0300) != 0300)
+         || (owner != (uid_t) -1 && group != (gid_t) -1
+             && (parent_mode & 07000) != 0))
+       {
+         tmp_mode = 0700;
+         re_protect = 1;
+       }
+      else
+       {
+         tmp_mode = parent_mode;
+         re_protect = 0;
+       }
+
+      slash = dirpath;
+      while (*slash == '/')
+       slash++;
+      while (slash = index (slash, '/'))
+       {
+         *slash = '\0';
+         if (stat (dirpath, &stats))
+           {
+             if (mkdir (dirpath, tmp_mode))
+               {
+                 error (0, errno, "cannot make directory `%s'", dirpath);
+                 umask (oldmask);
+                 return 1;
+               }
+             else
+               {
+                 if (verbose_fmt_string != NULL)
+                   error (0, 0, verbose_fmt_string, dirpath);
+
+                 if (owner != (uid_t) -1 && group != (gid_t) -1
+                     && chown (dirpath, owner, group)
+#ifdef AFS
+                     && errno != EPERM
+#endif
+                     )
+                   {
+                     error (0, errno, "%s", dirpath);
+                     retval = 1;
+                   }
+                 if (re_protect)
+                   {
+                     struct ptr_list *new = (struct ptr_list *)
+                       alloca (sizeof (struct ptr_list));
+                     new->dirname_end = slash;
+                     new->next = leading_dirs;
+                     leading_dirs = new;
+                   }
+               }
+           }
+         else if (!S_ISDIR (stats.st_mode))
+           {
+             error (0, 0, "`%s' exists but is not a directory", dirpath);
+             umask (oldmask);
+             return 1;
+           }
+
+         *slash++ = '/';
+
+         /* Avoid unnecessary calls to `stat' when given
+            pathnames containing multiple adjacent slashes.  */
+         while (*slash == '/')
+           slash++;
+       }
+
+      /* We're done making leading directories.
+        Make the final component of the path.  */
+
+      if (mkdir (dirpath, mode))
+       {
+         error (0, errno, "cannot make directory `%s'", dirpath);
+         umask (oldmask);
+         return 1;
+       }
+      if (verbose_fmt_string != NULL)
+       error (0, 0, verbose_fmt_string, dirpath);
+
+      if (owner != (uid_t) -1 && group != (gid_t) -1)
+       {
+         if (chown (dirpath, owner, group)
+#ifdef AFS
+             && errno != EPERM
+#endif
+             )
+           {
+             error (0, errno, "%s", dirpath);
+             retval = 1;
+           }
+         /* chown may have turned off some permission bits we wanted.  */
+         if ((mode & 07000) != 0 && chmod (dirpath, mode))
+           {
+             error (0, errno, "%s", dirpath);
+             retval = 1;
+           }
+       }
+
+      /* If the mode for leading directories didn't include owner "wx"
+        privileges, we have to reset their protections to the correct
+        value.  */
+      for (p = leading_dirs; p != NULL; p = p->next)
+       {
+         *(p->dirname_end) = '\0';
+         if (chmod (dirpath, parent_mode))
+           {
+             error (0, errno, "%s", dirpath);
+             retval = 1;
+           }
+       }
+    }
+  else
+    {
+      /* We get here if the entire path already exists.  */
+
+      if (!S_ISDIR (stats.st_mode))
+       {
+         error (0, 0, "`%s' exists but is not a directory", dirpath);
+         umask (oldmask);
+         return 1;
+       }
+
+      /* chown must precede chmod because on some systems,
+        chown clears the set[ug]id bits for non-superusers,
+        resulting in incorrect permissions.
+        On System V, users can give away files with chown and then not
+        be able to chmod them.  So don't give files away.  */
+
+      if (owner != (uid_t) -1 && group != (gid_t) -1
+         && chown (dirpath, owner, group)
+#ifdef AFS
+         && errno != EPERM
+#endif
+         )
+       {
+         error (0, errno, "%s", dirpath);
+         retval = 1;
+       }
+      if (chmod (dirpath, mode))
+       {
+         error (0, errno, "%s", dirpath);
+         retval = 1;
+       }
+    }
+
+  umask (oldmask);
+  return retval;
+}
diff --git a/lib/mkdir.c b/lib/mkdir.c
new file mode 100644 (file)
index 0000000..e68ccb1
--- /dev/null
@@ -0,0 +1,125 @@
+/* mkrmdir.c -- BSD compatible directory functions for System V
+   Copyright (C) 1988, 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.  */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#ifndef STDC_HEADERS
+extern int errno;
+#endif
+
+/* mkdir and rmdir adapted from GNU tar.  */
+
+/* Make directory DPATH, with permission mode DMODE.
+
+   Written by Robert Rother, Mariah Corporation, August 1985
+   (sdcsvax!rmr or rmr@uscd).  If you want it, it's yours.
+
+   Severely hacked over by John Gilmore to make a 4.2BSD compatible
+   subroutine. 11Mar86; hoptoad!gnu
+
+   Modified by rmtodd@uokmax 6-28-87 -- when making an already existing dir,
+   subroutine didn't return EEXIST.  It does now.  */
+
+int
+mkdir (dpath, dmode)
+     char *dpath;
+     int dmode;
+{
+  int cpid, status;
+  struct stat statbuf;
+
+  if (stat (dpath, &statbuf) == 0)
+    {
+      errno = EEXIST;          /* stat worked, so it already exists.  */
+      return -1;
+    }
+
+  /* If stat fails for a reason other than non-existence, return error.  */
+  if (errno != ENOENT)
+    return -1;
+
+  cpid = fork ();
+  switch (cpid)
+    {
+    case -1:                   /* Cannot fork.  */
+      return -1;               /* errno is set already.  */
+
+    case 0:                    /* Child process.  */
+      /* Cheap hack to set mode of new directory.  Since this child
+        process is going away anyway, we zap its umask.
+        This won't suffice to set SUID, SGID, etc. on this
+        directory, so the parent process calls chmod afterward.  */
+      status = umask (0);      /* Get current umask.  */
+      umask (status | (0777 & ~dmode));        /* Set for mkdir.  */
+      execl ("/bin/mkdir", "mkdir", dpath, (char *) 0);
+      _exit (1);
+
+    default:                   /* Parent process.  */
+      while (wait (&status) != cpid) /* Wait for kid to finish.  */
+       /* Do nothing.  */ ;
+
+      if (status & 0xFFFF)
+       {
+         errno = EIO;          /* /bin/mkdir failed.  */
+         return -1;
+       }
+      return chmod (dpath, dmode);
+    }
+}
+
+/* Remove directory DPATH.
+   Return 0 if successful, -1 if not.  */
+
+int
+rmdir (dpath)
+     char *dpath;
+{
+  int cpid, status;
+  struct stat statbuf;
+
+  if (stat (dpath, &statbuf) != 0)
+    return -1;                 /* stat set errno.  */
+
+  if ((statbuf.st_mode & S_IFMT) != S_IFDIR)
+    {
+      errno = ENOTDIR;
+      return -1;
+    }
+
+  cpid = fork ();
+  switch (cpid)
+    {
+    case -1:                   /* Cannot fork.  */
+      return -1;               /* errno is set already.  */
+
+    case 0:                    /* Child process.  */
+      execl ("/bin/rmdir", "rmdir", dpath, (char *) 0);
+      _exit (1);
+
+    default:                   /* Parent process.  */
+      while (wait (&status) != cpid) /* Wait for kid to finish.  */
+       /* Do nothing.  */ ;
+
+      if (status & 0xFFFF)
+       {
+         errno = EIO;          /* /bin/rmdir failed.  */
+         return -1;
+       }
+      return 0;
+    }
+}
diff --git a/lib/modechange.c b/lib/modechange.c
new file mode 100644 (file)
index 0000000..b09661d
--- /dev/null
@@ -0,0 +1,330 @@
+/* modechange.c -- file mode manipulation
+   Copyright (C) 1989, 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.  */
+
+/* Written by David MacKenzie <djm@ai.mit.edu> */
+
+/* The ASCII mode string is compiled into a linked list of `struct
+   modechange', which can then be applied to each file to be changed.
+   We do this instead of re-parsing the ASCII string for each file
+   because the compiled form requires less computation to use; when
+   changing the mode of many files, this probably results in a
+   performance gain. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "modechange.h"
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+char *malloc ();
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+#ifndef S_ISDIR
+#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+#endif
+
+/* Return newly allocated memory to hold one element of type TYPE. */
+#define talloc(type) ((type *) malloc (sizeof (type)))
+
+#define isodigit(c) ((c) >= '0' && (c) <= '7')
+
+static int oatoi ();
+
+/* Return a linked list of file mode change operations created from
+   MODE_STRING, an ASCII string that contains either an octal number
+   specifying an absolute mode, or symbolic mode change operations with
+   the form:
+   [ugoa...][[+-=][rwxXstugo...]...][,...]
+   MASKED_OPS is a bitmask indicating which symbolic mode operators (=+-)
+   should not affect bits set in the umask when no users are given.
+   Operators not selected in MASKED_OPS ignore the umask.
+
+   Return MODE_INVALID if `mode_string' does not contain a valid
+   representation of file mode change operations;
+   return MODE_MEMORY_EXHAUSTED if there is insufficient memory. */
+
+struct mode_change *
+mode_compile (mode_string, masked_ops)
+     register char *mode_string;
+     unsigned masked_ops;
+{
+  struct mode_change *head;    /* First element of the linked list. */
+  struct mode_change *change;  /* An element of the linked list. */
+  int i;                       /* General purpose temporary. */
+  int umask_value;             /* The umask value (surprise). */
+  unsigned short affected_bits;        /* Which bits in the mode are operated on. */
+  unsigned short affected_masked; /* `affected_bits' modified by umask. */
+  unsigned ops_to_mask;                /* Operators to actually use umask on. */
+
+  i = oatoi (mode_string);
+  if (i >= 0)
+    {
+      if (i > 07777)
+       return MODE_INVALID;
+      head = talloc (struct mode_change);
+      if (head == NULL)
+       return MODE_MEMORY_EXHAUSTED;
+      head->next = NULL;
+      head->op = '=';
+      head->flags = 0;
+      head->value = i;
+      head->affected = 07777;  /* Affect all permissions. */
+      return head;
+    }
+
+  umask_value = umask (0);
+  umask (umask_value);         /* Restore the old value. */
+
+  head = NULL;
+  --mode_string;
+
+  /* One loop iteration for each "ugoa...=+-rwxXstugo...[=+-rwxXstugo...]". */
+  do
+    {
+      affected_bits = 0;
+      ops_to_mask = 0;
+      /* Turn on all the bits in `affected_bits' for each group given. */
+      for (++mode_string;; ++mode_string)
+       switch (*mode_string)
+         {
+         case 'u':
+           affected_bits |= 04700;
+           break;
+         case 'g':
+           affected_bits |= 02070;
+           break;
+         case 'o':
+           affected_bits |= 01007;
+           break;
+         case 'a':
+           affected_bits |= 07777;
+           break;
+         default:
+           goto no_more_affected;
+         }
+
+    no_more_affected:
+      /* If none specified, affect all bits, except perhaps those
+        set in the umask. */
+      if (affected_bits == 0)
+       {
+         affected_bits = 07777;
+         ops_to_mask = masked_ops;
+       }
+
+      while (*mode_string == '=' || *mode_string == '+' || *mode_string == '-')
+       {
+         /* Add the element to the tail of the list, so the operations
+            are performed in the correct order. */
+         if (head == NULL)
+           {
+             head = talloc (struct mode_change);
+             if (head == NULL)
+               return MODE_MEMORY_EXHAUSTED;
+             change = head;
+           }
+         else
+           {
+             change->next = talloc (struct mode_change);
+             if (change->next == NULL)
+               {
+                 mode_free (change);
+                 return MODE_MEMORY_EXHAUSTED;
+               }
+             change = change->next;
+           }
+
+         change->next = NULL;
+         change->op = *mode_string;    /* One of "=+-". */
+         affected_masked = affected_bits;
+         if (ops_to_mask & (*mode_string == '=' ? MODE_MASK_EQUALS
+                            : *mode_string == '+' ? MODE_MASK_PLUS
+                            : MODE_MASK_MINUS))
+           affected_masked &= ~umask_value;
+         change->affected = affected_masked;
+         change->value = 0;
+         change->flags = 0;
+
+         /* Set `value' according to the bits set in `affected_masked'. */
+         for (++mode_string;; ++mode_string)
+           switch (*mode_string)
+             {
+             case 'r':
+               change->value |= 00444 & affected_masked;
+               break;
+             case 'w':
+               change->value |= 00222 & affected_masked;
+               break;
+             case 'X':
+               change->flags |= MODE_X_IF_ANY_X;
+               /* Fall through. */
+             case 'x':
+               change->value |= 00111 & affected_masked;
+               break;
+             case 's':
+               /* Set the setuid/gid bits if `u' or `g' is selected. */
+               change->value |= 06000 & affected_masked;
+               break;
+             case 't':
+               /* Set the "save text image" bit if `o' is selected. */
+               change->value |= 01000 & affected_masked;
+               break;
+             case 'u':
+               /* Set the affected bits to the value of the `u' bits
+                  on the same file.  */
+               if (change->value)
+                 goto invalid;
+               change->value = 00700;
+               change->flags |= MODE_COPY_EXISTING;
+               break;
+             case 'g':
+               /* Set the affected bits to the value of the `g' bits
+                  on the same file.  */
+               if (change->value)
+                 goto invalid;
+               change->value = 00070;
+               change->flags |= MODE_COPY_EXISTING;
+               break;
+             case 'o':
+               /* Set the affected bits to the value of the `o' bits
+                  on the same file.  */
+               if (change->value)
+                 goto invalid;
+               change->value = 00007;
+               change->flags |= MODE_COPY_EXISTING;
+               break;
+             default:
+               goto no_more_values;
+             }
+       no_more_values:;
+       }
+  } while (*mode_string == ',');
+  if (*mode_string == 0)
+    return head;
+invalid:
+  mode_free (head);
+  return MODE_INVALID;
+}
+
+/* Return file mode OLDMODE, adjusted as indicated by the list of change
+   operations CHANGES.  If OLDMODE is a directory, the type `X'
+   change affects it even if no execute bits were set in OLDMODE.
+   The returned value has the S_IFMT bits cleared. */
+
+unsigned short
+mode_adjust (oldmode, changes)
+     unsigned oldmode;
+     register struct mode_change *changes;
+{
+  unsigned short newmode;      /* The adjusted mode and one operand. */
+  unsigned short value;                /* The other operand. */
+
+  newmode = oldmode & 07777;
+
+  for (; changes; changes = changes->next)
+    {
+      if (changes->flags & MODE_COPY_EXISTING)
+       {
+         /* Isolate in `value' the bits in `newmode' to copy, given in
+            the mask `changes->value'. */
+         value = newmode & changes->value;
+
+         if (changes->value & 00700)
+           /* Copy `u' permissions onto `g' and `o'. */
+           value |= (value >> 3) | (value >> 6);
+         else if (changes->value & 00070)
+           /* Copy `g' permissions onto `u' and `o'. */
+           value |= (value << 3) | (value >> 3);
+         else
+           /* Copy `o' permissions onto `u' and `g'. */
+           value |= (value << 3) | (value << 6);
+
+         /* In order to change only `u', `g', or `o' permissions,
+            or some combination thereof, clear unselected bits.
+            This can not be done in mode_compile because the value
+            to which the `changes->affected' mask is applied depends
+            on the old mode of each file. */
+         value &= changes->affected;
+       }
+      else
+       {
+         value = changes->value;
+         /* If `X', do not affect the execute bits if the file is not a
+            directory and no execute bits are already set. */
+         if ((changes->flags & MODE_X_IF_ANY_X)
+             && !S_ISDIR (oldmode)
+             && (newmode & 00111) == 0)
+           value &= ~00111;    /* Clear the execute bits. */
+       }
+
+      switch (changes->op)
+       {
+       case '=':
+         /* Preserve the previous values in `newmode' of bits that are
+            not affected by this change operation. */
+         newmode = (newmode & ~changes->affected) | value;
+         break;
+       case '+':
+         newmode |= value;
+         break;
+       case '-':
+         newmode &= ~value;
+         break;
+       }
+    }
+  return newmode;
+}
+
+/* Free the memory used by the list of file mode change operations
+   CHANGES. */
+
+void
+mode_free (changes)
+     register struct mode_change *changes;
+{
+  register struct mode_change *next;
+
+  while (changes)
+    {
+      next = changes->next;
+      free (changes);
+      changes = next;
+    }
+}
+
+/* Return a positive integer containing the value of the ASCII
+   octal number S.  If S is not an octal number, return -1.  */
+
+static int
+oatoi (s)
+     char *s;
+{
+  register int i;
+
+  if (*s == 0)
+    return -1;
+  for (i = 0; isodigit (*s); ++s)
+    i = i * 8 + *s - '0';
+  if (*s)
+    return -1;
+  return i;
+}
diff --git a/lib/modechange.h b/lib/modechange.h
new file mode 100644 (file)
index 0000000..4a29883
--- /dev/null
@@ -0,0 +1,55 @@
+/* modechange.h -- definitions for file mode manipulation
+   Copyright (C) 1989, 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.  */
+
+/* Masks for the `flags' field in a `struct mode_change'. */
+
+/* Affect the execute bits only if at least one execute bit is set already,
+   or if the file is a directory. */
+#define MODE_X_IF_ANY_X 01
+
+/* If set, copy some existing permissions for u, g, or o onto the other two.
+   Which of u, g, or o is copied is determined by which bits are set in the
+   `value' field. */
+#define MODE_COPY_EXISTING 02
+
+struct mode_change
+{
+  char op;                     /* One of "=+-". */
+  char flags;                  /* Special operations. */
+  unsigned short affected;     /* Set for u/g/o/s/s/t, if to be affected. */
+  unsigned short value;                /* Bits to add/remove. */
+  struct mode_change *next;    /* Link to next change in list. */
+};
+
+/* Masks for mode_compile argument. */
+#define MODE_MASK_EQUALS 1
+#define MODE_MASK_PLUS 2
+#define MODE_MASK_MINUS 4
+
+/* Error return values for mode_compile. */
+#define MODE_INVALID (struct mode_change *) 0
+#define MODE_MEMORY_EXHAUSTED (struct mode_change *) 1
+
+#ifdef __STDC__
+struct mode_change *mode_compile (char *, unsigned);
+unsigned short mode_adjust (unsigned, struct mode_change *);
+void mode_free (struct mode_change *);
+#else
+struct mode_change *mode_compile ();
+unsigned short mode_adjust ();
+void mode_free ();
+#endif
diff --git a/lib/mountlist.c b/lib/mountlist.c
new file mode 100644 (file)
index 0000000..78955f7
--- /dev/null
@@ -0,0 +1,402 @@
+/* mountlist.c -- return a list of mounted filesystems
+   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.  */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "mountlist.h"
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+void free ();
+#endif
+#if defined(USG) || defined(STDC_HEADERS)
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+char *strstr ();
+char *xmalloc ();
+char *xrealloc ();
+char *xstrdup ();
+void error ();
+
+#ifdef MOUNTED_GETMNTENT1      /* 4.3BSD, SunOS, HP-UX, Dynix, Irix.  */
+#include <mntent.h>
+#if !defined(MOUNTED)
+#  if defined(MNT_MNTTAB)      /* HP-UX.  */
+#    define MOUNTED MNT_MNTTAB
+#  endif
+#  if defined(MNTTABNAME)      /* Dynix.  */
+#    define MOUNTED MNTTABNAME
+#  endif
+#endif
+#endif
+
+#ifdef MOUNTED_GETMNTINFO      /* 4.4BSD.  */
+#include <sys/mount.h>
+#endif
+
+#ifdef MOUNTED_GETMNT          /* Ultrix.  */
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/fs_types.h>
+#endif
+
+#ifdef MOUNTED_FREAD           /* SVR2.  */
+#include <mnttab.h>
+#endif
+
+#ifdef MOUNTED_FREAD_FSTYP     /* SVR3.  */
+#include <mnttab.h>
+#include <sys/fstyp.h>
+#include <sys/statfs.h>
+#endif
+
+#ifdef MOUNTED_GETMNTENT2      /* SVR4.  */
+#include <sys/mnttab.h>
+#endif
+
+#ifdef MOUNTED_VMOUNT          /* AIX.  */
+#include <fshelp.h>
+#include <sys/vfs.h>
+#endif
+
+#ifdef MOUNTED_GETMNTENT1      /* 4.3BSD, SunOS, HP-UX, Dynix, Irix.  */
+/* Return the value of the hexadecimal number represented by CP.
+   No prefix (like '0x') or suffix (like 'h') is expected to be
+   part of CP. */
+
+static int
+xatoi (cp)
+     char *cp;
+{
+  int val;
+  
+  val = 0;
+  while (*cp)
+    {
+      if (*cp >= 'a' && *cp <= 'f')
+       val = val * 16 + *cp - 'a' + 10;
+      else if (*cp >= 'A' && *cp <= 'F')
+       val = val * 16 + *cp - 'A' + 10;
+      else if (*cp >= '0' && *cp <= '9')
+       val = val * 16 + *cp - '0';
+      else
+       break;
+      cp++;
+    }
+  return val;
+}
+#endif /* MOUNTED_GETMNTENT1.  */
+
+#ifdef MOUNTED_GETMNTINFO      /* 4.4BSD.  */
+static char *
+fstype_to_string (t)
+     short t;
+{
+  switch (t)
+    {
+    case MOUNT_UFS:
+      return "ufs";
+    case MOUNT_NFS:
+      return "nfs";
+    case MOUNT_PC:
+      return "pc";
+#ifdef MOUNT_MFS
+    case MOUNT_MFS:
+      return "mfs";
+#endif
+#ifdef MOUNT_LO
+    case MOUNT_LO:
+      return "lo";
+#endif
+#ifdef MOUNT_TFS
+    case MOUNT_TFS:
+      return "tfs";
+#endif
+#ifdef MOUNT_TMP
+    case MOUNT_TMP:
+      return "tmp";
+#endif
+    default:
+      return "?";
+    }
+}
+#endif /* MOUNTED_GETMNTINFO */
+
+#ifdef MOUNTED_VMOUNT          /* AIX.  */
+static char *
+fstype_to_string (t)
+     int t;
+{
+  struct vfs_ent *e;
+
+  e = getvfsbytype (t);
+  if (!e || !e->vfsent_name)
+    return "none";
+  else
+    return e->vfsent_name;
+}
+#endif /* MOUNTED_VMOUNT */
+
+/* Return a list of the currently mounted filesystems, or NULL on error.
+   Add each entry to the tail of the list so that they stay in order.
+   If NEED_FS_TYPE is nonzero, ensure that the filesystem type fields in
+   the returned list are valid.  Otherwise, they might not be.
+   If ALL_FS is zero, do not return entries for filesystems that
+   are automounter (dummy) entries.  */
+
+struct mount_entry *
+read_filesystem_list (need_fs_type, all_fs)
+     int need_fs_type, all_fs;
+{
+  struct mount_entry *mount_list;
+  struct mount_entry *me;
+  struct mount_entry *mtail;
+
+  /* Start the list off with a dummy entry. */
+  me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
+  me->me_next = NULL;
+  mount_list = mtail = me;
+
+#ifdef MOUNTED_GETMNTENT1      /* 4.3BSD, SunOS, HP-UX, Dynix, Irix.  */
+  {
+    struct mntent *mnt;
+    char *table = MOUNTED;
+    FILE *fp;
+    char *devopt;
+
+    fp = setmntent (table, "r");
+    if (fp == NULL)
+      return NULL;
+
+    while ((mnt = getmntent (fp)))
+      {
+       if (!all_fs && (!strcmp (mnt->mnt_type, "ignore")
+                       || !strcmp (mnt->mnt_type, "auto")))
+         continue;
+
+       me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
+       me->me_devname = xstrdup (mnt->mnt_fsname);
+       me->me_mountdir = xstrdup (mnt->mnt_dir);
+       me->me_type = xstrdup (mnt->mnt_type);
+       devopt = strstr (mnt->mnt_opts, "dev=");
+       if (devopt)
+         {
+           if (devopt[4] == '0' && (devopt[5] == 'x' || devopt[5] == 'X'))
+             me->me_dev = xatoi (devopt + 6);
+           else
+             me->me_dev = xatoi (devopt + 4);
+         }
+       else
+         me->me_dev = -1;      /* Magic; means not known yet. */
+       me->me_next = NULL;
+
+       /* Add to the linked list. */
+       mtail->me_next = me;
+       mtail = me;
+      }
+
+    if (endmntent (fp) == 0)
+      return NULL;
+  }
+#endif /* MOUNTED_GETMNTENT1. */
+
+#ifdef MOUNTED_GETMNTINFO      /* 4.4BSD.  */
+  {
+    struct statfs *fsp;
+    int entries;
+
+    entries = getmntinfo (&fsp, MNT_NOWAIT);
+    if (entries < 0)
+      return NULL;
+    while (entries-- > 0)
+      {
+       me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
+       me->me_devname = xstrdup (fsp->f_mntfromname);
+       me->me_mountdir = xstrdup (fsp->f_mntonname);
+       me->me_type = fstype_to_string (fsp->f_type);
+       me->me_dev = -1;        /* Magic; means not known yet. */
+       me->me_next = NULL;
+
+       /* Add to the linked list. */
+       mtail->me_next = me;
+       mtail = me;
+       fsp++;
+      }
+  }
+#endif /* MOUNTED_GETMNTINFO */
+
+#ifdef MOUNTED_GETMNT          /* Ultrix.  */
+  {
+    int offset = 0;
+    int val;
+    struct fs_data fsd;
+
+    while ((val = getmnt (&offset, &fsd, sizeof (fsd), NOSTAT_MANY,
+                         (char *) 0)) > 0)
+      {
+       me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
+       me->me_devname = xstrdup (fsd.fd_req.devname);
+       me->me_mountdir = xstrdup (fsd.fd_req.path);
+       me->me_type = gt_names[fsd.fd_req.fstype];
+       me->me_dev = fsd.fd_req.dev;
+       me->me_next = NULL;
+
+       /* Add to the linked list. */
+       mtail->me_next = me;
+       mtail = me;
+      }
+    if (val < 0)
+      return NULL;
+  }
+#endif /* MOUNTED_GETMNT. */
+
+#if defined (MOUNTED_FREAD) || defined (MOUNTED_FREAD_FSTYP) /* SVR[23].  */
+  {
+    struct mnttab mnt;
+    char *table = "/etc/mnttab";
+    FILE *fp;
+
+    fp = fopen (table, "r");
+    if (fp == NULL)
+      return NULL;
+
+    while (fread (&mnt, sizeof mnt, 1, fp) > 0)
+      {
+       me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
+#ifdef GETFSTYP                        /* SVR3.  */
+       me->me_devname = xstrdup (mnt.mt_dev);
+#else
+       me->me_devname = xmalloc (strlen (mnt.mt_dev) + 6);
+       strcpy (me->me_devname, "/dev/");
+       strcpy (me->me_devname + 5, mnt.mt_dev);
+#endif
+       me->me_mountdir = xstrdup (mnt.mt_filsys);
+       me->me_dev = -1;        /* Magic; means not known yet. */
+       me->me_type = "";
+#ifdef GETFSTYP                        /* SVR3.  */
+       if (need_fs_type)
+         {
+           struct statfs fsd;
+           char typebuf[FSTYPSZ];
+
+           if (statfs (me->me_mountdir, &fsd, sizeof fsd, 0) != -1
+               && sysfs (GETFSTYP, fsd.f_fstyp, typebuf) != -1)
+             me->me_type = xstrdup (typebuf);
+         }
+#endif
+       me->me_next = NULL;
+
+       /* Add to the linked list. */
+       mtail->me_next = me;
+       mtail = me;
+      }
+
+    if (fclose (fp) == EOF)
+      return NULL;
+  }
+#endif /* MOUNTED_FREAD || MOUNTED_FREAD_FSTYP.  */
+
+#ifdef MOUNTED_GETMNTENT2      /* SVR4.  */
+  {
+    struct mnttab mnt;
+    char *table = MNTTAB;
+    FILE *fp;
+    int ret;
+
+    fp = fopen (table, "r");
+    if (fp == NULL)
+      return NULL;
+
+    while ((ret = getmntent (fp, &mnt)) == 0)
+      {
+       me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
+       me->me_devname = xstrdup (mnt.mnt_special);
+       me->me_mountdir = xstrdup (mnt.mnt_mountp);
+       me->me_type = xstrdup (mnt.mnt_fstype);
+       me->me_dev = -1;        /* Magic; means not known yet. */
+       me->me_next = NULL;
+
+       /* Add to the linked list. */
+       mtail->me_next = me;
+       mtail = me;
+      }
+
+    if (ret > 0)
+      return NULL;
+   if (fclose (fp) == EOF)
+      return NULL;
+  }
+#endif /* MOUNTED_GETMNTENT2.  */
+
+#ifdef MOUNTED_VMOUNT          /* AIX.  */
+  {
+    int bufsize;
+    char *entries, *thisent;
+    struct vmount *vmp;
+
+    /* Ask how many bytes to allocate for the mounted filesystem info.  */
+    mntctl (MCTL_QUERY, sizeof bufsize, (struct vmount *) &bufsize);
+    entries = xmalloc (bufsize);
+
+    /* Get the list of mounted filesystems.  */
+    mntctl (MCTL_QUERY, bufsize, (struct vmount *) entries);
+
+    for (thisent = entries; thisent < entries + bufsize;
+        thisent += vmp->vmt_length)
+      {
+       vmp = (struct vmount *) thisent;
+       me = (struct mount_entry *) xmalloc (sizeof (struct mount_entry));
+       if (vmp->vmt_flags & MNT_REMOTE)
+         {
+           char *host, *path;
+
+           /* Prepend the remote pathname.  */
+           host = thisent + vmp->vmt_data[VMT_HOSTNAME].vmt_off;
+           path = thisent + vmp->vmt_data[VMT_OBJECT].vmt_off;
+           me->me_devname = xmalloc (strlen (host) + strlen (path) + 2);
+           strcpy (me->me_devname, host);
+           strcat (me->me_devname, ":");
+           strcat (me->me_devname, path);
+         }
+       else
+         {
+           me->me_devname = xstrdup (thisent + 
+                                     vmp->vmt_data[VMT_OBJECT].vmt_off);
+         }
+       me->me_mountdir = xstrdup (thisent + vmp->vmt_data[VMT_STUB].vmt_off);
+       me->me_type = xstrdup (fstype_to_string (vmp->vmt_gfstype));
+       me->me_dev = -1;        /* vmt_fsid might be the info we want.  */
+       me->me_next = NULL;
+
+       /* Add to the linked list. */
+       mtail->me_next = me;
+       mtail = me;
+      }
+    free (entries);
+  }
+#endif /* MOUNTED_VMOUNT. */
+
+  /* Free the dummy head. */
+  me = mount_list;
+  mount_list = mount_list->me_next;
+  free (me);
+  return mount_list;
+}
diff --git a/lib/mountlist.h b/lib/mountlist.h
new file mode 100644 (file)
index 0000000..78fb1b3
--- /dev/null
@@ -0,0 +1,32 @@
+/* mountlist.h -- declarations for list of mounted filesystems
+   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.  */
+
+/* A mount table entry. */
+struct mount_entry
+{
+  char *me_devname;            /* Device node pathname, including "/dev/". */
+  char *me_mountdir;           /* Mount point directory pathname. */
+  char *me_type;               /* "nfs", "4.2", etc. */
+  dev_t me_dev;                        /* Device number of me_mountdir. */
+  struct mount_entry *me_next;
+};
+
+#if __STDC__
+struct mount_entry *read_filesystem_list (int need_fs_type, int all_fs);
+#else
+struct mount_entry *read_filesystem_list ();
+#endif
diff --git a/lib/rename.c b/lib/rename.c
new file mode 100644 (file)
index 0000000..a40bbb5
--- /dev/null
@@ -0,0 +1,79 @@
+/* rename.c -- BSD compatible directory function for System V
+   Copyright (C) 1988, 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.  */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#ifndef STDC_HEADERS
+extern int errno;
+#endif
+
+#if !defined(S_ISDIR) && defined(S_IFDIR)
+#define        S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+#endif
+
+/* Rename file FROM to file TO.
+   Return 0 if successful, -1 if not. */
+
+int
+rename (from, to)
+     char *from;
+     char *to;
+{
+  struct stat from_stats;
+  int pid, status;
+
+  if (stat (from, &from_stats))
+    return -1;
+
+  if (unlink (to) && errno != ENOENT)
+    return -1;
+
+  if (S_ISDIR (from_stats.st_mode))
+    {
+      /* Need a setuid root process to link and unlink directories. */
+      pid = fork ();
+      switch (pid)
+       {
+       case -1:                /* Error. */
+         error (1, errno, "cannot fork");
+
+       case 0:                 /* Child. */
+         execl (MVDIR, "mvdir", from, to, (char *) 0);
+         error (255, errno, "cannot run `%s'", MVDIR);
+
+       default:                /* Parent. */
+         while (wait (&status) != pid)
+           /* Do nothing. */ ;
+
+         errno = 0;            /* mvdir printed the system error message. */
+         if (status)
+           return -1;
+       }
+    }
+  else
+    {
+      if (link (from, to))
+       return -1;
+      if (unlink (from) && errno != ENOENT)
+       {
+         unlink (to);
+         return -1;
+       }
+    }
+  return 0;
+}
diff --git a/lib/savedir.c b/lib/savedir.c
new file mode 100644 (file)
index 0000000..fce61da
--- /dev/null
@@ -0,0 +1,125 @@
+/* savedir.c -- save the list of files in a directory in a string
+   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.  */
+
+/* Written by David MacKenzie <djm@ai.mit.edu>. */
+
+#include <sys/types.h>
+#ifdef DIRENT
+#include <dirent.h>
+#ifdef direct
+#undef direct
+#endif
+#define direct dirent
+#define NLENGTH(direct) (strlen((direct)->d_name))
+#else
+#define NLENGTH(direct) ((direct)->d_namlen)
+#ifdef USG
+#ifdef SYSNDIR
+#include <sys/ndir.h>
+#else
+#include <ndir.h>
+#endif
+#else
+#include <sys/dir.h>
+#endif
+#endif
+
+#ifdef VOID_CLOSEDIR
+/* Fake a return value. */
+#define CLOSEDIR(d) (closedir (d), 0)
+#else
+#define CLOSEDIR(d) closedir (d)
+#endif
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#include <string.h>
+#else
+char *malloc ();
+char *realloc ();
+#ifndef NULL
+#define NULL 0
+#endif
+#endif
+
+char *stpcpy ();
+
+/* Return a freshly allocated string containing the filenames
+   in directory DIR, separated by '\0' characters;
+   the end is marked by two '\0' characters in a row.
+   NAME_SIZE is the number of bytes to initially allocate
+   for the string; it will be enlarged as needed.
+   Return NULL if DIR cannot be opened or if out of memory. */
+
+char *
+savedir (dir, name_size)
+     char *dir;
+     unsigned name_size;
+{
+  DIR *dirp;
+  struct direct *dp;
+  char *name_space;
+  char *namep;
+
+  dirp = opendir (dir);
+  if (dirp == NULL)
+    return NULL;
+
+  name_space = (char *) malloc (name_size);
+  if (name_space == NULL)
+    {
+      closedir (dirp);
+      return NULL;
+    }
+  namep = name_space;
+
+  while ((dp = readdir (dirp)) != NULL)
+    {
+      /* Skip "." and ".." (some NFS filesystems' directories lack them). */
+      if (dp->d_name[0] != '.'
+         || (dp->d_name[1] != '\0'
+             && (dp->d_name[1] != '.' || dp->d_name[2] != '\0')))
+       {
+         unsigned size_needed = (namep - name_space) + NLENGTH (dp) + 2;
+
+         if (size_needed > name_size)
+           {
+             char *new_name_space;
+
+             while (size_needed > name_size)
+               name_size += 1024;
+
+             new_name_space = realloc (name_space, name_size);
+             if (new_name_space == NULL)
+               {
+                 closedir (dirp);
+                 return NULL;
+               }
+             namep += new_name_space - name_space;
+             name_space = new_name_space;
+           }
+         namep = stpcpy (namep, dp->d_name) + 1;
+       }
+    }
+  *namep = '\0';
+  if (CLOSEDIR (dirp))
+    {
+      free (name_space);
+      return NULL;
+    }
+  return name_space;
+}
diff --git a/lib/stpcpy.c b/lib/stpcpy.c
new file mode 100644 (file)
index 0000000..4a70746
--- /dev/null
@@ -0,0 +1,30 @@
+/* stpcpy.c -- copy a string and return pointer to end of new string
+    Copyright (C) 1989, 1990 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. */
+
+/* Copy SOURCE into DEST, stopping after copying the first '\0', and
+   return a pointer to the '\0' at the end of DEST;
+   in other words, return DEST + strlen (SOURCE). */
+
+char *
+stpcpy (dest, source)
+     char *dest;
+     char *source;
+{
+  while ((*dest++ = *source++) != '\0')
+    /* Do nothing. */ ;
+  return dest - 1;
+}
diff --git a/lib/strdup.c b/lib/strdup.c
new file mode 100644 (file)
index 0000000..078c69b
--- /dev/null
@@ -0,0 +1,43 @@
+/* strdup.c -- return a newly allocated copy of a string
+   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.  */
+
+#ifdef STDC_HEADERS
+#include <string.h>
+#include <stdlib.h>
+#else
+char *malloc ();
+char *strcpy ();
+#endif
+
+#if !__STDC__
+#define const
+#endif
+
+/* Return a newly allocated copy of STR,
+   or 0 if out of memory. */
+
+char *
+strdup (str)
+     const char *str;
+{
+  char *newstr;
+
+  newstr = (char *) malloc (strlen (str) + 1);
+  if (newstr)
+    strcpy (newstr, str);
+  return newstr;
+}
diff --git a/lib/stripslash.c b/lib/stripslash.c
new file mode 100644 (file)
index 0000000..802204f
--- /dev/null
@@ -0,0 +1,39 @@
+/* stripslash.c -- remove trailing slashes from a string
+   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(STDC_HEADERS) || defined(USG)
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+/* Remove trailing slashes from PATH.
+   This is useful when using filename completion from a shell that
+   adds a "/" after directory names (such as tcsh and bash), because
+   the Unix rename and rmdir system calls return an "Invalid argument" error
+   when given a path that ends in "/" (except for the root directory).  */
+
+void
+strip_trailing_slashes (path)
+     char *path;
+{
+  int last;
+
+  last = strlen (path) - 1;
+  while (last > 0 && path[last] == '/')
+    path[last--] = '\0';
+}
diff --git a/lib/strstr.c b/lib/strstr.c
new file mode 100644 (file)
index 0000000..6c67d9f
--- /dev/null
@@ -0,0 +1,49 @@
+/* strstr.c -- return the offset of one string within another
+   Copyright (C) 1989, 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.  */
+
+/* Author:
+       Mike Rendell                    Department of Computer Science
+       michael@garfield.mun.edu        Memorial University of Newfoundland
+       ..!uunet!garfield!michael       St. John's, Nfld., Canada
+       (709) 737-4550                  A1C 5S7
+*/
+
+/* Return the starting address of string S2 in S1;
+   return 0 if it is not found. */
+
+char *
+strstr (s1, s2)
+     char *s1;
+     char *s2;
+{
+  int i;
+  char *p1;
+  char *p2;
+  char *s = s1;
+
+  for (p2 = s2, i = 0; *s; p2 = s2, i++, s++)
+    {
+      for (p1 = s; *p1 && *p2 && *p1 == *p2; p1++, p2++)
+       ;
+      if (!*p2)
+       break;
+    }
+  if (!*p2)
+    return s1 + i;
+
+  return 0;
+}
diff --git a/lib/userspec.c b/lib/userspec.c
new file mode 100644 (file)
index 0000000..60c6ecc
--- /dev/null
@@ -0,0 +1,178 @@
+/* userspec.c -- Parse a user and group string.
+   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, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
+\f
+#include <stdio.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+#if defined(USG) || defined(STDC_HEADERS)
+#include <string.h>
+#define index strchr
+#else
+#include <strings.h>
+#endif
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+char *malloc ();
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifndef _POSIX_VERSION
+struct passwd *getpwnam ();
+struct group *getgrnam ();
+struct group *getgrgid ();
+#endif
+
+#ifdef _POSIX_SOURCE
+#define endpwent()
+#define endgrent()
+#endif
+
+#define isdigit(c) ((c) >= '0' && (c) <= '9')
+
+char *strdup ();
+static int isnumber ();
+
+/* Extract from NAME, which has the form "[user][:.][group]",
+   a USERNAME, UID U, GROUPNAME, and GID G.
+   Either user or group, or both, must be present.
+   If the group is omitted but the ":" or "." separator is given,
+   use the given user's login group.
+
+   USERNAME and GROUPNAME will be in newly malloc'd memory.
+   Either one might be NULL instead, indicating that it was not
+   given and the corresponding numeric ID was left unchanged.
+   Might write NULs into NAME.
+
+   Return NULL if successful, a static error message string if not.  */
+
+char *
+parse_user_spec (name, uid, gid, username, groupname)
+     char *name;
+     uid_t *uid;
+     gid_t *gid;
+     char **username, **groupname;
+{
+  static char *tired = "virtual memory exhausted";
+  struct passwd *pwd;
+  struct group *grp;
+  char *cp;
+  int use_login_group = 0;
+
+  *username = *groupname = NULL;
+
+  /* Check whether a group is given.  */
+  cp = index (name, ':');
+  if (cp == NULL)
+    cp = index (name, '.');
+  if (cp != NULL)
+    {
+      *cp++ = '\0';
+      if (*cp == '\0')
+       {
+         if (cp == name + 1)
+           /* Neither user nor group given, just "." or ":".  */
+           return "can not omit both user and group";
+         else
+           /* "user.".  */
+           use_login_group = 1;
+       }
+      else
+       {
+         /* Explicit group.  */
+         *groupname = strdup (cp);
+         if (*groupname == NULL)
+           return tired;
+         grp = getgrnam (cp);
+         if (grp == NULL)
+           {
+             if (!isnumber (cp))
+               return "invalid group";
+             *gid = atoi (cp);
+           }
+         else
+           *gid = grp->gr_gid;
+         endgrent ();          /* Save a file descriptor.  */
+       }
+    }
+
+  /* Parse the user name, now that any group has been removed.  */
+
+  if (name[0] == '\0')
+    /* No user name was given, just a group.  */
+    return NULL;
+
+  *username = strdup (name);
+  if (*username == NULL)
+    return tired;
+
+  pwd = getpwnam (name);
+  if (pwd == NULL)
+    {
+      if (!isnumber (name))
+       return "invalid user";
+      if (use_login_group)
+       return "cannot get the login group of a numeric UID";
+      *uid = atoi (name);
+    }
+  else
+    {
+      *uid = pwd->pw_uid;
+      if (use_login_group)
+       {
+         *gid = pwd->pw_gid;
+         grp = getgrgid (pwd->pw_gid);
+         if (grp == NULL)
+           {
+             *groupname = malloc (15);
+             if (*groupname == NULL)
+               return tired;
+             sprintf (*groupname, "%u", pwd->pw_gid);
+           }
+         else
+           {
+             *groupname = strdup (grp->gr_name);
+             if (*groupname == NULL)
+               return tired;
+           }
+         endgrent ();
+       }
+    }
+  endpwent ();
+  return NULL;
+}
+
+/* Return nonzero if STR represents an unsigned decimal integer,
+   otherwise return 0. */
+
+static int
+isnumber (str)
+     char *str;
+{
+  for (; *str; str++)
+    if (!isdigit (*str))
+      return 0;
+  return 1;
+}
diff --git a/lib/xstrdup.c b/lib/xstrdup.c
new file mode 100644 (file)
index 0000000..da39db3
--- /dev/null
@@ -0,0 +1,32 @@
+/* xstrdup.c -- copy a string with out of memory checking
+   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>
+#else
+#include <strings.h>
+#endif
+char *xmalloc ();
+
+/* Return a newly allocated copy of STRING.  */
+
+char *
+xstrdup (string)
+     char *string;
+{
+  return strcpy (xmalloc (strlen (string) + 1), string);
+}
diff --git a/lib/yesno.c b/lib/yesno.c
new file mode 100644 (file)
index 0000000..a705da7
--- /dev/null
@@ -0,0 +1,37 @@
+/* yesno.c -- read a yes/no response from stdin
+   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.  */
+
+#include <stdio.h>
+
+/* Read one line from standard input
+   and return nonzero if that line begins with y or Y,
+   otherwise return 0. */
+
+int
+yesno ()
+{
+  int c;
+  int rv;
+
+  fflush (stderr);
+  c = getchar ();
+  rv = (c == 'y') || (c == 'Y');
+  while (c != EOF && c != '\n')
+    c = getchar ();
+
+  return rv;
+}
diff --git a/old/fileutils/ChangeLog b/old/fileutils/ChangeLog
new file mode 100644 (file)
index 0000000..2469a50
--- /dev/null
@@ -0,0 +1,1901 @@
+Thu Oct 29 14:57:21 1992  David J. MacKenzie  (djm@kropotkin.gnu.ai.mit.edu)
+
+       * Version 3.4.
+
+       * cp.c (copy, re_protect), mv.c (copy_reg): Do utime and chown
+       before chmod, so set[ug]id bits don't get nuked.
+       Don't use fchmod.
+
+Wed Oct 28 16:13:18 1992  David J. MacKenzie  (djm@kropotkin.gnu.ai.mit.edu)
+
+       * cp.c, mv.c, ln.c: Rename some variables to use consistent
+       terminology: source and destination.
+
+       * ln.c, mkdir.c, mkfifo.c, mknod.c: Don't strip trailing slashes.
+       * install.c: Don't strip slashes from dest. dirs.
+
+Mon Aug 24 12:49:14 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * xgetcwd.c: Make path_max unsigned, not long.  From Bruce Evans.
+
+Sun Aug 23 03:06:04 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * idcache.c: Use a union for uid_t and gid_t.  From
+       bde@runx.oz.au (Bruce Evans).
+
+       * eaccess.c: Use NGROUPS_MAX if it's defined.  386BSD is like sun.
+
+Sat Aug 22 02:36:49 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * makepath.c: Use uid_t and gid_t.
+
+       * system.h, makepath.c: Use HAVE_ALLOCA_H, not sparc.
+
+       * cp.c (make_path, re_protect): Allocate room for terminating NULs.
+
+Fri Aug 21 21:12:18 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * fsusage.c [STAT_STATVFS]: Use f_bsize if f_frsize is 0.
+       From Paul M Reilly <pmr@rock.concert.net>.
+
+       * xgetcwd.c [!errno]: Declare errno.  From Karl Berry.
+
+       * chown.c (main, change_file_owner, change_dir_owner): Use
+       uid_t and gid_t.  From Rob McMahon <cudcv@csv.warwick.ac.uk>
+       and glaze@cs.mu.oz.au (Glaze).
+
+Thu Jul 23 14:29:17 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * Version 3.3.
+
+Sat Jul 18 20:12:56 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * idcache.c: Use uid_t and gid_t.
+       (getuidbyname, getgidbyname): New functions, for cpio.
+
+       * userspec.c: New file, from code in chown.c.
+       * chown.c: Use it.
+
+Fri Jul 17 00:43:38 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * system.h: Protect from getopt prototype in stdlib.h.
+
+       * ls.c [_AIX]: Include sys/ioctl.h.
+       * fsusage.c: Include sys/vfs.h if AIX PS/2, but not if RS6000.
+       From tranle@intellicorp.com (Minh Tran-Le).
+
+       * mvdir.c: Declare getcwd.  From Francois Pinard.
+
+       * chown.c, chgrp.c, install.c [_POSIX_SOURCE]: Define endpwent
+       and endgrent as empty.
+
+       * makepath.c (make_path): Add cast to alloca call.
+       From Jim Meyering.
+
+       * cp.c (copy, re_protect), mv.c (copy_reg): Notify root of
+       EPERM errors from chown.
+       * makepath.c, install.c [AFS]: Ignore EPERM from chown.
+
+       * system.h (ST_NBLOCKS) [_AIX && _I386]: st_blocks is in 4K units.
+       * fsusage.c (statfs) [_AIX && _I386]: Supply this function.
+       From tranle@intellicorp.com (Minh Tran-Le).
+
+Thu Jul 16 23:08:39 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * df.c (print_header, show_dev): In inode format, print the
+       total number of inodes as well.
+
+       * fsusage.[ch], df.c (show_dev): Count internally using
+       512-byte blocks, not 1024-byte, to avoid rounding errors.
+
+Mon Jul  6 20:03:54 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * rename.c: Use S_ISDIR instead of S_IFDIR.
+
+Fri Jul  3 14:36:34 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * fileblocks.c, system.h, cp.c, dd.c, mv.c, touch.c: Change
+       FOO_MISSING to HAVE_FOO.
+
+Wed Jun  3 19:28:04 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * xgetcwd.c (xgetcwd): Accept errno==EINVAL as nonfatal.
+
+Wed May 20 00:05:52 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * system.h: If we include a header file specifically to get
+       major et al., assume we have them.
+
+Mon May 11 20:04:10 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * chgrp.c, chown.c: --show-changes -> --changes.
+
+Sat May  9 18:39:38 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * system.h: Define DEV_BSIZE if not defined.
+
+Thu Apr 30 13:55:37 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * du.c (count_entry): Remove the trailing "/" before printing.
+
+Wed Apr 29 11:34:38 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * rename.c (rename): If removing `from' fails, remove `to' to
+       clean up.  From Matthew Farwell <dylan@ibmpcug.co.uk>.
+
+Thu Apr 23 21:14:16 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * ls.c (gobble_file): Only read the link contents if -l or the
+       file was named on the command line.
+
+Wed Apr 22 02:16:38 1992  David J. MacKenzie  (djm@churchy.gnu.ai.mit.edu)
+
+       * fsusage.c (get_fs_usage) [STAT_STATFS4 and _SEQUENT_]: Has f_bavail.
+       From Donn Cave <donn@carson.u.washington.edu>.
+
+       * getversion.c (get_version): If given invalid arg, exit.
+
+       * cp.c (copy): Fix mode with chmod if copying as a regular file.
+
+       * system.h, dd.c: SIGTYPE -> RETSIGTYPE.
+
+Sat Apr 18 00:18:41 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * fsusage.[ch] (get_fs_usage): Take another arg, the device name.
+       * fsusage.c (get_fs_usage) [STAT_READ]: Fix number of inodes
+       calculation.  Use the device name.  From Brian Matthews.
+       * df.c (show_dev): Pass the device name.
+
+Fri Apr 17 11:25:28 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * fsusage.c: Special-case AIX.
+
+       * mountlist.c [MOUNTED_VMOUNT]: New code for AIX, from
+       Garrett A. Wollman (wollman@uvm.edu).
+
+       * ls.c (gobble_file): Use stat, not lstat, on symlinked-to
+       file, for Unix compat.  From ian@airs.com (Ian Lance Taylor).
+
+Mon Apr  6 14:16:06 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * xgetcwd.c: Include stdio.h to get NULL.
+
+Thu Apr  2 14:41:18 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * df.c: Call sync only once, instead of once per filesystem.
+
+Wed Apr  1 16:00:08 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * Version 3.2.
+
+Tue Mar 31 13:39:06 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * df.c (main): stat all arg pathnames before getting
+       list of mounted filesystems.
+       (show_entry, show_point): Take a struct stat * as another arg,
+       to avoid repeatedly statting files.
+
+Mon Mar 30 12:21:28 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * install.c (main): Allow symbolic modes for -m.
+       (atoo): Function removed.
+
+       * mkdir.c (main): Include invalid -m arg in error message.
+
+       * fsusage.c (get_fs_usage) [STAT_STATFS4]: Go back to using
+       512 instead of f_bsize.  Empirically, it gives the right answer.
+
+       * mvdir.c (main): Don't deref NULL pointer on last iteration
+       of loop.
+
+       * fsusage.c (adjust_blocks): New function.
+       (get_fs_usge): Call it.
+
+       * mvdir.c (main): Don't possibly try to use ".." entry of new dir
+       before creating it.
+
+       * fsusage.c (get_fs_usage) [STAT_STATFS4]: Use f_bsize member
+       of struct statfs.
+
+Sat Mar 28 00:36:57 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * dd.c (copy_simple, copy_with_block, copy_with_unblock,
+       translate_buffer, swab_buffer): New functions, mostly made
+       from code taken from copy.  Incorporate some optimizations due
+       to Stuart Kemp: For each type of conversion, only check
+       whether to do it once per buffer read, instead of once per character.
+       (copy): If conv=block and the input didn't end with a newline,
+       pad the final block with spaces.
+Wed Mar 25 14:35:17 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * system.h: Don't use BSIZE for calculating ST_BLKSIZE if it
+       isn't defined.
+
+       * mountlist.c [MOUNTED_FREAD], fsusage.c [STAT_READ]: New code
+       for SVR2, from archive@ideahb.sublink.org (Lele Gaifax).
+
+Tue Mar 24 14:53:19 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * mvdir.c: Use getcwd, not getwd.
+
+       * system.h, xgetcwd.c: Redo how PATH_MAX is figured out, to
+       work on SVR3.
+
+       * fsusage.c, mountlist.c: New files split from fsinfo.c.
+       Revise conditionals to make the two files independant of each other.
+       * fsusage.h, mountlist.h: New files split from fsinfo.h.
+       * df.c: Use them.
+
+Mon Mar 23 13:01:07 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * fsinfo.c (read_filesystem_list): Take another arg, all_fs.
+       [FS_MNTENT]: Ignore type "auto" (from amd) as well as "ignore"
+       (from automounter), if not all_fs.
+       * df.c (main): Pass the new arg.
+
+       * fsinfo.h: Add function decls.
+
+       * chown.c, chgrp.c, chmod.c: Remove -L option.  Didn't handle
+       changing symlinks correctly and wasn't very useful.
+
+Sat Mar 14 17:38:38 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * dirname.c (dirname): Don't use strdup.
+
+Fri Mar 13 14:56:15 1992  David J. MacKenzie  (djm@wookumz.gnu.ai.mit.edu)
+
+       * rm.c (remove_file, remove_dir, clear_directory): If -f was
+       given, don't complain about ENOENT when removing.
+
+Mon Mar  9 00:09:48 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * install.c (main): Allow making multiple dirs with -d.
+       (usage): Document it.
+       * makepath.c (make_path): Clear umask while working and
+       restore it when done.  chmod dirs that should have
+       set[ug]id or sticky bits set, if we're chowning them.
+       Make chown failure nonfatal.  Do chmod after chown, not before.
+
+       * du.c (count_entry): Set exit_status on nonfatal error.
+       (main): Use it.
+
+Sun Mar  8 22:07:50 1992  David J. MacKenzie  (djm@nutrimat.gnu.ai.mit.edu)
+
+       * du.c (du_files): New function, from code in main.
+       Use xgetcwd instead of getcwd or getwd.
+       * system.h: Don't declare getcwd or getwd.
+
+       * xgetcwd.c: New file.
+
+       * ls.c (main): Exit with `exit_status' instead of 0.
+       (print_dir, gobble_file, get_link_name): Set exit_status on error.
+
+       * ls.c (print_long_format): Allow a slop factor for deciding
+       what is in the future.
+
+       * All programs: Change usage messages and documentation for
+       long options to use -- instead of +.
+
+       * df.c (main, usage): Add -v option for SysV compat.
+
+Tue Feb  4 12:45:09 1992  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * fileblocks.c [!NINDIR]: Try to fake indirect block info for
+       systems that don't define it.
+
+Thu Jan 16 01:04:16 1992  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * df.c (print_header): Capitalize some header words for
+       POSIX.2a draft 8.
+
+Sat Jan  4 01:19:25 1992  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * posixtm.y: Capitalize token name.
+
+Tue Dec 24 01:05:44 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * system.h, makepath.c, idcache.c, eaccess.c, backupfile.c,
+       install.c, dd.c, chown.c, chgrp.c: Change POSIX ifdefs to
+       HAVE_UNISTD_H.
+
+Wed Dec 18 16:42:00 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * system.h: To get major, minor and makedev, don't check for
+       _POSIX_SOURCE and USG; use MAJOR_IN_MKDEV and MAJOR_IN_SYSMACROS.
+
+Mon Dec 16 18:16:42 1991  David J. MacKenzie  (djm at wombat.gnu.ai.mit.edu)
+
+       * dd.c (skip): Fix typos in arg name.
+       (output_char): Fix off by one error in check.
+
+Sun Dec  8 19:55:06 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * system.h: Only define major et al. if not already defined.
+
+Fri Dec  6 18:26:53 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * dd.c (main) [POSIX]: Use sigaction instead of signal, which
+       POSIX doesn't have.
+
+       * df.c, du.c, ls.c: POSIX_ME_HARDER -> POSIXLY_CORRECT.
+
+Wed Dec  4 14:30:16 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * dd.c: Cleanups, mostly from Stuart Kemp:
+       (output_char): New macro, from code in copy.
+       (write_output): New function, used by output_char, from code
+       in copy.
+       (skip): New function, from code in copy.
+       (copy): Use output_char and skip.
+       Simplify test for quitting before main loop.
+       Zero buffer using bzero for speed.
+
+Sun Nov 17 19:39:04 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * fsinfo.c (get_fs_usage) [FS_MNTENT]: Take blocksize into account.
+       (read_filesystem_list) [FS_MNTENT]: Ignore filesystems of type
+       "ignore" (automounter dummy entries).
+
+       * install.c (change_attributes): Do chmod even if chown fails.
+
+Thu Oct 24 23:50:46 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * chown.c (change_file_owner), chgrp.c (change_file_group):
+       don't skip symlinks, since the chown system call works on them.
+
+Fri 18 Oct 1991 23:29:24  Jim Meyering (meyering at wombat)
+
+       * configure: fixed test to detect sequent's strange interpretation
+       of utime(file, NULL).
+
+Fri Oct 18 00:30:42 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * eaccess.c: GID_T -> GETGROUPS_T, for clarity.
+
+Sat Oct 12 12:25:55 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.
+
+       * df.c: Don't declare sync, to avoid conflict with Minix (and
+       maybe others) unistd.h.
+       (show_point): Cast -1 to dev_t before comparing, in case dev_t
+       is unsigned.  From Rainer Orth.
+
+       * chown.c [!POSIX]: Declare getgrgid.
+
+Fri Sep 13 14:55:41 1991  David J. MacKenzie  (djm at churchy.gnu.ai.mit.edu)
+
+       * eaccess.c [POSIX]: Always use sysconf to get NGROUPS_MAX.
+
+Thu Sep  5 23:40:39 1991  David J. MacKenzie  (djm at apple-gunkies)
+
+       * system.h: Instead of defining getwd in terms of getcwd with
+       PATH_MAX as an arg (which might be -1 on POSIX), define getcwd
+       in terms of getwd.
+       * du.c (main): Call getcwd with path_max as an arg.
+
+       * install.c (change_attributes): Do chown before chmod, so
+       chown doesn't remove set[ug]id bits.
+
+Wed Aug 28 20:53:50 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * Version 3.1.
+
+Mon Aug 26 15:44:16 1991  David J. MacKenzie  (djm at pogo.gnu.ai.mit.edu)
+
+       * du.c (main): If pathconf fails, use 1024 for PATH_MAX.  This
+       happens if "/" is NFS-mounted.
+
+Sun Aug 25 00:56:11 1991  David J. MacKenzie  (djm at apple-gunkies)
+
+       * df.c, fsinfo.c, fsinfo.h: New program.
+       * configure: Check for various ways of getting info on mounted
+       filesystems. 
+
+Thu Aug 22 10:53:23 1991  David J. MacKenzie  (djm at apple-gunkies)
+
+       * src/Makefile.in: Workaround #10006 for C compilers that are
+       too dumb to allow -c and -o together.  Copy the source files.
+       * system.h: Moved from src to lib to avoid having to add yet
+       more -I options to CFLAGS.
+
+       * du.c, ls.c: If POSIX_ME_HARDER is set in environment, use
+       512-byte blocks by default.
+
+Wed Aug 21 13:03:14 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * Version 3.0.
+
+       * du.c, ls.c: Make 1K blocks the default size, and -k a no-op.
+       Down with dumb standards!
+
+       * system.h, backupfile.c, savedir.c [VOID_CLOSEDIR]: Fake a
+       return value for closedir, which returns void on some systems,
+       like Sequents.
+       * configure: Check sys/dir.h for 'void closedir'.
+
+Tue Aug 20 22:22:47 1991  Jim Meyering (meyering at nutrimat)
+
+       * mvdir.c (main):  Clean up loop to stat component
+       directories -- as in makepath and pathchk.
+
+Tue Aug 20 22:10:47 1991  Jim Meyering (meyering at nutrimat)
+
+       * dirname.c (dirname):  Allocate exact amount of space
+       needed for result.
+
+Tue Aug 20 02:13:40 1991  David J. MacKenzie  (djm at apple-gunkies)
+
+       * savedir.c (savedir): Try to open directory before allocating
+       buffer.  From Jim Meyering.
+
+Mon Aug 19 01:14:13 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * Many files: indent '#pragma alloca' so non-ANSI compilers
+       don't choke on it.
+
+       * backupfile.c (max_backup_version): Check closedir return
+       value (though it might not do any good).
+
+Tue Aug  6 20:50:56 1991  David J. MacKenzie  (djm at wheat-chex)
+
+       * configure, Makefile.in's: Support +srcdir option, using VPATH.
+       Don't check for bison, just try it and if it fails use yacc.
+       Create config.status.  Fix up clean targets.
+
+       * posixtm.y (posixtm): New function.
+
+       Most of the following is from Paul Eggert:
+       * savedir.c (savedir), ls.c (print_dir), rm.c
+       (clear_directory): Check closedir return for errors.
+       * dd.c (main): Check for stdin or stdout being closed.
+       * dd.c (quit), install.c (copy_file): Check for close errors.
+       * mv.c (copy): Was missing a close.
+
+Sat Aug  3 02:05:51 1991  David J. MacKenzie  (djm at apple-gunkies)
+
+       * ln.c: Declare link() unconditionally (SCO UNIX needs it).
+
+Tue Jul 30 00:23:19 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * configure: NEED_TZSET has become FTIME_MISSING.
+
+       * configure: Define uid_t and gid_t if sys/types.h doesn't.
+
+Sat Jul 27 00:55:16 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * configure: Only compile fileblocks.c if st_blocks is missing.
+
+       * cp.c (copy): Make directories with initial mode of source
+       permissions minus umask, plus 0700.  For POSIX and John Gilmore.
+
+       * system.h: Include errno.h and, if STDC_HEADERS, stdlib.h.
+       * All programs: Remove includes of those files.
+
+       * ftruncate.c: New file.
+       * configure: Use it if needed.
+
+Wed Jul 24 02:09:45 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * ls.c (get_name_link), cp.c (copy) [_AIX]: Allocate extra
+       space for the buffer, since st_size is wrong.
+
+       * system.h: Don't declare alloca for AIX.
+       * makepath.c, posixtm.y, cp.c, du.c, ln.c, ls.c, mv.c:
+       Declare alloca first (AIX needs it).
+
+       * cp.c, dd.c, touch.c: Use SEEK_ instead of L_.
+       * system.h: Define SEEK_ if not defined.
+
+Tue Jul 23 15:02:20 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * eaccess.c: GID_T is int if ultrix as well as if sun.
+
+Mon Jul 22 11:39:31 1991  David J. MacKenzie  (djm at bleen)
+
+       * install.c: Use uid_t and gid_t.
+
+       * eaccess.c: Support POSIX method of getting multiple groups.
+
+       * xmalloc.c (xmalloc, xrealloc): Exit with value 2 on error,
+       not 1, so cmp can use it.
+
+Sat Jul 20 14:24:40 1991  David J. MacKenzie  (djm at bleen)
+
+       * Move cat cmp cut expand head paste split tac tail unexpand
+       to textutils.
+
+       * system.h [MKFIFO_MISSING]: Define mkfifo macro.
+       * cp.c, mkfifo.c: Don't define it.
+
+       * mknod.c, gmknod.1: New files.
+
+Fri Jul 19 13:43:01 1991  David J. MacKenzie  (djm at apple-gunkies)
+
+       * version.c: New file.
+       * All C programs: Link with it, to get version number in the
+       binary where at least `strings -' and GNU grep can find it.
+
+Mon Jul 15 13:46:53 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * configure: Also look in sys/signal.h for signal decl.
+
+Sun Jul 14 22:43:57 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * Rename touchtm.y back to posixtm.y, as the date command will
+       use it too.
+
+Mon Jul  8 22:56:36 1991  David J. MacKenzie  (djm at wookumz.gnu.ai.mit.edu)
+
+       * Replace lib/Makefile with lib/Makefile.in.
+       * configure: For some library functions that might be missing,
+       conditionally add the .o files to lib/Makefile instead of
+       defining func_MISSING.
+       * lib/mkdir.c: Renamed from lib/mkrmdir.c.
+
+Sat Jul  6 02:19:09 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * xstrdup.c [STDC_HEADERS]: Include string.h.
+
+       * stripslash.h: Include string header file.
+
+       * configure: Add to DEFS if Minix.
+
+       * system.h [_POSIX_SOURCE]: Make ST_BLKSIZE 1024 instead of
+       512, for better performance.
+
+       * system.h, configure: If sys/mkdev.h exists, use it instead
+       of sys/sysmacros.h.
+
+       * configure: echo messages to stdout, not stderr.
+       Use test programs to see if alloca needs -lPW and if chars are
+       unsigned. 
+
+Tue Jul  2 03:16:32 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * chown.c, chgrp.c [!POSIX]: Declare pwd.h and grp.h functions.
+
+Sat Jun 29 16:46:12 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * cp.h: Don't declare `open', to avoid conflict with varargs
+       prototypes.
+
+       * chown.c, chgrp.c: Include sys/types.h before, not after,
+       pwd.h and grp.h, to get uid_t and gid_t if necessary.
+
+Fri Jun 28 01:12:45 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * ls.c: Use time_t instead of long, where appropriate.
+
+Thu Jun 27 16:31:45 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * touchtm.y: Renamed from posixtime.y for SysV systems with Bison.
+
+       * configure: No longer need to pass bison the -y option.
+       Now lib/Makefile should allow a parallel make with bison.
+
+       * cp.c (copy_reg), cat.c (main),
+       touch.c (touch, utime_now), mv.c (copy): Check close return
+       value for delayed error report due to NFS.
+
+Thu Jun 20 01:33:06 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * configure: Include $DEFS when compiling test programs.
+
+       * configure: Use test programs instead of grep to check for
+       USG, POSIX, and ANSI C headers, in case symbols are defined in
+       header files included by the standard ones.  Check for BSD
+       memory functions (bcopy et al.) as well as string functions.
+       Add notice to top of generated Makefile saying that it's
+       automatically generated.
+
+Thu Jun 13 00:50:18 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * Version 2.1.
+
+       * configure: If rename is missing, define MVDIR.
+       Use , instead of / as sed substitution separator so variables'
+       values can contain slashes.
+
+       * du.c (main): Use alloca to allocate `wd' instead of making
+       it an auto array, since PATH_MAX might be a call to pathconf.
+
+Wed Jun 12 19:56:22 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * cp-aux.c (usage), install.c (usage), ln.c (usage), mv.c
+       (usage): Combine the option lists for the multiple usage forms.
+
+Tue Jun 11 00:12:15 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * idcache.c: pwd.h and grp.h might need sys/types.h.
+
+       * configure: Create Makefile from Makefile.in instead of
+       makefile from makefile.skel, to more closely follow the new
+       GNU coding standards.
+
+       * ls.c (file_interesting): Use POSIX.2 fnmatch instead of glob_match.
+
+       * configure: If $RANDOM is implemented (ksh, bash or zsh), use
+       the `type' builtin to determine if gcc, bison, ranlib are
+       available.  ksh writes "fubar: command not found" to stderr,
+       foiling the test -n "`command 2>&1`" method.
+       Remove makefile on signal.
+
+       * system.h: Include sys/param.h if not _POSIX_SOURCE instead
+       of if not POSIX, to get DEV_BSIZE.
+
+       * makepath.c, posixtime.y, system.h: Add _AIX case to alloca decl.
+
+Sun Jun  9 01:26:27 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * Version 2.0.
+
+       * basename.c, dirname.c: Use str[r]chr and string.h if
+       STDC_HEADERS as well as if USG.
+
+       * touch.c (utime_now): Created from code in touch ().
+
+Sat Jun  8 11:02:32 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * backupfile.c: Use POSIX instead of _POSIX_SOURCE to
+       determine whether to check whether readdir returned a valid
+       entry. 
+
+Fri Jun  7 21:44:51 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * fileblocks.c (st_blocks), system.h (ST_NBLOCKS): Always
+       return number of 512 byte blocks, not DEV_BSIZE blocks.
+       (convert_blocks): Always expect 512-byte blocks.
+
+Thu Jun  6 12:54:26 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * system.h [POSIX]: If PATH_MAX not defined, use pathconf.
+       Remove NAME_MAX stuff; not used.
+
+       * system.h: Make #include <strings.h> depend on not (USG or
+       STDC_HEADERS) instead of not (USG or POSIX).
+
+       * configure: New shell script to aid compilation.
+
+Mon Jun  3 16:42:41 1991  David J. MacKenzie  (djm at wheat-chex)
+
+       * cp.h: Remove some decls of functions returning int that
+       conflict with prototypes on HPUX.
+
+       * cp.c (make_path, re_protect): New functions from Jim
+       Meyering.  Changes to other functions as well, to add +path,
+       +link, +symbolic-link options.
+
+Sun Jun  2 15:45:24 1991  David J. MacKenzie  (djm at wheat-chex)
+
+       * most files: use GPL version 2.
+
+Sat Jun  1 20:17:35 1991  David J. MacKenzie  (djm at wheat-chex)
+
+       * rm.c, backupfile.c: If _POSIX_SOURCE, don't refer to d_ino.
+
+Sun May 19 18:42:09 1991  David J. MacKenzie  (djm at churchy.gnu.ai.mit.edu)
+
+       * touch.c: Renamed getdate to get_date to avoid SVR4 conflict.
+
+Thu May 16 23:12:01 1991  David J. MacKenzie  (djm at albert.gnu.ai.mit.edu)
+
+       * cp.c, mv.c, ln.c: Use alloca and strcpy directly instead of
+       in a macro for generating backup filename.  The latter
+       sometimes coredumps for some reason.
+
+Sat Apr 20 00:03:09 1991  David J. MacKenzie  (djm at geech.gnu.ai.mit.edu)
+
+       * dd.c: Add conv=notrunc and truncate output file by default,
+       for POSIX.
+
+       * rm.c (rm): Refuse to remove path/. and path/.., as well as `.'
+       and `..', for POSIX.
+
+       * chown.c: Allow `:' as well as `.' to separate group from
+       user, for POSIX.2 draft 11.
+
+       * Many programs: Don't bother to get the long-option index
+       value from getopt_long, since we ignore it.
+
+       * Many programs: Separate long-option option names from their
+       args with `=' instead of ` ' in usage messages.
+
+       * touch.c (touch): Don't refuse to touch non-regular files.
+
+Wed Apr 10 12:19:30 1991  David J. MacKenzie  (djm at churchy.gnu.ai.mit.edu)
+
+       * cp.c, cp-aux.c: Add -a +archive option, an easier to
+       remember synonym for -dpR.
+
+Fri Mar 15 16:16:54 1991  David J. MacKenzie  (djm at geech.ai.mit.edu)
+
+       * mv.c (copy): Try to preserve file ownership in
+       cross-filesystem copies.
+
+       * backupfile.c, rm.c: Go back to using d_ino instead of
+       d_fileno.  POSIX.1 specifies neither, and d_ino is more
+       common, perhaps ubiquitous.
+
+       * chown.c (describe_change): Don't print the group name if it
+       didn't change (thus is a null pointer).
+       (main): Initialize group name to null.
+
+Mon Feb 25 11:44:14 1991  David J. MacKenzie  (djm at geech.ai.mit.edu)
+
+       * dd.c (copy): Only seek if not seeking to start of file, so
+       "dd >> foo" works with Minix shell that doesn't open foo in
+       append mode. 
+
+Thu Feb 21 11:59:39 1991  David J. MacKenzie  (djm at geech.ai.mit.edu)
+
+       * ln.c (do_link), mv.c (do_move), cp.c (copy): Store backup
+       filename using alloca so we don't have to free it every place
+       we return.  From Jim Meyering.
+
+Thu Feb 14 00:41:43 1991  David J. MacKenzie  (djm at geech.ai.mit.edu)
+
+       * cp.c (copy_reg): Only make holes when copying a regular file
+       onto a regular file.
+
+Fri Jan 18 06:31:59 1991  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * ls.c: Move defn. of S_IEXEC to after header files are included.
+       * cp.h: Always declare stat and lstat.
+
+Thu Jan 10 02:16:55 1991  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp.h: Only declare some system calls if not POSIX.
+
+       * eaccess.c, idcache.c, dd.c, install.c, ln.c, system.h:
+       Change _POSIX_SOURCE to POSIX.
+
+       * fileblocks.c, system.h: Change STBLOCKS_MISSING to
+       ST_BLOCKS_MISSING (was already that way in cp.c).
+
+Fri Dec 28 18:40:34 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * chmod.c, chown.c, chgrp.c, du.c: Rename -d option to -L for
+       similarity to ls and cpio. 
+
+Thu Dec 27 00:06:45 1990  David J. MacKenzie  (djm at egypt)
+
+       * rm.c (clear_directory): Keep looking for files to remove
+       until we don't find any, so that any .nfs* files created by
+       removing other files are also removed, eventually.
+
+       * install.c (main): Strip trailing slashes on all args.
+
+       * mv.c (copy): Open target file with mode 0600, not 0777.
+       [FCHMOD_MISSING]: Perform chmod after closing files, not
+       before, for MS-DOS.
+
+       * cp.c (do_copy): Don't append `..' to target dir name.
+
+       * du.c (main, count_entry, usage): Add -D +dereference-args
+       and -d +dereference options.
+
+Wed Dec 26 03:39:18 1990  David J. MacKenzie  (djm at egypt)
+
+       * dirname.c, xstrdup.c: Get decls from standard files, if available.
+
+Thu Dec 20 23:10:22 1990  David J. MacKenzie  (djm at egypt)
+
+       * makepath.c: New file, adapted from code in mkdir.c,
+       install.c, and cpio util.c by Jim Meyering.
+       * mkdir.c, install.c: Use make_path.
+
+Sun Dec 16 00:56:54 1990  David J. MacKenzie  (djm at egypt)
+
+       * chown.c, chgrp.c: New files.
+
+Sat Dec 15 20:42:32 1990  David J. MacKenzie  (djm at egypt)
+
+       * cp.h: Declare POSIX functions always -- _POSIX_SOURCE
+       doesn't imply STDC declarations.
+
+       * system.h: Define S_ISTYPE macros not defined by sys/stat.h.
+
+       * Many files: Use S_ISTYPE macros.
+
+       * backupfile.c, rm.c: Use name d_fileno for member of struct
+       dirent instead of d_ino, for POSIX.
+
+Wed Dec 12 23:38:22 1990  David J. MacKenzie  (djm at egypt)
+
+       * ls.c: Declare time() as time_t instead of long, to prevent
+       conflict with standard header files.
+
+       * cp.c (copy_reg): Instead of using NO_SPARSE_FILES, use
+       st_blocks to determine whether the original file contains any
+       sparse blocks, and only create them if so.  On systems without
+       st_blocks, to be safe, never create sparse blocks.
+
+Thu Nov  8 12:16:27 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * idcache.c: If _POSIX_SOURCE not defined, declare getpw and
+       getgr functions (not an optimal solution, but I hate to add
+       yet another configuration macro).
+
+       * Makefile: Define AR and RANLIB and pass to child makes.
+       lib/Makefile: Use them.
+
+Tue Nov  6 23:18:06 1990  David J. MacKenzie  (djm at mole.ai.mit.edu)
+
+       * idcache.c: New file from code in ls.c.
+
+Fri Nov  2 14:34:40 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * Move files into src and lib directories, split out library
+       functions into separate files in lib, and rewrite Makefiles.
+
+Mon Oct 29 01:20:46 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * mv.c (do_move), rm.c (remove_file, remove_dir): If stdin is
+       a tty and file is unwritable, prompt before overwriting.
+
+       * cp.c (copy_reg): Only make sparse files if
+       NO_SPARSE_FILES is undefined, to accomodate dumb kernels.
+
+       * du.c (count_entry): Remove misinformed HPUX kludge that
+       doesn't really fix the problem.
+
+       * rm.c (rm): Check for textual equality with '.' and '..', not
+       dev/inode equality.
+
+Sat Oct 27 23:38:55 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * rm.c (check_stack): If not interactive, don't prompt when
+       corruption is found, just quit.
+       (remove_file, remove_dir): Delete leading spaces in verbose
+       output. 
+
+       * cp.c (copy), rm.c (remove_dir): If we think the dest. file
+       is unwritable, warn the user in the interactive prompt instead
+       of automatically skipping the file.  Because of race
+       conditions and other protection mechanisms we might not know
+       about, and POSIX.
+
+Mon Oct  8 18:51:25 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * du.c (main, usage, count_entry): Add +separate-dirs -S option.
+
+       * dd.c (main): Don't trap SIGINT if it was being ignored.
+
+Tue Sep 25 16:40:43 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * install.c (copy_file, install_file_in_file): Change
+       attributes after stripping, to guard against strip programs
+       that clear setuid bits, etc.
+
+Fri Sep 21 22:31:43 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * cp.c (copy_reg): Put back ftruncate way of making holes
+       because the other way can't make a hole at the end of a file.
+
+Tue Sep 18 03:47:45 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * install.c (change_attributes): Don't ignore EPERM for chown,
+       since the default uid is now the current uid.
+
+Sun Sep  9 16:54:19 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * Version 1.4.
+
+       * cp.h: Declare free returning void, not int, so it
+       doesn't bomb on Xenix.
+
+Fri Sep  7 04:35:35 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * system.h, backupfile.c, savedir.c [DIRENT]: if direct is
+       defined (as on Ultrix 4.0), undefine it before redefining it.
+
+Tue Sep  4 03:10:24 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * dd.c (apply_translations, translate_charset): Code moved
+       from parse_conversion.
+       (apply_translations): Convert from EBCDIC to ASCII before
+       converting case.
+
+       * mvdir.c (fullpath): Return a value.
+
+       * dd.c (copy): Increment count of truncated records once
+       per record, not once per character that overflows.
+
+Mon Sep  3 22:23:57 1990  David J. MacKenzie  (djm at coke)
+
+       * dd.c (swab_array): Function removed.
+       (copy): Rewrite conv=swab to work when odd number of bytes
+       are read.
+       (scanargs): Die if invalid numeric value is given.
+       (parse_integer): Return -1 if invalid arg.
+       (bit_count): Faster version from Jim Meyering.
+
+       * cp.c, mkfifo.c [MKFIFO_MISSING]: Define mkfifo.
+
+Thu Aug 30 00:17:02 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * mvdir.c (main): Make sure `from' is not a parent of any part
+       of `to', not just the explicitly given part.
+       (fullpath): New function.
+
+Wed Aug 29 19:50:05 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * mvdir.c: Renamed from mv_dir.c, for consistency with mkdir and rmdir.
+       * dirlib.c: Caller changed.
+
+Tue Aug 28 18:05:24 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * touch.c (main): Don't interpret first non-option arg as a
+       time if `--' is given (POSIX-required kludge).
+
+       * touch.c: Add long-named options.
+
+       * Many files: Include <getopt.h> instead of "getopt.h" since
+       getopt.h will be in the GNU /usr/include.
+
+       * install.c: Declare some functions.
+
+       * touch.c, getdate.y, posixtime.y, mktime.c: New files, from bin-src.
+
+       * posixtime.y: Move year from before time to after it (but
+       before the seconds), for 1003.2 draft 10.
+
+Mon Aug 27 03:25:36 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * touch.c (main): If no time is given and first arg is a valid
+       timespec, use it as one.
+
+Sat Aug 25 01:36:16 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * posixtime.y: Enclose YYABORT in braces in case some yacc's
+       need it.
+
+       * touch.c: Remove -i option.  Change some error messages.
+       (readname): Function removed.
+
+Thu Aug 23 12:56:33 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * cp.c (copy): Only restore dir mode if it was changed.
+
+Wed Aug 22 01:45:54 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * cp.c (copy): Don't only backup files when -f is given.
+
+       * ls.c: Add -X +sort=extension option.  Rename
+       +kilobyte-file-size to +kilobytes.
+
+       * du.c: Rename -f option to -x, for POSIX.  Rename
+       +kilobyte-file-size to +kilobytes.  Add -b, +bytes option for
+       POSIX. 
+
+       * cp-aux.c (usage): Change -o to -x.
+       (stpcpy): Renamed from str_cpy.  Change callers in cp.c.
+
+       * cp.c: New variable, `flag_copy_as_regular'.
+       (main): For -R, unset `flag_copy_as_regular'.
+       Rename -o to -x for consistency with du.
+       (copy): Only unlink destination files when -f is given.
+       Only prompt when -i given and copying as a regular file.
+       Move check for previous link after other checks, reducing
+       duplicate code.
+       Create directories with mode 0700 initially, for POSIX.
+
+Mon Aug 20 03:29:08 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * dd.c (copy): Swap input bytes instead of output bytes.
+       (swab_array): New function.
+
+       * dd.c (copy): If sync and noerror, zero the buffer before the
+       read instead of after so that any data read before an error
+       occurred are preserved.
+       On read error, print stats and seek past the bad block if noerror.
+       noerror doesn't affect write errors, for POSIX.
+       (scanargs): Use two buffers if no buffer sizes given.
+       Do not block or unblock if cbs not given.
+       (print_stats): New function.
+       (quit): Call it.
+
+Mon Aug 13 23:30:03 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * cp.c (copy): If dest. exists and is unwritable, skip the file.
+
+       * rm.c, mv.c, cp.c, ln.c (main): Respect the last -f or -i given,
+       for POSIX.
+
+       * rm.c (remove_file): Only prompt if -i is given.
+       (main, usage): Remove -o +override-mode option, obsolete if
+       POSIX accepts our objection about prompting.
+
+       * mv.c (do_move): Only prompt if -i is given.
+
+       * ln.c (do_link): If dest. file exists and -i and -f not
+       given, skip the file.
+
+Tue Aug  7 12:51:18 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * dd.c (main): If seek= given, don't truncate output file.
+       (copy): Use `read' to skip output blocks if not regular file.
+       Sync with NUL instead of SPC.
+
+Mon Aug  6 14:43:30 1990  David J. MacKenzie  (djm at pogo.ai.mit.edu)
+
+       * rm.c: Rename `ignore_errors' to `ignore_missing_files', and
+       have it only suppress messages about nonexisting files.
+       (main): Get dev and ino of `.' and `..'.
+       (rm): If file is the same as `.' or `..', return with error.
+       (remove_file): Remove the file rather than skipping it if
+       unwritable, no -i, and stdin not tty.
+       (remove_dir): Return an error if directory is nonwritable,
+       rather than nonreadable or nonsearchable, for POSIX.2 draft 10.
+
+       * chmod.c (main): Use fixed error checking to make sure that
+       options aren't mixed together in the same args as mode specifiers.
+
+Sun Aug  5 11:51:12 1990  David J. MacKenzie  (djm at pogo.ai.mit.edu)
+
+       * chmod.c (main): Use umask for '-' op.
+
+Sat Aug  4 10:11:30 1990  David J. MacKenzie  (djm at pogo.ai.mit.edu)
+
+       * mkfifo.c: Remove -p +path option, no longer specified by POSIX.
+
+Fri Aug  3 13:38:28 1990  David J. MacKenzie  (djm at pogo.ai.mit.edu)
+
+       * mkdir.c, mkfifo.c, create.c (main): Don't tell mode_compile to
+       respect the umask for certain operations, since the umask is 0 anyway.
+
+       * install.c (get_ids): Use getuid and getgid to get defaults,
+       instead of -1.
+
+Fri Jul 27 14:32:40 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * backupfile.c (dirname): Always replace frontmost slash with a null.
+
+Thu Jul 26 00:20:35 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * cp.h: Declare umask as unsigned short.
+
+       * eaccess.c: Make uid and gid unsigned short, and group array unsigned.
+
+Wed Jul 25 18:38:57 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * rm.c (remove_file, remove_dir): Print verbose message right
+       before actually trying to remove the file, after the prompting.
+
+       * ls.c (getuser, getgroup): Make uid and gid unsigned short, not int.
+
+Tue Jul 24 03:39:42 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp.c (copy), ln.c (do_link), mv.c (do_move): For +verbose,
+       print the file names just before actually attempting the
+       copy/link/move, to produce a list of the files that they
+       actually try to copy/link/move, omitting skipped files.
+       Remove leading spaces from +verbose output.
+
+Mon Jul 23 16:57:44 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp.c (copy): Make +update operate silently, like +one-file-system. 
+
+       * ln.c: Add -F as synonym for -d, for SunOS compatibility.
+
+Sun Jul 15 23:23:28 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp.c (copy): Go back to using xstat on dest.
+
+Wed Jul 11 12:10:33 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp.c (copy): Make directories with desired mode plus u+wx so
+       if the copy is interrupted, the dir is closer to the desired mode.
+       Don't backup directories.
+
+Sun Jul  8 00:39:31 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * rm.c (main, usage): Add new option -d, +directory.
+       (rm): If -d given, use remove_file instead of remove_dir for
+       directories. 
+       (remove_file): If directory, print "remove directory `foo'?"
+       for interactive instead of "remove `foo'?".
+
+       * ln.c (main): If -s given, print warning message if symlinks
+       are not available.
+       * mkfifo.c (main): If fifo's are not available, print message
+       and exit.
+
+Fri Jul  6 02:02:49 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * install.c (main): Use the current user and group ID for the
+       default owner and group.
+
+       * mv.c (main): New option -u, +update.
+       (do_move): Don't move nondirectories if -u and there is an existing 
+       destination that has the same or newer mtime.
+       (usage): Document -u, +update.
+
+       * cp.c (main): New option -u, +update.
+       (copy): Don't copy nondirectories if -u and there is an existing 
+       destination that has the same or newer mtime.
+       * cp-aux.c (usage): Document -u ,+update.
+
+Thu Jul  5 10:04:12 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * ln.c (do_link): Don't check whether OLD exists before trying
+       to make link.
+
+Tue Jul  3 01:51:55 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * ls.c: Allow "+time=atime" and "+time=ctime" for C hackers.
+
+       * chmod.c (main): Don't check whether multiple mode arguments
+       are given, because optind has a different value depending on
+       whether or not the option is the last character in the
+       ARGV-element.
+
+Sat Jun 30 12:32:51 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * cp.c (copy): Use lstat on dest. file, not *xstat.
+
+Mon Jun 25 18:07:20 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * ls.c (print_long_format): Truncate user and group names to 8
+       chars to preserve column alignment.
+       (length_of_file_name_and_frills): Don't assume type indicator
+       will be printed for unknown file types that some os's have.
+
+       * install.c: Declare getgrnam for systems where grp.h doesn't.
+
+Sat Jun 23 00:06:35 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * Version 1.3.
+
+       * du.c (count_entry) [HPUX_NFS_BUG]: If the size of the file
+       according to the number of blocks reported is twice or more than
+       the size of the file according to the number of bytes
+       reported, halve the number of blocks.
+
+Fri Jun 22 00:38:20 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp.c (copy_dir): Initialize 'ret' to 0.
+
+       * cp.c (main), ln.c (main), mv.c (main), rm.c (main):
+       Make -i override -f and -o, to be conservative about
+       removing peoples' files.
+
+       * mkdir.c (make_path), mkfifo.c (make_path): Don't try to stat
+       "" or "/".
+
+       * rm.c, rmdir.c, mkdir.c, mkfifo.c: Move code to remove
+       slashes at the end of an arg from main to
+       strip_trailing_slashes. 
+
+       * install.c (strip): Print error message if the `strip'
+       program can't be run.
+
+       * system.h (convert_blocks): Macro moved from du.c and ls.c.
+       Take a second parameter indicating whether to convert to
+       kilobytes or 512 byte blocks.
+       * ls.c, du.c: Pass second parameter to convert_blocks.
+
+Thu Jun 21 01:19:28 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * ls.c (print_long_format): Use mode_string instead of filemodestring. 
+
+       * ls.c (print_long_format): Compare times as longs, not ints.
+       (longdiff): Macro to compare two longs efficiently if sizeof
+       int == sizeof long and less efficiently but correctly if they
+       are different sizes.
+       (compare_ctime, etc.): Use longdiff.
+
+       * ls.c (decode_switches): Make -k not imply -s, to allow the
+       summary directory size printed by -l to be in 1k blocks
+       without having the size of each file printed as well.
+       (convert_blocks): Provide for systems with a blocksize that is
+       other than 512 or 1024 bytes.
+
+       * du.c (main): Exit with status 0 normally.
+       (convert_blocks): Provide for systems with a blocksize that is
+       other than 512 or 1024 bytes.
+
+Wed Jun 20 01:46:09 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * ln.c (do_link): Take out code to give an error if source and
+       dest are the same file.  The dubious usefulness of the special
+       case to prevent 'ln x x' from removing 'x' (ln -i can be used
+       instead) is not worth preventing 'ln x y' from failing the
+       second time in a row, and appears to contradict POSIX anyway.
+
+Mon Jun 18 02:48:17 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * ls.c (print_file_name_and_frills):
+       (length_of_file_name_and_frills, print_long_format):
+       Allow 6 digits for i-number, not 5.
+
+Sun Jun 17 00:09:23 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * install.c (install_dir): Don't check whether "" or the root
+       directory exists (the former fails on some systems).
+
+       * system.h: Make inclusion of sys/file.h conditional on USG
+       and _POSIX_SOURCE, not DIRENT.
+
+       * chmod.c (change_dir_mode): Use xrealloc instead of free and
+       xmalloc in case malloc already left extra room.
+       (xrealloc): New function.
+
+       * rm.c (clear_directory): Prevent buffer overruns.
+       More efficient string handling.  Don't skip rest of directory
+       if continuing after finding circular inode.
+       (xrealloc): New function.
+
+Sat Jun 16 01:45:42 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * argmatch.c (invalid_arg): Change order in which the items
+       are printed. 
+
+       * ls.c: Add +tabsize (-T) option.
+
+Fri Jun 15 23:40:55 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * dd.c (scanargs): For ibs and obs, set C_HARDWAY.
+       (copy): Use different buffers only if C_HARDWAY, not if
+       blocksizes are the same, to ensure constant output block sizes.
+
+Wed Jun 13 23:56:20 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * savedir.c: New file from code in chmod.c, modified to
+       prevent buffer overruns.
+       * chmod.c (change_dir_mode), cp.c (copy_dir), du.c
+       (count_entry): Use savedir.
+
+Thu Jun  7 03:52:02 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * system.h (ST_BLKSIZE) [!STBLOCKS_MISSING]: If st_blksize is
+       0 (as on pipe reads on some systems), use BSIZE instead.
+       Define BSIZE as DEV_BSIZE if necessary.
+
+       * Makefile, system.h, fileblocks.c: Use STBLOCKS_MISSING to
+       control whether st_blksize and st_blocks are used.
+       * Makefile, system.h, backupfile.c: Use DIRENT to control
+       whether <dirent.h> is used.
+
+Thu May 31 00:55:36 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * fileblocks.c: New file.
+       * du.c (blocks_to_kb): Replace with convert_blocks macro.
+       (main): Recognize new -k option.
+       (usage): Document it.
+       * ls.c (nblocks): Replace with convert_blocks macro.
+       * system.h (ST_BLKSIZE) [USG]: Use BSIZE from sys/param.h instead of
+       having the user define BLKSIZE.
+       (ST_NBLOCKS) [USG]: Use st_blocks from fileblocks.c.
+
+Wed May 23 00:40:39 1990  David J. MacKenzie  (djm at apple-gunkies)
+
+       * argmatch.c: New file, taken from ls.c.
+       * getversion.c (get_version): Use argmatch, to allow
+       abbreviations.  Default backup type is existing_numbered.
+       * mv.c (main), ln.c (main), cp.c (main): Only make backups if
+       -b (+backup) is given.  If envar SIMPLE_BACKUP_SUFFIX is set,
+       use it as a default instead of `~'.
+       * mv.c (usage), ln.c (usage), cp-aux.c (usage): Update messages.
+
+Tue May 22 00:56:51 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * install.c: New file (from ../bin-src).
+
+       * dd.c (copy): Don't count completely failed writes as partial
+       writes.  Make buffers unsigned.  If blocking or unblocking,
+       pad final partial buffer if necessary.
+
+       * getversion.c: New file.
+       * mv.c (main), cp.c (main), ln.c (main): Control backup types
+       with getenv ("VERSION_CONTROL") and +version-control or -V.
+
+       * cp.c (yesno), mv.c (yesno), ln.c (yesno): Stop reading if
+       EOF reached as well as at newline.
+
+       * backupfile.[ch]: Rename var `version_control' to `backup_type'.
+
+Sat May 19 23:38:46 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * touch.c: Change some error messages.  Include "getopt.h".
+
+Sat May 19 00:16:50 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * mv.c (main), ln.c (main), cp.c (main): Revise
+       backup-creation options.
+       * mv.c (usage), ln.c (usage), cp-aux.c (usage): Revise messages.
+
+       * chmod.c (describe_change): Use mode_string instead of
+       filemodestring. 
+
+       * cp.c (main): Recognize new options for making backups.
+       * cp.c (copy): Make backups if requested.  Fix typo.
+       * cp-aux.c (usage): Update message.
+
+       * mv.c, cp.c: Remove code to conditionally use utimes instead
+       of utime, since the extra resolution of utimes was not being
+       used, the emulation overhead is probably insignificant,
+       and utime is a standard function.
+
+       * cp-hash.c: Fix up comments.
+
+Fri May 18 23:06:23 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * mv.c (do_move): Only make backup if dest file exists.
+       Don't continue moving file if dest can't be backed up.
+       * ln.c (do_link): Don't try to unlink dest if it was backed up.
+       Don't continue moving file if dest can't be backed up.
+
+       * system.h: Make SIGTYPE default to void if not defined.
+
+       * modechange.[ch]: Rename struct and external functions to start
+       with 'mode_'.
+       * modechange.c (oatoi): Make static.
+       (mode_compile): Take an additional arg indicating which
+       symbolic operators should be affected by the umask.
+       * modechange.h: Add defines for mode_compile arg mask.
+       If __STDC__, use prototypes.
+       * chmod.c, mkdir.c, mkfifo.c, create.c: Account for above changes.
+
+Tue May 15 16:17:34 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * dd.c (copy): Quit with nonzero status if final write fails.
+
+Mon May 14 14:34:10 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * dd.c: Make translation tables unsigned.
+       (main): Give `input_file' and `output_file' nonzero values for
+       stdin and stdout. 
+       (parse_conversion): Set new global vars 'space_character' and
+       'newline_character' to correct values when translating to EBCDIC
+       (either flavor).
+       (copy): Use 'space_character' and 'newline_character' instead
+       of hardcoded ASCII values.  Ignore attempts to seek on output pipe,
+       socket, or fifo.  If possible, seek instead of reading to skip
+       initial input records.  Sync with `space_character' instead of
+       nulls, for POSIX.
+
+       * cp.c (copy_reg): Compare lseek values as longs, not ints.
+
+Sat May 12 01:16:42 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp-hash (remember_created): Return error status instead of
+       fatal error. 
+       * cp.c (copy): Change caller.
+       (do_copy, copy_reg): Return error status instead of fatal error.
+
+       * Move rename emulation from mv.c to dirlib.c so other
+       programs can use it.
+       * mv.c, ln.c (main): Recognize new options for making backups.
+       * mv.c (do_move), ln.c (do_link): Make backups if requested.
+       * mv.c, ln.c (usage): Update message.
+       * backupfile.c, backupfile.h: New files.
+
+       * cp.h: Ifdef out decl of umask because of SunOS 4.1 (POSIX) conflict.
+
+       * Define all `main' functions as returning void.
+
+Fri May 11 02:11:03 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * ln.c, mv.c, rm.c, rmdir.c, create.c, chmod.c: Change some
+       error messages. 
+
+       * du.c, cp-aux.c (error): Function removed.
+       Change callers to use error.c version.
+       * cp.c (copy, do_copy, copy_dir): Return an error status.
+       * ls.c (error, fatal, perror_with_name): Functions removed.
+       Change callers to use error.c.
+
+Sat May  5 23:46:48 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * ln.c (do_link): Don't allow trying to link a file to itself,
+       because the source file would be removed if they are the same
+       directory entry, and also for consistency with mv and cp.
+
+Fri May  4 13:42:53 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp.c (copy_reg): Only write a null to the end of the file if
+       the end of the file was sparse.
+
+       * ls.c (print_name_with_quoting): Make the char to print
+       unsigned to prevent sign extension problems with -b.
+
+Fri Apr 20 13:52:15 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * Version 1.2 released.
+
+Wed Apr 18 14:36:15 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * Makefile: Use chsize for ftruncate on Xenix.
+
+       * cp.c (copy): Remove broken code that attempted to
+       substitute for ftruncate on systems missing it.
+
+Mon Apr 16 13:58:01 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp-aux.c (usage): Fix mistake in message.
+
+       * Version 1.1 released.
+
+Sat Apr 14 17:23:11 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * ls.c (main): Don't remove leading path from program_name.
+       (basename): Function removed.
+       (length_of_file_name_and_frills): Don't add 1 for type indicator
+       for block and character special files. 
+
+Thu Apr 12 19:50:15 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * Makefile: Suggest using -DBLKSIZE=512 instead of 1024 for USG.
+
+       * dd.c (copy): Print copying statistics when exiting because
+       of a read or seek error.
+       (interrupt_handler): New function.
+       (main): Trap SIGINT to run interrupt_handler, for POSIX.
+
+Tue Apr 10 01:09:38 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * chmod.c (change_file_mode): Don't change the mode of
+       symbolic links.
+
+Mon Apr  9 13:30:00 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * modechange.c (compile_mode): Return an error if an octal
+       number argument is too large.
+
+Sun Apr  8 20:33:20 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * dd.c: Use `error' instead of `fatal' and `pfatal_with_name',
+       for greater control of the message format.
+       * head.c, tail.c: Use `error' instead of `fatal_perror' and
+       `nonfatal_perror'.  Remove some unnecessary info from messages.
+       * chmod.c, create.c, ln.c, mkdir.c, mkfifo.c, mv.c, mv_dir.c,
+       rm.c, rmdir.c: Remove definition of `error'.
+       * error.c: New file created from code in mv.c.
+       * Makefile: Link the above programs with error.o.
+
+       * ln.c (do_link): Use eaccess_stat to determine writability.
+       * mv.c (do_move): Ditto.
+       * rm.c (remove_file): Ditto.
+       (remove_dir): Use eaccess_stat to determine readability and
+       searchability.  Move initial interactive query here from
+       clear_directory. 
+       * Makefile: Link ln, mv, and rm with eaccess.o.
+
+Sat Apr  7 11:47:52 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * Makefile: Link cp with eaccess.o.
+       * eaccess.c: New file adapted from code in cp.c and cp-aux.c.
+       * cp.c (copy): Use eaccess_stat to determine writability.
+       Consider a file unwritable by root if it has no permissions.
+       (main): Remove groups initialization code.
+       * cp-aux.c (member): Function deleted.
+
+       * cp.c (copy): Temporarily change the mode of directories if
+       necessary to overwrite them when running recursively.
+       Consider a directory to be non-overwritable if it lacks write
+       permission as well as if it lacks execute permission.
+
+       * rm.c, mv.c, mv_dir.c, chmod.c, create.c, ln.c: Remove some
+       irrelevant or redundant information from error messages.
+
+Fri Apr  6 15:20:45 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp.c (copy): Only change mode of regular files and directories;
+       others are already correct.
+
+Thu Apr  5 04:31:56 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * dd.c: Remove the vars that are set by command line options
+       from a useless struct and give them more meaningful names.
+
+Mon Apr  2 02:58:34 1990  David J. MacKenzie  (djm at spike.ai.mit.edu)
+
+       * cp.c (main): Use NGROUPS from sys/param.h to determine
+       whether BSD multiple groups are supported and how large to
+       make the array.
+       * Makefile: Remove references to GETGROUPS_MISSING.
+
+Sun Apr  1 18:53:57 1990  David J. MacKenzie  (djm at spike.ai.mit.edu)
+
+       * cp.c (main): Always initialize group info.
+
+Sat Mar 31 22:29:57 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * mv.c [RENAME_MISSING] (rename): To rename directories, run
+       setuid root mv_dir program. 
+
+Tue Mar 20 14:28:25 1990  David J. MacKenzie  (djm at pogo.ai.mit.edu)
+
+       * touch.c: Remove POSIX_COMPAT ifdef since there is no reason
+       to disable the GNU extensions.
+       (main): Set new global var `program_name'.
+       (error): Replace with more versatile version.
+       Global: Change calls to fprintf and error to use the new error.
+       (main): Initialize global variables.  Don't bother making
+       temporary copy of arg to -d.  Don't ignore any files named on
+       the command line if -i is given.
+       (usage): Don't take an arg.  Use `program_name' instead of
+       hardcoded name.
+       (touch): In utime emulation for BSD, ftruncate the file to its
+       original size so empty files stay empty after being touched.
+
+Sun Mar 18 01:02:39 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * ln.c (strip_trailing_slashes): New function.
+       (main, do_link): Call it.
+
+       * cp-aux.c (strip_trailing_slashes): New function.
+       * cp.c (do_copy): Call it.
+       * cp.h: Declare it.
+
+       * mv.c (strip_trailing_slashes): New function.
+       (main, movefile): Call it.
+
+Sat Mar 17 21:45:35 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp-aux.c, cp.h: Rename user_confirm_overwriting to yesno and
+       don't have it print a prompt, so it can be used in several
+       places. 
+
+       * cp.c (do_copy): Change an error message to resemble mv's.
+       Remove all trailing slashes from all non-option args.
+       (main): Set new global var `stdin_not_tty'.
+       (copy): Use POSIX method of handling file overwriting and
+       prompting. 
+
+       * dirlib.c (mkdir): Use chmod to set the directory mode after
+       successful creation, so set[ug]id and sticky bits are set
+       correctly. 
+
+Thu Mar 15 12:33:23 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * Makefile: Add commented out definitions for SCO Xenix.
+
+       * ls.c (print_type_indicator): Don't print a '*' next to
+       executable block or character special files.
+
+        * chmod.c (error): New function, replacing nonfatal_perror,
+        memory_out, and invalid_mode.
+        Global: Call error instead of the above functions.
+       (change_dir_mode): Make the new size of the path twice the
+       size of the name that was too long, rather than twice its old
+       size. 
+
+       * rm.c: Move interactive query about whether to remove a
+       directory from remove_dir to clear_directory; only query for
+       directories that are not empty.
+
+Wed Mar 14 10:48:40 1990  David J. MacKenzie  (djm at rice-chex)
+
+       * system.h [USG]: Define X_OK.
+
+       * rm.c (main): Set new global var `stdin_not_tty'.
+       (rm): Most of code moved to two new functions, remove_file and
+       remove_dir.
+       (remove_file): Use POSIX method of determining whether to remove
+       non-directories.
+       (remove_dir): Use POSIX method of determining whether to
+       remove directories, almost.
+       (perror_with_name): Function removed.
+       (error): Simple version replaced with more powerful version.
+       Global: Change calls to fprintf, perror_with_name, and old
+       error to calls to new error.
+
+       * ln.c (main): Set new global var `stdin_not_tty'.
+       If force, turn off interactive.
+       (do_link): By default, don't allow hard links to symbolic links to
+       directories.  Use POSIX method of determining whether to
+       overwrite destination.
+       (yesno): Function renamed from confirm, and arg removed.
+       (lisdir): Function removed.
+
+       * mv.c (main): Set new global var `stdin_not_tty'.
+       (yesno): Function renamed from yes.
+       (do_move): Use POSIX method of determining whether to
+       overwrite destination.
+
+       * Makefile: Make executables depend on .o files, not .c files,
+       to allow for parallel compilation.
+
+Tue Mar 13 00:50:14 1990  David J. MacKenzie  (djm at rice-chex)
+
+       * rm.c (main): Disallow removal of paths that have '..' as the
+       final element.
+       (basename): New function.
+
+       * ls.c (print_type_indicator): Mark FIFOs with '|' and sockets
+       with '='.
+       (print_long_format): Print numbers as unsigned and add extra
+       space for POSIX flag.
+
+       * dd.c: Make the record counts unsigned.
+       (quit): Print them as unsigned.
+
+       * modechange.c (compile_mode): Only get umask value when needed.
+       If users are not given or are `a', affect set?id and sticky bits.
+       If memory is exhausted while allocating a new list element,
+       free the old elements before returning.
+
+       * Makefile (CC): Add comment noting that either fixincludes or
+       -traditional needs to be used for gcc to compile ioctl calls
+       correctly. 
+
+Mon Mar 12 16:25:23 1990  Jim Kingdon  (kingdon at pogo.ai.mit.edu)
+
+       * touch.c [UTIME_OF_NULL_MISSING]: Call lseek() before write().
+
+       * posixtime.y [__GNUC__]: Use __builtin_alloca.
+
+Fri Mar  9 10:25:09 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * chmod.c (main): Recognize "a,+-=" as valid options.
+
+       * mv.c: Move the code to copy files across filesystems from
+       do_move to a new function, copy, which will eventually be
+       replaced with modules from cp and rm (POSIX requires mv to
+       move directories recursively across filesystems).
+       (do_move): Don't query about overriding a mode that prohibits
+       writing if interactive.  Remove unneeded variable.
+       (copy): Unlink target if copy fails partway through.
+
+Thu Mar  8 10:56:16 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp.c (copy): Don't remove a destination file of a different
+       type unless +force is given.
+
+       * ls.c (decode_switches, usage): Add -U (for "unsorted") as an
+       equivalent to +sort=none.
+
+Mon Mar  5 16:31:14 1990  Torbj|rn Granlund  (tege at echnaton)
+
+       * cp.c (copy): Test for temporarily modified permission mode
+         after the other test, so that `-p' work for files whose mode
+         needed a temporary mode change.
+       * cp.c (copy): Don't waste time calling unlink if we already
+         know that the destination doesn't exists.
+       * cp.c (comment before do_copy): Correct.
+       * cp.c (comment before copy): Describe all params.
+       * cp.c (copy): Only change permission mode for regular files
+         and directories.
+       * cp.c (copy): Unlink the destination file if its type is
+         different from the source.  If the destination is a
+         directory,  error.
+
+Mon Mar  5 00:34:36 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * chmod.c (nonfatal_perror): Don't check for force_silent.
+       (change_file_mode, change_dir_mode): If force_silent, don't
+       print error messages.
+
+       * mv.c (main): If force, turn off interactive.
+       (do_move): Simplify check for query.  Rename `stb' to
+       `to_stats' and `stbf' to `from_stats'.
+       Return error condition if original file could not be renamed or
+       unlinked. 
+
+       * rm.c: Rename global `force_flag' to `ignore_errors' and change its
+       meaning so that it does not overlap with `override_mode'.
+       (main): Have -f +force set override_mode.  If override_mode is
+       set, turn off interactive.
+       (rm): Simplify checks for whether to query the user, based on
+       the new relationship between override_mode and interactive.
+
+Sun Mar  4 23:39:03 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * ln.c (main): Reword an error message to be more like mv's.
+
+       * rmdir.c: Move global `errors' into main instead of having 
+       error set it.
+
+       * mkdir.c: Move global `errors' into main and have make_path
+       return an error status instead of having error set it.
+
+       * chmod.c: Move global `errors' into main and have
+       change_file_mode and change_dir_mode return an error status
+       instead of setting it in nonfatal_perror.
+
+Sat Mar  3 13:59:40 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * ln.c (main): Don't strip leading dirs from argv[0].
+
+       * ln.c (confirm), mv.c (yes, do_move),
+       cp-aux.c (user_confirm_overwriting), rm.c (rm, yesno, check_stack):
+       Print query messages to stderr instead of stdout, for POSIX.
+       Include program name in messages.
+
+Sat Mar  3 11:27:27 1990  Torbj|rn Granlund  (tege at echnaton)
+
+       * cp.c (copy): Don't unlink directories with flag_force
+         (`-f').  Also avoid using force when not necessary.
+         Always copy fifo's and symbolic links as themselves.
+
+       * cp.c (copy_reg): Make int scan first, char scan then, to
+         find frist non-zero byte.  This to avoid false hole
+         creation.
+
+Sat Mar  3 10:22:28 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * mv.c: Rename `pgm' to `program_name'.  Move global `errors'
+       into main.  Have do_move and movefile return an error status
+       instead having error set it.  Remove global vars `args'
+       and `args_left'.
+       (main): Rename `ac' and `av' to `argc' and `argv' and use them
+       and `optind' instead of `args' and `args_left'.
+
+       * cp.c (copy): Don't ignore errors other than EPERM from chown.
+
+Fri Mar  2 16:20:57 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * rm.c (main, usage): Allow -R as a synonym for -r, for POSIX.
+
+       * cp.c (copy): If flag_preserve, preserve the owner and group
+       if possible, as well as mode.
+       (main): Allow -R as a synonym for -r option, for POSIX.
+       * cp-aux.c (usage): Mention -R.
+
+Tue Feb 27 11:49:04 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * cp.c (copy): If not recursive, copy special files and
+       symlinks like regular files and omit fifos.
+
+Mon Feb 26 19:55:24 1990  Jim Kingdon  (kingdon at pogo.ai.mit.edu)
+
+       * ls.c (print_long_format): If time is in the future, print the year.
+       Make the cutoff for old files 6 months not 300 days.
+
+Mon Feb 26 13:31:07 1990  Jim Kingdon  (kingdon at pogo.ai.mit.edu)
+
+       * touch.c, Makefile: Use getdate.y instead of unctime.y.
+
+       * touch.c: Remove posixtime.
+       (main): Check for error from posixtime.
+       posixtime.y: New file.
+
+       * touch.c: Change a few cryptic error messages.
+       Include <errno.h> not <sys/errno.h>.
+       (just_set_amtime): New variable.
+       (touch): Add if (just_set_amtime) code.
+
+Mon Feb 26 15:03:29 1990  Torbj|rn Granlund  (tege at echnaton)
+
+       * cp.c (copy): Test for recursive copy in DIR alternative in
+         the switch statement, so all file types are copied correctly
+         even in a non-recursive copy.
+       * cp.c (copy): Return after having created a symlink, since
+         chmod and utimes dereference, and would affect the symlink
+         target.  Remove test for symlinks after switch.
+
+Sun Feb 25 18:31:09 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * Makefile: Compile ls after vdir so systems with a cc that
+       can't do -c -o don't have to compile ls.c twice for ls.
+
+       * dd.c (usage): Add braces around alternatives.
+
+       * ls.c (print_long_format): Always print the group, for POSIX.
+       (decode_switches): Make -g option a no-op for BSD users.
+       (usage): Remove +group option.
+
+Wed Feb 21 11:13:26 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * ln.c (error): New function.
+       (main, do_link): Call error instead of fprintf and exit.
+       (main): Recognize new -d +directory option to allow superuser to
+       make hard links to dirs, like the BSD ln -f option.
+       (do_link): Don't allow hard links to dirs (they are hard to
+       get rid of -- rmdir and unlink don't do it), unless -d was given.
+       (usage): Mention -d +directory option.
+
+       * rmdir.c (main): Remove trailing slashes from args (added by
+       shell file completion but the rmdir syscall can't handle them).
+       * mkdir.c (main): Remove trailing slashes from args, for
+       uniformity with rmdir (you can't do file completion on dirs
+       that haven't been made yet . . .).
+
+       * mv.c: Rename global var `nargs' to `args_left' to avoid
+       conflict with undocumented BSD libc function (the new name is
+       clearer, anyway).
+
+Tue Feb 20 17:09:19 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * dd.c: Use new global var `program_name' in error messages
+       instead of hardcoded "dd".
+       (main): Set program_name from argv[0].
+
+       * chmod.c, head.c, tail.c (main): Don't strip leading dirs
+       from argv[0].
+       (basename): Function removed.
+
+       * rm.c (main): Don't strip leading dirs from argv[0].
+
+Mon Feb 19 14:34:18 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * rm.c (main): Strip trailing slashes from each arg.
+
+Thu Feb 15 13:23:52 1990  David J. MacKenzie  (djm at rice-chex)
+
+       * Makefile [HPUX CFLAGS]: Add -DUTIMES_MISSING.
+
+Wed Feb 14 15:01:18 1990  David J. MacKenzie  (djm at rice-chex)
+
+       * Makefile (dist): Don't make a non-compressed tar file.
+
+       * mv.c (do_move): Refuse to copy non-regular files across filesystems.
+
+Tue Feb 13 15:06:18 1990  Jim Kingdon  (kingdon at pogo.ai.mit.edu)
+
+       * touch.c (getname): New function.
+       (main): Use it.
+
+Mon Feb 12 11:30:45 1990  David J. MacKenzie  (djm at rice-chex)
+
+       * ln.c (do_link): Check error return from unlink.
+       Include errno.h.
+
+       * du.c (main): Check error return from stat.
+       (str_copyc, str_concatc): Don't return a value, since it is
+       ignored. 
+
+       * cp.c (copy): Check error return from unlink and chmod.  Fix
+       typo in call to error.
+
+       * mv.c (do_move): Check error return of fchmod/chmod and utime[s].
+       (rename): Check error return of unlink.
+
+       * Makefile Definitions of preprocessor macros moved from
+       cp.c and mv.c.  HAVE_FTRUNCATE changed to FTRUNCATE_MISSING.
+       * Makefile, dirlib.c: NEED_MKDIR changed to MKDIR_MISSING.
+       * mv.c, cp.c: Change USG ifdefs to UTIMES_MISSING.
+
+Sun Feb 11 17:50:29 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * chmod.c (usage): Add yet another ellipsis.
+
+Sun Feb 11 16:41:30 1990  Jim Kingdon  (kingdon at pogo.ai.mit.edu)
+
+       * cp.c (copy_reg): Use HAVE_FTRUNCATE to decide whether to
+       use ftruncate().
+       (main): Use GETGROUPS_MISSING to decide whether to use getgroups().
+       [hpux || !USG]: Define HAVE_FTRUNCATE.
+       [USG && !hpux]: Define GETGROUPS_MISSING.
+       mv.c (rename): Put in #ifdef RENAME_MISSING not #ifdef USG.
+       (do_move): Use FCHMOD_MISSING to decide whether to use fchmod().
+       [USG && !hpux]: Define FCHMOD_MISSING and RENAME_MISSING.
+
+Fri Feb  9 10:25:03 1990  David J. MacKenzie  (djm at rice-chex)
+
+       * mv.c (movefile): Remove trailing slashes from FROM (some
+       filename completion systems add them for dirs, and they cause
+       the rename syscall to fail).
+
+Thu Feb  8 22:50:12 1990  Torbj|rn Granlund  (tege at sics.se)
+
+       * cp.c (copy_reg): Change error handling after lseek, since
+         this is a fatal error.  Also change error message to
+         something more generally understood.
+       * Handle files that end in a zero block on USG systems.
+
+       * cp-aux.c (error): Use FATAL to recog fatal errs.
+
+Thu Feb  8 21:25:40 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * ln.c: Remove incorrect comment.
+
+       * cp.c, cp-aux.c (usage): Change +dereference option to
+       +no-dereference, since dereferencing is done by default and
+       the option turns it off.
+
+Mon Feb  5 17:29:20 1990  David J. MacKenzie  (djm at albert.ai.mit.edu)
+
+       * Version 1.0 released.
+\f
+Local Variables:
+mode: indented-text
+left-margin: 8
+version-control: never
+End:
diff --git a/old/fileutils/NEWS b/old/fileutils/NEWS
new file mode 100644 (file)
index 0000000..63066f8
--- /dev/null
@@ -0,0 +1,13 @@
+Major changes in release 3.4:
+* cp -p and mv preserve setuid and setgid bits
+* chown works on systems where sizeof(uid_t) != sizeof(int) 
+  or sizeof(uid) != sizeof(gid)
+* catch errors from spurious slashes at ends of arguments
+\f
+Major changes in release 3.3:
+* df sped up by not calling sync for every filesystem
+* df ported to AIX (RS/6000 and PS/2), and SVR2 port fixed
+* df -i now also prints the total number of inodes per filesystem
+* ls sped up by not reading symlink contents unnecessarily
+* du doesn't die on POSIX systems when the root filesystem is NFS mounted
+* cp and mv report chown Permission denied errors when run by root
diff --git a/src/chgrp.c b/src/chgrp.c
new file mode 100644 (file)
index 0000000..aeb638f
--- /dev/null
@@ -0,0 +1,275 @@
+/* chgrp -- change group ownership of files
+   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 David MacKenzie <djm@gnu.ai.mit.edu>. */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <grp.h>
+#include <getopt.h>
+#include "system.h"
+
+#ifndef _POSIX_VERSION
+struct group *getgrnam ();
+#endif
+
+#ifdef _POSIX_SOURCE
+#define endgrent()
+#endif
+
+int lstat ();
+
+char *savedir ();
+char *xmalloc ();
+char *xrealloc ();
+int change_file_group ();
+int change_dir_group ();
+int isnumber ();
+void describe_change ();
+void error ();
+void parse_group ();
+void usage ();
+
+/* The name the program was run with. */
+char *program_name;
+
+/* If nonzero, change the ownership of directories recursively. */
+int recurse;
+
+/* If nonzero, force silence (no error messages). */
+int force_silent;
+
+/* If nonzero, describe the files we process. */
+int verbose;
+
+/* If nonzero, describe only owners or groups that change. */
+int changes_only;
+
+/* The name of the group to which ownership of the files is being given. */
+char *groupname;
+
+struct option long_options[] =
+{
+  {"recursive", 0, 0, 'R'},
+  {"changes", 0, 0, 'c'},
+  {"silent", 0, 0, 'f'},
+  {"quiet", 0, 0, 'f'},
+  {"verbose", 0, 0, 'v'},
+  {0, 0, 0, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int group;
+  int errors = 0;
+  int optc;
+
+  program_name = argv[0];
+  recurse = force_silent = verbose = changes_only = 0;
+
+  while ((optc = getopt_long (argc, argv, "Rcfv", long_options, (int *) 0))
+        != EOF)
+    {
+      switch (optc)
+       {
+       case 'R':
+         recurse = 1;
+         break;
+       case 'c':
+         verbose = 1;
+         changes_only = 1;
+         break;
+       case 'f':
+         force_silent = 1;
+         break;
+       case 'v':
+         verbose = 1;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (optind >= argc - 1)
+    usage ();
+
+  parse_group (argv[optind++], &group);
+
+  for (; optind < argc; ++optind)
+    errors |= change_file_group (argv[optind], group);
+
+  exit (errors);
+}
+
+/* Set *G according to NAME. */
+
+void
+parse_group (name, g)
+     char *name;
+     int *g;
+{
+  struct group *grp;
+
+  groupname = name;
+  if (*name == '\0')
+    error (1, 0, "can not change to null group");
+
+  grp = getgrnam (name);
+  if (grp == NULL)
+    {
+      if (!isnumber (name))
+       error (1, 0, "invalid group `%s'", name);
+      *g = atoi (name);
+    }
+  else
+    *g = grp->gr_gid;
+  endgrent ();         /* Save a file descriptor. */
+}
+
+/* Change the ownership of FILE to GID GROUP.
+   If it is a directory and -R is given, recurse.
+   Return 0 if successful, 1 if errors occurred. */
+
+int
+change_file_group (file, group)
+     char *file;
+     int group;
+{
+  struct stat file_stats;
+  int errors = 0;
+
+  if (lstat (file, &file_stats))
+    {
+      if (force_silent == 0)
+       error (0, errno, "%s", file);
+      return 1;
+    }
+
+  if (group != file_stats.st_gid)
+    {
+      if (verbose)
+       describe_change (file, 1);
+      if (chown (file, file_stats.st_uid, group))
+       {
+         if (force_silent == 0)
+           error (0, errno, "%s", file);
+         errors = 1;
+       }
+    }
+  else if (verbose && changes_only == 0)
+    describe_change (file, 0);
+
+  if (recurse && S_ISDIR (file_stats.st_mode))
+    errors |= change_dir_group (file, group, &file_stats);
+  return errors;
+}
+
+/* Recursively change the ownership of the files in directory DIR
+   to GID GROUP.
+   STATP points to the results of lstat on DIR.
+   Return 0 if successful, 1 if errors occurred. */
+
+int
+change_dir_group (dir, group, statp)
+     char *dir;
+     int group;
+     struct stat *statp;
+{
+  char *name_space, *namep;
+  char *path;                  /* Full path of each entry to process. */
+  unsigned dirlength;          /* Length of `dir' and '\0'. */
+  unsigned filelength;         /* Length of each pathname to process. */
+  unsigned pathlength;         /* Bytes allocated for `path'. */
+  int errors = 0;
+
+  errno = 0;
+  name_space = savedir (dir, statp->st_size);
+  if (name_space == NULL)
+    {
+      if (errno)
+       {
+         if (force_silent == 0)
+           error (0, errno, "%s", dir);
+         return 1;
+       }
+      else
+       error (1, 0, "virtual memory exhausted");
+    }
+
+  dirlength = strlen (dir) + 1;        /* + 1 is for the trailing '/'. */
+  pathlength = dirlength + 1;
+  /* Give `path' a dummy value; it will be reallocated before first use. */
+  path = xmalloc (pathlength);
+  strcpy (path, dir);
+  path[dirlength - 1] = '/';
+
+  for (namep = name_space; *namep; namep += filelength - dirlength)
+    {
+      filelength = dirlength + strlen (namep) + 1;
+      if (filelength > pathlength)
+       {
+         pathlength = filelength * 2;
+         path = xrealloc (path, pathlength);
+       }
+      strcpy (path + dirlength, namep);
+      errors |= change_file_group (path, group);
+    }
+  free (path);
+  free (name_space);
+  return errors;
+}
+
+/* Tell the user the group name to which ownership of FILE
+   has been given; if CHANGED is zero, FILE was that group already. */
+
+void
+describe_change (file, changed)
+     char *file;
+     int changed;
+{
+  if (changed)
+    printf ("group of %s changed to %s\n", file, groupname);
+  else
+    printf ("group of %s retained as %s\n", file, groupname);
+}
+
+/* Return nonzero if STR represents an unsigned decimal integer,
+   otherwise return 0. */
+
+int
+isnumber (str)
+     char *str;
+{
+  for (; *str; str++)
+    if (!isdigit (*str))
+      return 0;
+  return 1;
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-Rcfv] [--recursive] [--changes] [--silent] [--quiet]\n\
+       [--verbose] group file...\n",
+          program_name);
+  exit (1);
+}
diff --git a/src/chmod.c b/src/chmod.c
new file mode 100644 (file)
index 0000000..1d44c38
--- /dev/null
@@ -0,0 +1,268 @@
+/* chmod -- change permission modes of files
+   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.  */
+
+/* Options:
+   -R  Recursively change modes of directory contents.
+   -c  Verbosely describe only files whose modes actually change.
+   -f  Do not print error messages about files.
+   -v  Verbosely describe changed modes.
+
+   David MacKenzie <djm@gnu.ai.mit.edu> */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "modechange.h"
+#include "system.h"
+
+int lstat ();
+
+char *savedir ();
+char *xmalloc ();
+char *xrealloc ();
+int change_file_mode ();
+int change_dir_mode ();
+void describe_change ();
+void error ();
+void mode_string ();
+void usage ();
+
+/* The name the program was run with. */
+char *program_name;
+
+/* If nonzero, change the modes of directories recursively. */
+int recurse;
+
+/* If nonzero, force silence (no error messages). */
+int force_silent;
+
+/* If nonzero, describe the modes we set. */
+int verbose;
+
+/* If nonzero, describe only modes that change. */
+int changes_only;
+
+/* Parse the ASCII mode given on the command line into a linked list
+   of `struct mode_change' and apply that to each file argument. */
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  struct mode_change *changes;
+  int errors = 0;
+  int modeind = 0;             /* Index of the mode argument in `argv'. */
+  int thisind;
+  int c;
+
+  program_name = argv[0];
+  recurse = force_silent = verbose = changes_only = 0;
+
+  while (1)
+    {
+      thisind = optind ? optind : 1;
+
+      c = getopt (argc, argv, "RcfvrwxXstugoa,+-=");
+      if (c == EOF)
+       break;
+
+      switch (c)
+       {
+       case 'r':
+       case 'w':
+       case 'x':
+       case 'X':
+       case 's':
+       case 't':
+       case 'u':
+       case 'g':
+       case 'o':
+       case 'a':
+       case ',':
+       case '+':
+       case '-':
+       case '=':
+         if (modeind != 0 && modeind != thisind)
+           error (1, 0, "invalid mode");
+         modeind = thisind;
+         break;
+       case 'R':
+         recurse = 1;
+         break;
+       case 'c':
+         verbose = 1;
+         changes_only = 1;
+         break;
+       case 'f':
+         force_silent = 1;
+         break;
+       case 'v':
+         verbose = 1;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (modeind == 0)
+    modeind = optind++;
+  if (optind >= argc)
+    usage ();
+
+  changes = mode_compile (argv[modeind],
+                         MODE_MASK_EQUALS | MODE_MASK_PLUS | MODE_MASK_MINUS);
+  if (changes == MODE_INVALID)
+    error (1, 0, "invalid mode");
+  else if (changes == MODE_MEMORY_EXHAUSTED)
+    error (1, 0, "virtual memory exhausted");
+
+  for (; optind < argc; ++optind)
+    errors |= change_file_mode (argv[optind], changes);
+
+  exit (errors);
+}
+
+/* Change the mode of FILE according to the list of operations CHANGES.
+   Return 0 if successful, 1 if errors occurred. */
+
+int
+change_file_mode (file, changes)
+     char *file;
+     struct mode_change *changes;
+{
+  struct stat file_stats;
+  unsigned short newmode;
+  int errors = 0;
+
+  if (lstat (file, &file_stats))
+    {
+      if (force_silent == 0)
+       error (0, errno, "%s", file);
+      return 1;
+    }
+#ifdef S_ISLNK
+  if (S_ISLNK (file_stats.st_mode))
+    return 0;
+#endif
+
+  newmode = mode_adjust (file_stats.st_mode, changes);
+
+  if (newmode != (file_stats.st_mode & 07777))
+    {
+      if (verbose)
+       describe_change (file, newmode, 1);
+      if (chmod (file, (int) newmode))
+       {
+         if (force_silent == 0)
+           error (0, errno, "%s", file);
+         errors = 1;
+       }
+    }
+  else if (verbose && changes_only == 0)
+    describe_change (file, newmode, 0);
+
+  if (recurse && S_ISDIR (file_stats.st_mode))
+    errors |= change_dir_mode (file, changes, &file_stats);
+  return errors;
+}
+
+/* Recursively change the modes of the files in directory DIR
+   according to the list of operations CHANGES.
+   STATP points to the results of lstat on DIR.
+   Return 0 if successful, 1 if errors occurred. */
+
+int
+change_dir_mode (dir, changes, statp)
+     char *dir;
+     struct mode_change *changes;
+     struct stat *statp;
+{
+  char *name_space, *namep;
+  char *path;                  /* Full path of each entry to process. */
+  unsigned dirlength;          /* Length of DIR and '\0'. */
+  unsigned filelength;         /* Length of each pathname to process. */
+  unsigned pathlength;         /* Bytes allocated for `path'. */
+  int errors = 0;
+
+  errno = 0;
+  name_space = savedir (dir, statp->st_size);
+  if (name_space == NULL)
+    {
+      if (errno)
+       {
+         if (force_silent == 0)
+           error (0, errno, "%s", dir);
+         return 1;
+       }
+      else
+       error (1, 0, "virtual memory exhausted");
+    }
+
+  dirlength = strlen (dir) + 1;        /* + 1 is for the trailing '/'. */
+  pathlength = dirlength + 1;
+  /* Give `path' a dummy value; it will be reallocated before first use. */
+  path = xmalloc (pathlength);
+  strcpy (path, dir);
+  path[dirlength - 1] = '/';
+
+  for (namep = name_space; *namep; namep += filelength - dirlength)
+    {
+      filelength = dirlength + strlen (namep) + 1;
+      if (filelength > pathlength)
+       {
+         pathlength = filelength * 2;
+         path = xrealloc (path, pathlength);
+       }
+      strcpy (path + dirlength, namep);
+      errors |= change_file_mode (path, changes);
+    }
+  free (path);
+  free (name_space);
+  return errors;
+}
+
+/* Tell the user the mode MODE that file FILE has been set to;
+   if CHANGED is zero, FILE had that mode already. */
+
+void
+describe_change (file, mode, changed)
+     char *file;
+     unsigned short mode;
+     int changed;
+{
+  char perms[11];              /* "-rwxrwxrwx" ls-style modes. */
+
+  mode_string (mode, perms);
+  perms[10] = '\0';            /* `mode_string' does not null terminate. */
+  if (changed)
+    printf ("mode of %s changed to %04o (%s)\n",
+           file, mode & 07777, &perms[1]);
+  else
+    printf ("mode of %s retained as %04o (%s)\n",
+           file, mode & 07777, &perms[1]);
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-Rcfv] mode file...\n\
+       mode is [ugoa...][[+-=][rwxXstugo...]...][,...] or octal number\n",
+          program_name);
+  exit (1);
+}
diff --git a/src/chown.c b/src/chown.c
new file mode 100644 (file)
index 0000000..2bc6987
--- /dev/null
@@ -0,0 +1,271 @@
+/* chown -- change user and group ownership of files
+   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.  */
+
+/* 
+              |                      user
+              | unchanged                 explicit
+ -------------|-------------------------+-------------------------|
+ g unchanged  | ---                     | chown u                |
+ r            |-------------------------+-------------------------|
+ o explicit   | chgrp g or chown .g     | chown u.g              |
+ u            |-------------------------+-------------------------|
+ p from passwd| ---                    | chown u.                |
+              |-------------------------+-------------------------|
+
+   Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <getopt.h>
+#include "system.h"
+
+#ifndef _POSIX_VERSION
+struct passwd *getpwnam ();
+struct group *getgrnam ();
+struct group *getgrgid ();
+#endif
+
+#ifdef _POSIX_SOURCE
+#define endgrent()
+#define endpwent()
+#endif
+
+int lstat ();
+
+char *parse_user_spec ();
+char *savedir ();
+char *xmalloc ();
+char *xrealloc ();
+int change_file_owner ();
+int change_dir_owner ();
+int isnumber ();
+void describe_change ();
+void error ();
+void usage ();
+
+/* The name the program was run with. */
+char *program_name;
+
+/* If nonzero, change the ownership of directories recursively. */
+int recurse;
+
+/* If nonzero, force silence (no error messages). */
+int force_silent;
+
+/* If nonzero, describe the files we process. */
+int verbose;
+
+/* If nonzero, describe only owners or groups that change. */
+int changes_only;
+
+/* The name of the user to which ownership of the files is being given. */
+char *username;
+
+/* The name of the group to which ownership of the files is being given. */
+char *groupname;
+
+struct option long_options[] =
+{
+  {"recursive", 0, 0, 'R'},
+  {"changes", 0, 0, 'c'},
+  {"silent", 0, 0, 'f'},
+  {"quiet", 0, 0, 'f'},
+  {"verbose", 0, 0, 'v'},
+  {0, 0, 0, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  uid_t user = -1;             /* New uid; -1 if not to be changed. */
+  gid_t group = -1;            /* New gid; -1 if not to be changed. */
+  int errors = 0;
+  int optc;
+  char *e;
+
+  program_name = argv[0];
+  recurse = force_silent = verbose = changes_only = 0;
+
+  while ((optc = getopt_long (argc, argv, "Rcfv", long_options, (int *) 0))
+        != EOF)
+    {
+      switch (optc)
+       {
+       case 'R':
+         recurse = 1;
+         break;
+       case 'c':
+         verbose = 1;
+         changes_only = 1;
+         break;
+       case 'f':
+         force_silent = 1;
+         break;
+       case 'v':
+         verbose = 1;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (optind >= argc - 1)
+    usage ();
+
+  e = parse_user_spec (argv[optind], &user, &group, &username, &groupname);
+  if (e)
+    error (1, 0, "%s: %s", argv[optind], e);
+  if (username == NULL)
+    username = "";
+
+  for (++optind; optind < argc; ++optind)
+    errors |= change_file_owner (argv[optind], user, group);
+
+  exit (errors);
+}
+
+/* Change the ownership of FILE to UID USER and GID GROUP.
+   If it is a directory and -R is given, recurse.
+   Return 0 if successful, 1 if errors occurred. */
+
+int
+change_file_owner (file, user, group)
+     char *file;
+     uid_t user;
+     gid_t group;
+{
+  struct stat file_stats;
+  uid_t newuser;
+  gid_t newgroup;
+  int errors = 0;
+
+  if (lstat (file, &file_stats))
+    {
+      if (force_silent == 0)
+       error (0, errno, "%s", file);
+      return 1;
+    }
+
+  newuser = user == (uid_t) -1 ? file_stats.st_uid : user;
+  newgroup = group == (gid_t) -1 ? file_stats.st_gid : group;
+  if (newuser != file_stats.st_uid || newgroup != file_stats.st_gid)
+    {
+      if (verbose)
+       describe_change (file, 1);
+      if (chown (file, newuser, newgroup))
+       {
+         if (force_silent == 0)
+           error (0, errno, "%s", file);
+         errors = 1;
+       }
+    }
+  else if (verbose && changes_only == 0)
+    describe_change (file, 0);
+
+  if (recurse && S_ISDIR (file_stats.st_mode))
+    errors |= change_dir_owner (file, user, group, &file_stats);
+  return errors;
+}
+
+/* Recursively change the ownership of the files in directory DIR
+   to UID USER and GID GROUP.
+   STATP points to the results of lstat on DIR.
+   Return 0 if successful, 1 if errors occurred. */
+
+int
+change_dir_owner (dir, user, group, statp)
+     char *dir;
+     uid_t user;
+     gid_t group;
+     struct stat *statp;
+{
+  char *name_space, *namep;
+  char *path;                  /* Full path of each entry to process. */
+  unsigned dirlength;          /* Length of `dir' and '\0'. */
+  unsigned filelength;         /* Length of each pathname to process. */
+  unsigned pathlength;         /* Bytes allocated for `path'. */
+  int errors = 0;
+
+  errno = 0;
+  name_space = savedir (dir, statp->st_size);
+  if (name_space == NULL)
+    {
+      if (errno)
+       {
+         if (force_silent == 0)
+           error (0, errno, "%s", dir);
+         return 1;
+       }
+      else
+       error (1, 0, "virtual memory exhausted");
+    }
+
+  dirlength = strlen (dir) + 1;        /* + 1 is for the trailing '/'. */
+  pathlength = dirlength + 1;
+  /* Give `path' a dummy value; it will be reallocated before first use. */
+  path = xmalloc (pathlength);
+  strcpy (path, dir);
+  path[dirlength - 1] = '/';
+
+  for (namep = name_space; *namep; namep += filelength - dirlength)
+    {
+      filelength = dirlength + strlen (namep) + 1;
+      if (filelength > pathlength)
+       {
+         pathlength = filelength * 2;
+         path = xrealloc (path, pathlength);
+       }
+      strcpy (path + dirlength, namep);
+      errors |= change_file_owner (path, user, group);
+    }
+  free (path);
+  free (name_space);
+  return errors;
+}
+
+/* Tell the user the user and group names to which ownership of FILE
+   has been given; if CHANGED is zero, FILE had those owners already. */
+
+void
+describe_change (file, changed)
+     char *file;
+     int changed;
+{
+  if (changed)
+    printf ("owner of %s changed to ", file);
+  else
+    printf ("owner of %s retained as ", file);
+  if (groupname)
+    printf ("%s.%s\n", username, groupname);
+  else
+    printf ("%s\n", username);
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-Rcfv] [--recursive] [--changes] [--silent] [--quiet]\n\
+       [--verbose] [user][:.][group] file...\n",
+          program_name);
+  exit (1);
+}
diff --git a/src/cp-hash.c b/src/cp-hash.c
new file mode 100644 (file)
index 0000000..a0afcfc
--- /dev/null
@@ -0,0 +1,217 @@
+/* cp-hash.c  -- file copying (hash search routines)
+   Copyright (C) 1989, 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 Torbjorn Granlund, Sweden (tege@sics.se). */
+
+#include <stdio.h>
+#include "cp.h"
+
+char *hash_insert ();
+char *hash_insert2 ();
+
+struct htab *htab;
+char new_file;
+
+/* Add PATH to the list of files that we have created.
+   Return 0 if successful, 1 if not. */
+
+int
+remember_created (path)
+     char *path;
+{
+  struct stat sb;
+
+  if (stat (path, &sb) < 0)
+    {
+      error (0, errno, "%s", path);
+      return 1;
+    }
+
+  hash_insert (sb.st_ino, sb.st_dev, &new_file);
+  return 0;
+}
+
+/* Add path NODE, copied from inode number INO and device number DEV,
+   to the list of files we have copied.
+   Return NULL if inserted, otherwise non-NULL. */
+
+char *
+remember_copied (node, ino, dev)
+     char *node;
+     ino_t ino;
+     dev_t dev;
+{
+  return hash_insert (ino, dev, node);
+}
+
+/* Allocate space for the hash structures, and set the global
+   variable `htab' to point to it.  The initial hash module is specified in
+   MODULUS, and the number of entries are specified in ENTRY_TAB_SIZE.  (The
+   hash structure will be rebuilt when ENTRY_TAB_SIZE entries have been
+   inserted, and MODULUS and ENTRY_TAB_SIZE in the global `htab' will be
+   doubled.)  */
+
+void
+hash_init (modulus, entry_tab_size)
+     unsigned modulus;
+     unsigned entry_tab_size;
+{
+  struct htab *htab_r;
+
+  htab_r = (struct htab *)
+    xmalloc (sizeof (struct htab) + sizeof (struct entry *) * modulus);
+
+  htab_r->entry_tab = (struct entry *)
+    xmalloc (sizeof (struct entry) * entry_tab_size);
+
+  htab_r->modulus = modulus;
+  htab_r->entry_tab_size = entry_tab_size;
+  htab = htab_r;
+
+  forget_all ();
+}
+
+/* Reset the hash structure in the global variable `htab' to
+   contain no entries.  */
+
+void
+forget_all ()
+{
+  int i;
+  struct entry **p;
+
+  htab->first_free_entry = 0;
+
+  p = htab->hash;
+  for (i = htab->modulus; i > 0; i--)
+    *p++ = NULL;
+}
+\f
+/* Insert path NODE, copied from inode number INO and device number DEV,
+   into the hash structure in the global variable `htab', if an entry with
+   the same inode and device was not found already.
+   Return NULL if inserted, otherwise non-NULL. */
+
+char *
+hash_insert (ino, dev, node)
+     ino_t ino;
+     dev_t dev;
+     char *node;
+{
+  struct htab *htab_r = htab;
+
+  if (htab_r->first_free_entry >= htab_r->entry_tab_size)
+    {
+      int i;
+      struct entry *ep;
+      unsigned modulus;
+      unsigned entry_tab_size;
+
+      /* Increase the number of hash entries, and re-hash the data.
+        The method of shrinking and increasing is made to compactify
+        the heap.  If twice as much data would be allocated
+        straightforwardly, we would never re-use a byte of memory.  */
+
+      /* Let htab shrink.  Keep only the header, not the pointer vector.  */
+
+      htab_r = (struct htab *)
+       xrealloc ((char *) htab_r, sizeof (struct htab));
+
+      modulus = 2 * htab_r->modulus;
+      entry_tab_size = 2 * htab_r->entry_tab_size;
+
+      /* Increase the number of possible entries.  */
+
+      htab_r->entry_tab = (struct entry *)
+       xrealloc ((char *) htab_r->entry_tab,
+                 sizeof (struct entry) * entry_tab_size);
+
+      /* Increase the size of htab again.  */
+
+      htab_r = (struct htab *)
+       xrealloc ((char *) htab_r,
+                 sizeof (struct htab) + sizeof (struct entry *) * modulus);
+
+      htab_r->modulus = modulus;
+      htab_r->entry_tab_size = entry_tab_size;
+      htab = htab_r;
+
+      i = htab_r->first_free_entry;
+
+      /* Make the increased hash table empty.  The entries are still
+        available in htab->entry_tab.  */
+
+      forget_all ();
+
+      /* Go through the entries and install them in the pointer vector
+        htab->hash.  The items are actually inserted in htab->entry_tab at
+        the position where they already are.  The htab->coll_link need
+        however be updated.  Could be made a little more efficient.  */
+
+      for (ep = htab_r->entry_tab; i > 0; i--)
+       {
+         hash_insert2 (htab_r, ep->ino, ep->dev, ep->node);
+         ep++;
+       }
+    }
+
+  return hash_insert2 (htab_r, ino, dev, node);
+}
+
+/* Insert path NODE, copied from inode number INO and device number DEV,
+   into the hash structure HTAB, if not already present.
+   Return NULL if inserted, otherwise non-NULL. */
+
+char *
+hash_insert2 (htab, ino, dev, node)
+     struct htab *htab;
+     ino_t ino;
+     dev_t dev;
+     char *node;
+{
+  struct entry **hp, *ep2, *ep;
+  hp = &htab->hash[ino % htab->modulus];
+  ep2 = *hp;
+
+  /* Collision?  */
+
+  if (ep2 != NULL)
+    {
+      ep = ep2;
+
+      /* Search for an entry with the same data.  */
+
+      do
+       {
+         if (ep->ino == ino && ep->dev == dev)
+           return ep->node;    /* Found an entry with the same data.  */
+         ep = ep->coll_link;
+       }
+      while (ep != NULL);
+
+      /* Did not find it.  */
+
+    }
+
+  ep = *hp = &htab->entry_tab[htab->first_free_entry++];
+  ep->ino = ino;
+  ep->dev = dev;
+  ep->node = node;
+  ep->coll_link = ep2;         /* ep2 is NULL if not collision.  */
+
+  return NULL;
+}
diff --git a/src/cp.c b/src/cp.c
new file mode 100644 (file)
index 0000000..d717344
--- /dev/null
+++ b/src/cp.c
@@ -0,0 +1,1226 @@
+/* cp.c  -- file copying (main routines)
+   Copyright (C) 1989, 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 Torbjorn Granlund, David MacKenzie, and Jim Meyering. */
+
+#ifdef _AIX
+ #pragma alloca
+#endif
+#include <stdio.h>
+#include <getopt.h>
+#include "cp.h"
+#include "backupfile.h"
+
+#ifndef _POSIX_VERSION
+uid_t geteuid ();
+#endif
+
+/* Used by do_copy, make_path, and re_protect
+   to keep a list of leading directories whose protections
+   need to be fixed after copying. */
+struct dir_attr
+{
+  int is_new_dir;
+  int slash_offset;
+  struct dir_attr *next;
+};
+
+char *dirname ();
+enum backup_type get_version ();
+int eaccess_stat ();
+
+static int make_path ();
+static int re_protect ();
+
+/* Initial number of entries in each hash table entry's table of inodes.  */
+#define INITIAL_HASH_MODULE 100
+
+/* Initial number of entries in the inode hash table.  */
+#define INITIAL_ENTRY_TAB_SIZE 70
+
+/* A pointer to either lstat or stat, depending on
+   whether dereferencing of symlinks is done.  */
+int (*xstat) ();
+
+/* The invocation name of this program.  */
+char *program_name;
+
+/* If nonzero, copy all files except directories and, if not dereferencing
+   them, symbolic links, as if they were regular files. */
+int flag_copy_as_regular = 1;
+
+/* If nonzero, dereference symbolic links (copy the files they point to). */
+int flag_dereference = 1;
+
+/* If nonzero, remove existing destination nondirectories. */
+int flag_force = 0;
+
+/* If nonzero, create hard links instead of copying files.
+   Create destination directories as usual. */
+int flag_hard_link = 0;
+
+/* If nonzero, query before overwriting existing destinations
+   with regular files. */
+int flag_interactive = 0;
+
+/* If nonzero, the command "cp x/e_file e_dir" uses "e_dir/x/e_file"
+   as its destination instead of the usual "e_dir/e_file." */
+int flag_path = 0;
+
+/* If nonzero, give the copies the original files' permissions,
+   ownership, and timestamps. */
+int flag_preserve = 0;
+
+/* If nonzero, copy directories recursively and copy special files
+   as themselves rather than copying their contents. */
+int flag_recursive = 0;
+
+/* If nonzero, create symbolic links instead of copying files.
+   Create destination directories as usual. */
+int flag_symbolic_link = 0;
+
+/* If nonzero, when copying recursively, skip any subdirectories that are
+   on different filesystems from the one we started on. */
+int flag_one_file_system = 0;
+
+/* If nonzero, do not copy a nondirectory that has an existing destination
+   with the same or newer modification time. */
+int flag_update = 0;
+
+/* If nonzero, display the names of the files before copying them. */
+int flag_verbose = 0;
+
+/* The error code to return to the system. */
+int exit_status = 0;
+
+/* The bits to preserve in created files' modes. */
+int umask_kill;
+
+/* This process's effective user ID.  */
+uid_t myeuid;
+
+struct option long_opts[] =
+{
+  {"archive", 0, NULL, 'a'},
+  {"backup", 0, NULL, 'b'},
+  {"force", 0, NULL, 'f'},
+  {"interactive", 0, NULL, 'i'},
+  {"link", 0, NULL, 'l'},
+  {"no-dereference", 0, &flag_dereference, 0},
+  {"one-file-system", 0, &flag_one_file_system, 1},
+  {"path", 0, &flag_path, 1},
+  {"preserve", 0, &flag_preserve, 1},
+  {"recursive", 0, NULL, 'R'},
+  {"suffix", 1, NULL, 'S'},
+  {"symbolic-link", 0, NULL, 's'},
+  {"update", 0, &flag_update, 1},
+  {"verbose", 0, &flag_verbose, 1},
+  {"version-control", 1, NULL, 'V'},
+  {NULL, 0, NULL, 0}
+};
+\f
+void
+main (argc, argv)
+     int argc;
+     char *argv[];
+{
+  int c;
+  int make_backups = 0;
+  char *version;
+
+  program_name = argv[0];
+  myeuid = geteuid ();
+
+  version = getenv ("SIMPLE_BACKUP_SUFFIX");
+  if (version)
+    simple_backup_suffix = version;
+  version = getenv ("VERSION_CONTROL");
+
+  /* Find out the current file creation mask, to knock the right bits
+     when using chmod.  The creation mask is set to to be liberal, so
+     that created directories can be written, even if it would not
+     have been allowed with the mask this process was started with.  */
+
+  umask_kill = 0777777 ^ umask (0);
+
+  while ((c = getopt_long (argc, argv, "abdfilprsuvxPRS:V:", long_opts,
+                          (int *) 0)) != EOF)
+    {
+      switch (c)
+       {
+       case 0:
+         break;
+
+       case 'a':               /* Like -dpR. */
+         flag_dereference = 0;
+         flag_preserve = 1;
+         flag_recursive = 1;
+         flag_copy_as_regular = 0;
+         break;
+
+       case 'b':
+         make_backups = 1;
+         break;
+
+       case 'd':
+         flag_dereference = 0;
+         break;
+
+       case 'f':
+         flag_force = 1;
+         flag_interactive = 0;
+         break;
+
+       case 'i':
+         flag_force = 0;
+         flag_interactive = 1;
+         break;
+
+       case 'l':
+         flag_hard_link = 1;
+         break;
+
+       case 'p':
+         flag_preserve = 1;
+         break;
+
+       case 'P':
+         flag_path = 1;
+         break;
+
+       case 'r':
+         flag_recursive = 1;
+         flag_copy_as_regular = 1;
+         break;
+
+       case 'R':
+         flag_recursive = 1;
+         flag_copy_as_regular = 0;
+         break;
+
+       case 's':
+#ifdef S_ISLNK
+         flag_symbolic_link = 1;
+#else
+         error (0, 0, "symbolic links not supported; making hard links");
+         flag_hard_link = 1;
+#endif
+         break;
+
+       case 'u':
+         flag_update = 1;
+         break;
+
+       case 'v':
+         flag_verbose = 1;
+         break;
+
+       case 'x':
+         flag_one_file_system = 1;
+         break;
+
+       case 'S':
+         simple_backup_suffix = optarg;
+         break;
+
+       case 'V':
+         version = optarg;
+         break;
+
+       default:
+         usage ((char *) 0);
+       }
+    }
+
+  if (flag_hard_link && flag_symbolic_link)
+    usage ("cannot make both hard and symbolic links");
+
+  if (make_backups)
+    backup_type = get_version (version);
+
+  if (flag_preserve == 1)
+    umask_kill = 0777777;
+
+  /* The key difference between -d (--no-dereference) and not is the version
+     of `stat' to call.  */
+
+  if (flag_dereference)
+    xstat = stat;
+  else
+    xstat = lstat;
+
+  /* Allocate space for remembering copied and created files.  */
+
+  hash_init (INITIAL_HASH_MODULE, INITIAL_ENTRY_TAB_SIZE);
+
+  exit_status |= do_copy (argc, argv);
+
+  exit (exit_status);
+}
+\f
+/* Scan the arguments, and copy each by calling copy.
+   Return 0 if successful, 1 if any errors occur. */
+
+int
+do_copy (argc, argv)
+     int argc;
+     char *argv[];
+{
+  char *dest;
+  struct stat sb;
+  int new_dst = 0;
+  int ret = 0;
+
+  if (optind >= argc)
+    usage ("missing file arguments");
+  if (optind >= argc - 1)
+    usage ("missing file argument");
+
+  dest = argv[argc - 1];
+
+  if (lstat (dest, &sb))
+    {
+      if (errno != ENOENT)
+       {
+         error (0, errno, "%s", dest);
+         return 1;
+       }
+      else
+       new_dst = 1;
+    }
+  else
+    {
+      struct stat sbx;
+
+      /* If `dest' is not a symlink to a nonexistent file, use
+        the results of stat instead of lstat, so we can copy files
+        into symlinks to directories. */
+      if (stat (dest, &sbx) == 0)
+       sb = sbx;
+    }
+
+  if (!new_dst && S_ISDIR (sb.st_mode))
+    {
+      /* cp file1...filen edir
+        Copy the files `file1' through `filen'
+        to the existing directory `edir'. */
+
+      for (;;)
+       {
+         char *arg;
+         char *ap;
+         char *dst_path;
+         int parent_exists = 1; /* True if dirname (dst_path) exists. */
+         struct dir_attr *attr_list;
+
+         arg = argv[optind];
+
+         strip_trailing_slashes (arg);
+
+         if (flag_path)
+           {
+             /* Append all of `arg' to `dest'.  */
+             dst_path = xmalloc (strlen (dest) + strlen (arg) + 2);
+             stpcpy (stpcpy (stpcpy (dst_path, dest), "/"), arg);
+
+             /* For --path, we have to make sure that the directory
+                dirname (dst_path) exists.  We may have to create a few
+                leading directories. */
+             parent_exists = !make_path (dst_path,
+                                         strlen (dest) + 1, 0700,
+                                         flag_verbose ? "%s -> %s\n" :
+                                         (char *) NULL,
+                                         &attr_list, &new_dst);
+           }
+         else
+           {
+             /* Append the last component of `arg' to `dest'.  */
+
+             ap = basename (arg);
+             /* For `cp -R source/.. dest', don't copy into `dest/..'. */
+             if (!strcmp (ap, ".."))
+               dst_path = dest;
+             else
+               {
+                 dst_path = xmalloc (strlen (dest) + strlen (ap) + 2);
+                 stpcpy (stpcpy (stpcpy (dst_path, dest), "/"), ap);
+               }
+           }
+
+         if (!parent_exists)
+           {
+             /* make_path failed, so we shouldn't even attempt the copy. */
+             ret = 1;
+           }
+         else
+           {
+             ret |= copy (arg, dst_path, new_dst, 0, (struct dir_list *) 0);
+             forget_all ();
+  
+             if (flag_path)
+               {
+                 ret |= re_protect (dst_path, strlen (dest) + 1,
+                                       attr_list);
+               }
+           }
+
+         ++optind;
+         if (optind == argc - 1)
+           break;
+       }
+      return ret;
+    }
+  else if (argc - optind == 2)
+    {
+      if (flag_path)
+       usage ("when preserving paths, last argument must be a directory");
+      return copy (argv[optind], dest, new_dst, 0, (struct dir_list *) 0);
+    }
+  else
+    usage ("when copying multiple files, last argument must be a directory");
+}
+\f
+/* Copy the file SRC_PATH to the file DST_PATH.  The files may be of
+   any type.  NEW_DST should be non-zero if the file DST_PATH cannot
+   exist because its parent directory was just created; NEW_DST should
+   be zero if DST_PATH might already exist.  DEVICE is the device
+   number of the parent directory, or 0 if the parent of this file is
+   not known.  ANCESTORS points to a linked, null terminated list of
+   devices and inodes of parent directories of SRC_PATH.
+   Return 0 if successful, 1 if an error occurs. */
+
+int
+copy (src_path, dst_path, new_dst, device, ancestors)
+     char *src_path;
+     char *dst_path;
+     int new_dst;
+     dev_t device;
+     struct dir_list *ancestors;
+{
+  struct stat src_sb;
+  struct stat dst_sb;
+  int src_mode;
+  int src_type;
+  char *earlier_file;
+  char *dst_backup = NULL;
+  int fix_mode = 0;
+
+  if ((*xstat) (src_path, &src_sb))
+    {
+      error (0, errno, "%s", src_path);
+      return 1;
+    }
+
+  /* Are we crossing a file system boundary?  */
+  if (flag_one_file_system && device != 0 && device != src_sb.st_dev)
+    return 0;
+
+  /* We wouldn't insert a node unless nlink > 1, except that we need to
+     find created files so as to not copy infinitely if a directory is
+     copied into itself.  */
+
+  earlier_file = remember_copied (dst_path, src_sb.st_ino, src_sb.st_dev);
+
+  /* Did we just create this file?  */
+
+  if (earlier_file == &new_file)
+    return 0;
+
+  src_mode = src_sb.st_mode;
+  src_type = src_sb.st_mode;
+
+  if (S_ISDIR (src_type) && !flag_recursive)
+    {
+      error (0, 0, "%s: omitting directory", src_path);
+      return 1;
+    }
+
+  if (!new_dst)
+    {
+      if ((*xstat) (dst_path, &dst_sb))
+       {
+         if (errno != ENOENT)
+           {
+             error (0, errno, "%s", dst_path);
+             return 1;
+           }
+         else
+           new_dst = 1;
+       }
+      else
+       {
+         /* The file exists already.  */
+
+         if (src_sb.st_ino == dst_sb.st_ino && src_sb.st_dev == dst_sb.st_dev)
+           {
+             if (flag_hard_link)
+               return 0;
+
+             error (0, 0, "`%s' and `%s' are the same file",
+                    src_path, dst_path);
+             return 1;
+           }
+
+         if (!S_ISDIR (src_type))
+           {
+             if (S_ISDIR (dst_sb.st_mode))
+               {
+                 error (0, 0,
+                        "%s: cannot overwrite directory with non-directory",
+                        dst_path);
+                 return 1;
+               }
+
+             if (flag_update && src_sb.st_mtime <= dst_sb.st_mtime)
+               return 0;
+           }
+
+         if (S_ISREG (src_type) && !flag_force)
+           {
+             if (flag_interactive)
+               {
+                 if (eaccess_stat (&dst_sb, W_OK) != 0)
+                   fprintf (stderr,
+                            "%s: overwrite `%s', overriding mode %04o? ",
+                            program_name, dst_path, dst_sb.st_mode & 07777);
+                 else
+                   fprintf (stderr, "%s: overwrite `%s'? ",
+                            program_name, dst_path);
+                 if (!yesno ())
+                   return 0;
+               }
+           }
+
+         if (backup_type != none && !S_ISDIR (dst_sb.st_mode))
+           {
+             char *tmp_backup = find_backup_file_name (dst_path);
+             if (tmp_backup == NULL)
+               error (1, 0, "virtual memory exhausted");
+             dst_backup = alloca (strlen (tmp_backup) + 1);
+             strcpy (dst_backup, tmp_backup);
+             free (tmp_backup);
+             if (rename (dst_path, dst_backup))
+               {
+                 if (errno != ENOENT)
+                   {
+                     error (0, errno, "cannot backup `%s'", dst_path);
+                     return 1;
+                   }
+                 else
+                   dst_backup = NULL;
+               }
+             new_dst = 1;
+           }
+         else if (flag_force)
+           {
+             if (S_ISDIR (dst_sb.st_mode))
+               {
+                 /* Temporarily change mode to allow overwriting. */
+                 if (eaccess_stat (&dst_sb, W_OK | X_OK) != 0)
+                   {
+                     if (chmod (dst_path, 0700))
+                       {
+                         error (0, errno, "%s", dst_path);
+                         return 1;
+                       }
+                     else
+                       fix_mode = 1;
+                   }
+               }
+             else
+               {
+                 if (unlink (dst_path) && errno != ENOENT)
+                   {
+                     error (0, errno, "cannot remove old link to `%s'",
+                            dst_path);
+                     return 1;
+                   }
+                 new_dst = 1;
+               }
+           }
+       }
+    }
+
+  /* If the source is a directory, we don't always create the destination
+     directory.  So --verbose should not announce anything until we're
+     sure we'll create a directory. */
+  if (flag_verbose && !S_ISDIR (src_type))
+    printf ("%s -> %s\n", src_path, dst_path);
+
+  /* Did we copy this inode somewhere else (in this command line argument)
+     and therefore this is a second hard link to the inode?  */
+
+  if (!flag_dereference && src_sb.st_nlink > 1 && earlier_file)
+    {
+      if (link (earlier_file, dst_path))
+       {
+         error (0, errno, "%s", dst_path);
+         goto un_backup;
+       }
+      return 0;
+    }
+
+  if (S_ISDIR (src_type))
+    {
+      struct dir_list *dir;
+
+      /* If this directory has been copied before during the
+         recursion, there is a symbolic link to an ancestor
+         directory of the symbolic link.  It is impossible to
+         continue to copy this, unless we've got an infinite disk.  */
+
+      if (is_ancestor (&src_sb, ancestors))
+       {
+         error (0, 0, "%s: cannot copy cyclic symbolic link", src_path);
+         goto un_backup;
+       }
+
+      /* Insert the current directory in the list of parents.  */
+
+      dir = (struct dir_list *) alloca (sizeof (struct dir_list));
+      dir->parent = ancestors;
+      dir->ino = src_sb.st_ino;
+      dir->dev = src_sb.st_dev;
+
+      if (new_dst || !S_ISDIR (dst_sb.st_mode))
+       {
+         /* Create the new directory writable and searchable, so
+             we can create new entries in it.  */
+
+         if (mkdir (dst_path, (src_mode & umask_kill) | 0700))
+           {
+             error (0, errno, "cannot create directory `%s'", dst_path);
+             goto un_backup;
+           }
+
+         /* Insert the created directory's inode and device
+             numbers into the search structure, so that we can
+             avoid copying it again.  */
+
+         if (remember_created (dst_path))
+           goto un_backup;
+
+         if (flag_verbose)
+           printf ("%s -> %s\n", src_path, dst_path);
+       }
+
+      /* Copy the contents of the directory.  */
+
+      if (copy_dir (src_path, dst_path, new_dst, &src_sb, dir))
+       return 1;
+    }
+#ifdef S_ISLNK
+  else if (flag_symbolic_link)
+    {
+      if (*src_path == '/'
+         || (!strncmp (dst_path, "./", 2) && index (dst_path + 2, '/') == 0)
+         || index (dst_path, '/') == 0)
+       {
+         if (symlink (src_path, dst_path))
+           {
+             error (0, errno, "%s", dst_path);
+             goto un_backup;
+           }
+         return 0;
+       }
+      else
+       {
+         error (0, 0,
+                "%s: can only make relative symbolic links in current directory", dst_path);
+         goto un_backup;
+       }
+    }
+#endif
+  else if (flag_hard_link)
+    {
+      if (link (src_path, dst_path))
+       {
+         error (0, errno, "cannot create link `%s'", dst_path);
+         goto un_backup;
+       }
+      return 0;
+    }
+  else if (S_ISREG (src_type)
+          || (flag_copy_as_regular && !S_ISDIR (src_type)
+#ifdef S_ISLNK
+              && !S_ISLNK (src_type)
+#endif
+              ))
+    {
+      if (copy_reg (src_path, dst_path))
+       goto un_backup;
+    }
+  else
+#ifdef S_ISFIFO
+  if (S_ISFIFO (src_type))
+    {
+      if (mkfifo (dst_path, src_mode & umask_kill))
+       {
+         error (0, errno, "cannot create fifo `%s'", dst_path);
+         goto un_backup;
+       }
+    }
+  else
+#endif
+    if (S_ISBLK (src_type) || S_ISCHR (src_type)
+#ifdef S_ISSOCK
+       || S_ISSOCK (src_type)
+#endif
+       )
+    {
+      if (mknod (dst_path, src_mode & umask_kill, src_sb.st_rdev))
+       {
+         error (0, errno, "cannot create special file `%s'", dst_path);
+         goto un_backup;
+       }
+    }
+  else
+#ifdef S_ISLNK
+#ifdef _AIX
+#define LINK_BUF PATH_MAX
+#else
+#define LINK_BUF src_sb.st_size
+#endif
+  if (S_ISLNK (src_type))
+    {
+      char *link_val = (char *) alloca (LINK_BUF + 1);
+      int link_size;
+
+      link_size = readlink (src_path, link_val, LINK_BUF);
+      if (link_size < 0)
+       {
+         error (0, errno, "cannot read symbolic link `%s'", src_path);
+         goto un_backup;
+       }
+      link_val[link_size] = '\0';
+
+      if (symlink (link_val, dst_path))
+       {
+         error (0, errno, "cannot create symbolic link `%s'", dst_path);
+         goto un_backup;
+       }
+      return 0;
+    }
+  else
+#endif
+    {
+      error (0, 0, "%s: unknown file type", src_path);
+      goto un_backup;
+    }
+
+  /* Adjust the times (and if possible, ownership) for the copy.
+     chown turns off set[ug]id bits for non-root,
+     so do the chmod last.  */
+
+  if (flag_preserve)
+    {
+      struct utimbuf utb;
+
+      utb.actime = src_sb.st_atime;
+      utb.modtime = src_sb.st_mtime;
+
+      if (utime (dst_path, &utb))
+       {
+         error (0, errno, "%s", dst_path);
+         return 1;
+       }
+
+      /* If non-root uses -p, it's ok if we can't preserve ownership.
+        But root probably wants to know, e.g. if NFS disallows it.  */
+      if (chown (dst_path, src_sb.st_uid, src_sb.st_gid)
+         && (errno != EPERM || myeuid == 0))
+       {
+         error (0, errno, "%s", dst_path);
+         return 1;
+       }
+    }
+
+  if ((flag_preserve || new_dst)
+      && (flag_copy_as_regular || S_ISREG (src_type) || S_ISDIR (src_type)))
+    {
+      if (chmod (dst_path, src_mode & umask_kill))
+       {
+         error (0, errno, "%s", dst_path);
+         return 1;
+       }
+    }
+  else if (fix_mode)
+    {
+      /* Reset the temporarily changed mode.  */
+      if (chmod (dst_path, dst_sb.st_mode))
+       {
+         error (0, errno, "%s", dst_path);
+         return 1;
+       }
+    }
+
+  return 0;
+
+un_backup:
+  if (dst_backup)
+    {
+      if (rename (dst_backup, dst_path))
+       error (0, errno, "cannot un-backup `%s'", dst_path);
+    }
+  return 1;
+}
+\f
+/* Ensure that the parent directory of CONST_DIRPATH exists, for
+   the --path option.
+
+   SRC_OFFSET is the index in CONST_DIRPATH (which is a destination
+   path) of the beginning of the source directory name.
+   Create any leading directories that don't already exist,
+   giving them permissions MODE.
+   If VERBOSE_FMT_STRING is nonzero, use it as a printf format
+   string for printing a message after successfully making a directory.
+   The format should take two string arguments: the names of the
+   source and destination directories.
+   Creates a linked list of attributes of intermediate directories,
+   *ATTR_LIST, for re_protect to use after calling copy.
+   Sets *NEW_DST to 1 if this function creates parent of CONST_DIRPATH.
+
+   Return 0 if parent of CONST_DIRPATH exists as a directory with the proper
+   permissions when done, otherwise 1. */
+
+static int
+make_path (const_dirpath, src_offset, mode, verbose_fmt_string,
+             attr_list, new_dst)
+     char *const_dirpath;
+     int src_offset;
+     int mode;
+     char *verbose_fmt_string;
+     struct dir_attr **attr_list;
+     int *new_dst;
+{
+  struct stat stats;
+  char *dirpath;               /* A copy of CONST_DIRPATH we can change. */
+  char *src;                   /* Source name in `dirpath'. */
+  char *tmp_dst_dirname;       /* Leading path of `dirpath', malloc. */
+  char *dst_dirname;           /* Leading path of `dirpath', alloca. */
+
+  dirpath = alloca (strlen (const_dirpath) + 1);
+  strcpy (dirpath, const_dirpath);
+
+  src = dirpath + src_offset;
+
+  tmp_dst_dirname = dirname (dirpath); 
+  dst_dirname = alloca (strlen (tmp_dst_dirname) + 1);
+  strcpy (dst_dirname, tmp_dst_dirname);
+  free (tmp_dst_dirname);
+
+  *attr_list = NULL;
+
+  if ((*xstat) (dst_dirname, &stats))
+    {
+      /* Parent of CONST_DIRNAME does not exist.
+        Make all missing intermediate directories. */
+      char *slash;
+
+      slash = src;
+      while (*slash == '/')
+       slash++;
+      while (slash = index (slash, '/'))
+       {
+         /* Add this directory to the list of directories whose modes need
+            fixing later. */
+         struct dir_attr *new =
+           (struct dir_attr *) xmalloc (sizeof (struct dir_attr));
+         new->slash_offset = slash - dirpath;
+         new->next = *attr_list;
+         *attr_list = new;
+
+         *slash = '\0';
+         if ((*xstat) (dirpath, &stats))
+           {
+             /* This element of the path does not exist.  We must set
+                *new_dst and new->is_new_dir inside this loop because,
+                for example, in the command `cp --path ../a/../b/c e_dir',
+                make_path creates only e_dir/../a if ./b already exists. */
+             *new_dst = 1;
+             new->is_new_dir = 1;
+             if (mkdir (dirpath, mode))
+               {
+                 error (0, errno, "cannot make directory `%s'", dirpath);
+                 return 1;
+               }
+             else
+               {
+                 if (verbose_fmt_string != NULL)
+                   printf (verbose_fmt_string, src, dirpath);
+               }
+           }
+         else if (!S_ISDIR (stats.st_mode))
+           {
+             error (0, 0, "`%s' exists but is not a directory", dirpath);
+             return 1;
+           }
+         else
+           {
+             new->is_new_dir = 0;
+             *new_dst = 0;
+           }
+         *slash++ = '/';
+
+         /* Avoid unnecessary calls to `stat' when given
+            pathnames containing multiple adjacent slashes.  */
+         while (*slash == '/')
+           slash++;
+       }
+    }
+
+  /* We get here if the parent of `dirpath' already exists. */
+
+  else if (!S_ISDIR (stats.st_mode))
+    {
+      error (0, 0, "`%s' exists but is not a directory", dst_dirname);
+      return 1;
+    }
+  else if (chmod (dst_dirname, mode))
+    {
+      error (0, errno, "%s", dst_dirname);
+      return 1;
+    }
+  else
+    {
+      *new_dst = 0;
+    }
+  return 0;
+}
+\f
+/* Ensure that the parent directories of CONST_DST_PATH have the
+   correct protections, for the --path option.  This is done
+   after all copying has been completed, to allow permissions
+   that don't include user write/execute.
+
+   SRC_OFFSET is the index in CONST_DST_PATH of the beginning of the
+   source directory name.
+
+   ATTR_LIST is a null-terminated linked list of structures that
+   indicates the end of the filename of each intermediate directory
+   in CONST_DST_PATH that may need to have its attributes changed.
+   The command `cp --path --preserve a/b/c d/e_dir' changes the
+   attributes of the directories d/e_dir/a and d/e_dir/a/b to match
+   the corresponding source directories regardless of whether they
+   existed before the `cp' command was given.
+
+   Return 0 if the parent of CONST_DST_PATH and any intermediate
+   directories specified by ATTR_LIST have the proper permissions
+   when done, otherwise 1. */
+
+static int
+re_protect (const_dst_path, src_offset, attr_list)
+     char *const_dst_path;
+     int src_offset;
+     struct dir_attr *attr_list;
+{
+  struct dir_attr *p;
+  char *dst_path;              /* A copy of CONST_DST_PATH we can change. */
+  char *src_path;              /* The source name in `dst_path'. */
+
+  dst_path = alloca (strlen (const_dst_path) + 1);
+  strcpy (dst_path, const_dst_path);
+  src_path = dst_path + src_offset; 
+
+  for (p = attr_list; p; p = p->next)
+    {
+      struct stat src_sb;
+
+      dst_path[p->slash_offset] = '\0';
+
+      if ((*xstat) (src_path, &src_sb))
+       {
+         error (0, errno, "%s", src_path);
+         return 1;
+       }
+
+      /* Adjust the times (and if possible, ownership) for the copy.
+        chown turns off set[ug]id bits for non-root,
+        so do the chmod last.  */
+
+      if (flag_preserve)
+       {
+         struct utimbuf utb;
+
+         utb.actime = src_sb.st_atime;
+         utb.modtime = src_sb.st_mtime;
+
+         if (utime (dst_path, &utb))
+           {
+             error (0, errno, "%s", dst_path);
+             return 1;
+           }
+
+         /* If non-root uses -p, it's ok if we can't preserve ownership.
+            But root probably wants to know, e.g. if NFS disallows it.  */
+         if (chown (dst_path, src_sb.st_uid, src_sb.st_gid)
+             && (errno != EPERM || myeuid == 0))
+           {
+             error (0, errno, "%s", dst_path);
+             return 1;
+           }
+       }
+
+      if (flag_preserve || p->is_new_dir)
+       {
+         if (chmod (dst_path, src_sb.st_mode & umask_kill))
+           {
+             error (0, errno, "%s", dst_path);
+             return 1;
+           }
+       }
+
+      dst_path[p->slash_offset] = '/';
+    }
+  return 0;
+}
+\f
+/* Read the contents of the directory SRC_PATH_IN, and recursively
+   copy the contents to DST_PATH_IN.  NEW_DST is non-zero if
+   DST_PATH_IN is a directory that was created previously in the
+   recursion.   SRC_SB and ANCESTORS describe SRC_PATH_IN.
+   Return 0 if successful, -1 if an error occurs. */
+
+int
+copy_dir (src_path_in, dst_path_in, new_dst, src_sb, ancestors)
+     char *src_path_in;
+     char *dst_path_in;
+     int new_dst;
+     struct stat *src_sb;
+     struct dir_list *ancestors;
+{
+  char *name_space;
+  char *namep;
+  char *src_path;
+  char *dst_path;
+  int ret = 0;
+
+  errno = 0;
+  name_space = savedir (src_path_in, src_sb->st_size);
+  if (name_space == 0)
+    {
+      if (errno)
+       {
+         error (0, errno, "%s", src_path_in);
+         return -1;
+       }
+      else
+       error (1, 0, "virtual memory exhausted");
+    }
+
+  namep = name_space;
+  while (*namep != '\0')
+    {
+      int fn_length = strlen (namep) + 1;
+
+      dst_path = xmalloc (strlen (dst_path_in) + fn_length + 1);
+      src_path = xmalloc (strlen (src_path_in) + fn_length + 1);
+
+      stpcpy (stpcpy (stpcpy (src_path, src_path_in), "/"), namep);
+      stpcpy (stpcpy (stpcpy (dst_path, dst_path_in), "/"), namep);
+
+      ret |= copy (src_path, dst_path, new_dst, src_sb->st_dev, ancestors);
+
+      /* Free the memory for `src_path'.  The memory for `dst_path'
+        cannot be deallocated, since it is used to create multiple
+        hard links.  */
+
+      free (src_path);
+
+      namep += fn_length;
+    }
+  free (name_space);
+  return -ret;
+}
+\f
+/* Copy a regular file from SRC_PATH to DST_PATH.
+   If the source file contains holes, copies holes and blocks of zeros
+   in the source file as holes in the destination file.
+   (Holes are read as zeroes by the `read' system call.)
+   Return 0 if successful, -1 if an error occurred. */
+
+int
+copy_reg (src_path, dst_path)
+     char *src_path;
+     char *dst_path;
+{
+  char *buf;
+  int buf_size;
+  int dest_desc;
+  int source_desc;
+  int n_read;
+  int n_written;
+  struct stat sb;
+  char *cp;
+  int *ip;
+  int return_val = 0;
+  long n_read_total = 0;
+  int last_write_made_hole = 0;
+  int make_holes = 0;
+
+  source_desc = open (src_path, O_RDONLY);
+  if (source_desc < 0)
+    {
+      error (0, errno, "%s", src_path);
+      return -1;
+    }
+
+  /* Create the new regular file with small permissions initially,
+     to not create a security hole.  */
+
+  dest_desc = open (dst_path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+  if (dest_desc < 0)
+    {
+      error (0, errno, "cannot create regular file `%s'", dst_path);
+      return_val = -1;
+      goto ret2;
+    }
+
+  /* Find out the optimal buffer size.  */
+
+  if (fstat (dest_desc, &sb))
+    {
+      error (0, errno, "%s", dst_path);
+      return_val = -1;
+      goto ret;
+    }
+
+  buf_size = ST_BLKSIZE (sb);
+
+#ifdef HAVE_ST_BLOCKS
+  if (S_ISREG (sb.st_mode))
+    {
+      /* Find out whether the file contains any sparse blocks. */
+
+      if (fstat (source_desc, &sb))
+       {
+         error (0, errno, "%s", src_path);
+         return_val = -1;
+         goto ret;
+       }
+
+      /* If the file has fewer blocks than would normally
+        be needed for a file of its size, then
+        at least one of the blocks in the file is a hole. */
+      if (S_ISREG (sb.st_mode) &&
+         sb.st_size - (sb.st_blocks * DEV_BSIZE) >= DEV_BSIZE)
+       make_holes = 1;
+    }
+#endif
+
+  /* Make a buffer with space for a sentinel at the end.  */
+
+  buf = (char *) alloca (buf_size + sizeof (int));
+
+  for (;;)
+    {
+      n_read = read (source_desc, buf, buf_size);
+      if (n_read < 0)
+       {
+         error (0, errno, "%s", src_path);
+         return_val = -1;
+         goto ret;
+       }
+      if (n_read == 0)
+       break;
+
+      n_read_total += n_read;
+
+      ip = 0;
+      if (make_holes)
+       {
+         buf[n_read] = 1;      /* Sentinel to stop loop.  */
+
+         /* Find first non-zero *word*, or the word with the sentinel.  */
+
+         ip = (int *) buf;
+         while (*ip++ == 0)
+           ;
+
+         /* Find the first non-zero *byte*, or the sentinel.  */
+
+         cp = (char *) (ip - 1);
+         while (*cp++ == 0)
+           ;
+
+         /* If we found the sentinel, the whole input block was zero,
+            and we can make a hole.  */
+
+         if (cp > buf + n_read)
+           {
+             /* Make a hole.  */
+             if (lseek (dest_desc, (off_t) n_read, SEEK_CUR) < 0L)
+               {
+                 error (0, errno, "%s", dst_path);
+                 return_val = -1;
+                 goto ret;
+               }
+             last_write_made_hole = 1;
+           }
+         else
+           /* Clear to indicate that a normal write is needed. */
+           ip = 0;
+       }
+      if (ip == 0)
+       {
+         n_written = write (dest_desc, buf, n_read);
+         if (n_written < n_read)
+           {
+             error (0, errno, "%s", dst_path);
+             return_val = -1;
+             goto ret;
+           }
+         last_write_made_hole = 0;
+       }
+    }
+
+  /* If the file ends with a `hole', something needs to be written at
+     the end.  Otherwise the kernel would truncate the file at the end
+     of the last write operation.  */
+
+  if (last_write_made_hole)
+    {
+#ifdef HAVE_FTRUNCATE
+      /* Write a null character and truncate it again.  */
+      if (write (dest_desc, "", 1) != 1
+         || ftruncate (dest_desc, n_read_total) < 0)
+#else
+      /* Seek backwards one character and write a null.  */
+      if (lseek (dest_desc, (off_t) -1, SEEK_CUR) < 0L
+         || write (dest_desc, "", 1) != 1)
+#endif
+       {
+         error (0, errno, "%s", dst_path);
+         return_val = -1;
+       }
+    }
+
+ret:
+  if (close (dest_desc) < 0)
+    {
+      error (0, errno, "%s", dst_path);
+      return_val = -1;
+    }
+ret2:
+  if (close (source_desc) < 0)
+    {
+      error (0, errno, "%s", src_path);
+      return_val = -1;
+    }
+
+  return return_val;
+}
diff --git a/src/dd.c b/src/dd.c
new file mode 100644 (file)
index 0000000..dd56068
--- /dev/null
+++ b/src/dd.c
@@ -0,0 +1,1020 @@
+/* dd -- convert a file while copying it.
+   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.  */
+
+/* Written by Paul Rubin, David MacKenzie, and Stuart Kemp. */
+
+/* Options:
+
+   Numbers can be followed by a multiplier:
+   b=512, k=1024, w=2, xm=number m
+
+   if=FILE                     Read from FILE instead of stdin.
+   of=FILE                     Write to FILE instead of stdout; don't
+                               truncate FILE.
+   ibs=BYTES                   Read BYTES bytes at a time.
+   obs=BYTES                   Write BYTES bytes at a time.
+   bs=BYTES                    Override ibs and obs.
+   cbs=BYTES                   Convert BYTES bytes at a time.
+   skip=BLOCKS                 Skip BLOCKS ibs-sized blocks at
+                               start of input.
+   seek=BLOCKS                 Skip BLOCKS obs-sized blocks at
+                               start of output.
+   count=BLOCKS                        Copy only BLOCKS input blocks.
+   conv=CONVERSION[,CONVERSION...]
+
+   Conversions:
+   ascii                       Convert EBCDIC to ASCII.
+   ebcdic                      Convert ASCII to EBCDIC.
+   ibm                         Convert ASCII to alternate EBCDIC.
+   block                       Pad newline-terminated records to size of
+                               cbs, replacing newline with trailing spaces.
+   unblock                     Replace trailing spaces in cbs-sized block
+                               with newline.
+   lcase                       Change uppercase characters to lowercase.
+   ucase                       Change lowercase characters to uppercase.
+   swab                                Swap every pair of input bytes.
+                               Unlike the Unix dd, this works when an odd
+                               number of bytes are read.
+   noerror                     Continue after read errors.
+   sync                                Pad every input block to size of ibs with
+                               trailing NULs. */
+
+#include <stdio.h>
+#include <ctype.h>
+#ifdef STDC_HEADERS
+#define ISLOWER islower
+#define ISUPPER isupper
+#else
+#define ISLOWER(c) (isascii ((c)) && islower ((c)))
+#define ISUPPER(c) (isascii ((c)) && isupper ((c)))
+#endif
+#include <sys/types.h>
+#include <signal.h>
+#include "system.h"
+
+#define equal(p, q) (strcmp ((p),(q)) == 0)
+#define max(a, b) ((a) > (b) ? (a) : (b))
+#define output_char(c) \
+  do { \
+  obuf[oc++] = (c); if (oc >= output_blocksize) write_output (); \
+  } while (0)
+
+/* Default input and output blocksize. */
+#define DEFAULT_BLOCKSIZE 512
+
+/* Conversions bit masks. */
+#define C_ASCII 01
+#define C_EBCDIC 02
+#define C_IBM 04
+#define C_BLOCK 010
+#define C_UNBLOCK 020
+#define C_LCASE 040
+#define C_UCASE 0100
+#define C_SWAB 0200
+#define C_NOERROR 0400
+#define C_NOTRUNC 01000
+#define C_SYNC 02000
+/* Use separate input and output buffers, and combine partial input blocks. */
+#define C_TWOBUFS 04000
+
+char *xmalloc ();
+RETSIGTYPE interrupt_handler ();
+int bit_count ();
+int parse_integer ();
+void apply_translations ();
+void copy ();
+void copy_simple ();
+void copy_with_block ();
+void copy_with_unblock ();
+void error ();
+void parse_conversion ();
+void print_stats ();
+void translate_charset ();
+void quit ();
+void scanargs ();
+void skip ();
+void usage ();
+void write_output ();
+
+/* The name this program was run with. */
+char *program_name;
+
+/* The name of the input file, or NULL for the standard input. */
+char *input_file = NULL;
+
+/* The input file descriptor. */
+int input_fd = 0;
+
+/* The name of the output file, or NULL for the standard output. */
+char *output_file = NULL;
+
+/* The output file descriptor. */
+int output_fd = 1;
+
+/* The number of bytes in which atomic reads are done. */
+long input_blocksize = -1;
+
+/* The number of bytes in which atomic writes are done. */
+long output_blocksize = -1;
+
+/* Conversion buffer size, in bytes.  0 prevents conversions. */
+long conversion_blocksize = 0;
+
+/* Skip this many records of `input_blocksize' bytes before input. */
+long skip_records = 0;
+
+/* Skip this many records of `output_blocksize' bytes before output. */
+long seek_record = 0;
+
+/* Copy only this many records.  <0 means no limit. */
+int max_records = -1;
+
+/* Bit vector of conversions to apply. */
+int conversions_mask = 0;
+
+/* If nonzero, filter characters through the translation table.  */
+int translation_needed = 0;
+
+/* Number of partial blocks written. */
+unsigned w_partial = 0;
+
+/* Number of full blocks written. */
+unsigned w_full = 0;
+
+/* Number of partial blocks read. */
+unsigned r_partial = 0;
+
+/* Number of full blocks read. */
+unsigned r_full = 0;
+
+/* Records truncated by conv=block. */
+unsigned r_truncate = 0;
+
+/* Output representation of newline and space characters.
+   They change if we're converting to EBCDIC.  */
+unsigned char newline_character = '\n';
+unsigned char space_character = ' ';
+
+struct conversion
+{
+  char *convname;
+  int conversion;
+};
+
+struct conversion conversions[] =
+{
+  "ascii", C_ASCII | C_TWOBUFS,        /* EBCDIC to ASCII. */
+  "ebcdic", C_EBCDIC | C_TWOBUFS,      /* ASCII to EBCDIC. */
+  "ibm", C_IBM | C_TWOBUFS,    /* Slightly different ASCII to EBCDIC. */
+  "block", C_BLOCK | C_TWOBUFS,        /* Variable to fixed length records. */
+  "unblock", C_UNBLOCK | C_TWOBUFS,    /* Fixed to variable length records. */
+  "lcase", C_LCASE | C_TWOBUFS,        /* Translate upper to lower case. */
+  "ucase", C_UCASE | C_TWOBUFS,        /* Translate lower to upper case. */
+  "swab", C_SWAB | C_TWOBUFS,  /* Swap bytes of input. */
+  "noerror", C_NOERROR,                /* Ignore i/o errors. */
+  "notrunc", C_NOTRUNC,                /* Do not truncate output file. */
+  "sync", C_SYNC,              /* Pad input records to ibs with NULs. */
+  NULL, 0
+};
+
+/* Translation table formed by applying successive transformations. */
+unsigned char trans_table[256];
+
+unsigned char ascii_to_ebcdic[] =
+{
+  0, 01, 02, 03, 067, 055, 056, 057,
+  026, 05, 045, 013, 014, 015, 016, 017,
+  020, 021, 022, 023, 074, 075, 062, 046,
+  030, 031, 077, 047, 034, 035, 036, 037,
+  0100, 0117, 0177, 0173, 0133, 0154, 0120, 0175,
+  0115, 0135, 0134, 0116, 0153, 0140, 0113, 0141,
+  0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367,
+  0370, 0371, 0172, 0136, 0114, 0176, 0156, 0157,
+  0174, 0301, 0302, 0303, 0304, 0305, 0306, 0307,
+  0310, 0311, 0321, 0322, 0323, 0324, 0325, 0326,
+  0327, 0330, 0331, 0342, 0343, 0344, 0345, 0346,
+  0347, 0350, 0351, 0112, 0340, 0132, 0137, 0155,
+  0171, 0201, 0202, 0203, 0204, 0205, 0206, 0207,
+  0210, 0211, 0221, 0222, 0223, 0224, 0225, 0226,
+  0227, 0230, 0231, 0242, 0243, 0244, 0245, 0246,
+  0247, 0250, 0251, 0300, 0152, 0320, 0241, 07,
+  040, 041, 042, 043, 044, 025, 06, 027,
+  050, 051, 052, 053, 054, 011, 012, 033,
+  060, 061, 032, 063, 064, 065, 066, 010,
+  070, 071, 072, 073, 04, 024, 076, 0341,
+  0101, 0102, 0103, 0104, 0105, 0106, 0107, 0110,
+  0111, 0121, 0122, 0123, 0124, 0125, 0126, 0127,
+  0130, 0131, 0142, 0143, 0144, 0145, 0146, 0147,
+  0150, 0151, 0160, 0161, 0162, 0163, 0164, 0165,
+  0166, 0167, 0170, 0200, 0212, 0213, 0214, 0215,
+  0216, 0217, 0220, 0232, 0233, 0234, 0235, 0236,
+  0237, 0240, 0252, 0253, 0254, 0255, 0256, 0257,
+  0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267,
+  0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277,
+  0312, 0313, 0314, 0315, 0316, 0317, 0332, 0333,
+  0334, 0335, 0336, 0337, 0352, 0353, 0354, 0355,
+  0356, 0357, 0372, 0373, 0374, 0375, 0376, 0377
+};
+
+unsigned char ascii_to_ibm[] =
+{
+  0, 01, 02, 03, 067, 055, 056, 057,
+  026, 05, 045, 013, 014, 015, 016, 017,
+  020, 021, 022, 023, 074, 075, 062, 046,
+  030, 031, 077, 047, 034, 035, 036, 037,
+  0100, 0132, 0177, 0173, 0133, 0154, 0120, 0175,
+  0115, 0135, 0134, 0116, 0153, 0140, 0113, 0141,
+  0360, 0361, 0362, 0363, 0364, 0365, 0366, 0367,
+  0370, 0371, 0172, 0136, 0114, 0176, 0156, 0157,
+  0174, 0301, 0302, 0303, 0304, 0305, 0306, 0307,
+  0310, 0311, 0321, 0322, 0323, 0324, 0325, 0326,
+  0327, 0330, 0331, 0342, 0343, 0344, 0345, 0346,
+  0347, 0350, 0351, 0255, 0340, 0275, 0137, 0155,
+  0171, 0201, 0202, 0203, 0204, 0205, 0206, 0207,
+  0210, 0211, 0221, 0222, 0223, 0224, 0225, 0226,
+  0227, 0230, 0231, 0242, 0243, 0244, 0245, 0246,
+  0247, 0250, 0251, 0300, 0117, 0320, 0241, 07,
+  040, 041, 042, 043, 044, 025, 06, 027,
+  050, 051, 052, 053, 054, 011, 012, 033,
+  060, 061, 032, 063, 064, 065, 066, 010,
+  070, 071, 072, 073, 04, 024, 076, 0341,
+  0101, 0102, 0103, 0104, 0105, 0106, 0107, 0110,
+  0111, 0121, 0122, 0123, 0124, 0125, 0126, 0127,
+  0130, 0131, 0142, 0143, 0144, 0145, 0146, 0147,
+  0150, 0151, 0160, 0161, 0162, 0163, 0164, 0165,
+  0166, 0167, 0170, 0200, 0212, 0213, 0214, 0215,
+  0216, 0217, 0220, 0232, 0233, 0234, 0235, 0236,
+  0237, 0240, 0252, 0253, 0254, 0255, 0256, 0257,
+  0260, 0261, 0262, 0263, 0264, 0265, 0266, 0267,
+  0270, 0271, 0272, 0273, 0274, 0275, 0276, 0277,
+  0312, 0313, 0314, 0315, 0316, 0317, 0332, 0333,
+  0334, 0335, 0336, 0337, 0352, 0353, 0354, 0355,
+  0356, 0357, 0372, 0373, 0374, 0375, 0376, 0377
+};
+
+unsigned char ebcdic_to_ascii[] =
+{
+  0, 01, 02, 03, 0234, 011, 0206, 0177,
+  0227, 0215, 0216, 013, 014, 015, 016, 017,
+  020, 021, 022, 023, 0235, 0205, 010, 0207,
+  030, 031, 0222, 0217, 034, 035, 036, 037,
+  0200, 0201, 0202, 0203, 0204, 012, 027, 033,
+  0210, 0211, 0212, 0213, 0214, 05, 06, 07,
+  0220, 0221, 026, 0223, 0224, 0225, 0226, 04,
+  0230, 0231, 0232, 0233, 024, 025, 0236, 032,
+  040, 0240, 0241, 0242, 0243, 0244, 0245, 0246,
+  0247, 0250, 0133, 056, 074, 050, 053, 041,
+  046, 0251, 0252, 0253, 0254, 0255, 0256, 0257,
+  0260, 0261, 0135, 044, 052, 051, 073, 0136,
+  055, 057, 0262, 0263, 0264, 0265, 0266, 0267,
+  0270, 0271, 0174, 054, 045, 0137, 076, 077,
+  0272, 0273, 0274, 0275, 0276, 0277, 0300, 0301,
+  0302, 0140, 072, 043, 0100, 047, 075, 042,
+  0303, 0141, 0142, 0143, 0144, 0145, 0146, 0147,
+  0150, 0151, 0304, 0305, 0306, 0307, 0310, 0311,
+  0312, 0152, 0153, 0154, 0155, 0156, 0157, 0160,
+  0161, 0162, 0313, 0314, 0315, 0316, 0317, 0320,
+  0321, 0176, 0163, 0164, 0165, 0166, 0167, 0170,
+  0171, 0172, 0322, 0323, 0324, 0325, 0326, 0327,
+  0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337,
+  0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347,
+  0173, 0101, 0102, 0103, 0104, 0105, 0106, 0107,
+  0110, 0111, 0350, 0351, 0352, 0353, 0354, 0355,
+  0175, 0112, 0113, 0114, 0115, 0116, 0117, 0120,
+  0121, 0122, 0356, 0357, 0360, 0361, 0362, 0363,
+  0134, 0237, 0123, 0124, 0125, 0126, 0127, 0130,
+  0131, 0132, 0364, 0365, 0366, 0367, 0370, 0371,
+  060, 061, 062, 063, 064, 065, 066, 067,
+  070, 071, 0372, 0373, 0374, 0375, 0376, 0377
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+#ifdef _POSIX_VERSION
+  struct sigaction sigact;
+#endif /* _POSIX_VERSION */
+  int i;
+
+  program_name = argv[0];
+
+  /* Initialize translation table to identity translation. */
+  for (i = 0; i < 256; i++)
+    trans_table[i] = i;
+
+  /* Decode arguments. */
+  scanargs (argc, argv);
+  apply_translations ();
+
+  if (input_file != NULL)
+    {
+      input_fd = open (input_file, O_RDONLY);
+      if (input_fd < 0)
+       error (1, errno, "%s", input_file);
+    }
+  else
+    input_file = "standard input";
+
+  if (input_fd == output_fd)
+    error (1, 0, "standard %s is closed", input_fd == 0 ? "input" : "output");
+
+  if (output_file != NULL)
+    {
+      int omode = O_RDWR | O_CREAT;
+
+      if (seek_record == 0 && !(conversions_mask & C_NOTRUNC))
+       omode |= O_TRUNC;
+      output_fd = open (output_file, omode, 0666);
+      if (output_fd < 0)
+       error (1, errno, "%s", output_file);
+#ifdef HAVE_FTRUNCATE
+      if (seek_record > 0 && !(conversions_mask & C_NOTRUNC))
+       {
+         if (ftruncate (output_fd, seek_record * output_blocksize) < 0)
+           error (0, errno, "%s", output_file);
+       }
+#endif
+    }
+  else
+    output_file = "standard output";
+  
+#ifdef _POSIX_VERSION
+  sigaction (SIGINT, NULL, &sigact);
+  if (sigact.sa_handler != SIG_IGN)
+    {
+      sigact.sa_handler = interrupt_handler;
+      sigemptyset (&sigact.sa_mask);
+      sigact.sa_flags = 0;
+      sigaction (SIGINT, &sigact, NULL);
+    }
+#else                          /* !_POSIX_VERSION */
+  if (signal (SIGINT, SIG_IGN) != SIG_IGN)
+    signal (SIGINT, interrupt_handler);
+#endif                         /* !_POSIX_VERSION */
+  copy ();
+}
+
+/* Throw away RECORDS blocks of BLOCKSIZE bytes on file descriptor FDESC,
+   which is open with read permission for FILE.  Store up to BLOCKSIZE
+   bytes of the data at a time in BUF, if necessary. */
+
+void
+skip (fdesc, file, records, blocksize, buf)
+     int fdesc;
+     char *file;
+     long records;
+     long blocksize;
+     char *buf;
+{
+  struct stat stats;
+
+  /* Use fstat instead of checking for errno == ESPIPE because
+     lseek doesn't work on some special files but doesn't return an
+     error, either. */
+  if (fstat (fdesc, &stats))
+    {
+      error (0, errno, "%s", file);
+      quit (1);
+    }
+
+  if (S_ISREG (stats.st_mode))
+    {
+      if (lseek (fdesc, records * blocksize, SEEK_SET) < 0)
+       {
+         error (0, errno, "%s", file);
+         quit (1);
+       }
+    }
+  else
+    {
+      while (records-- > 0)
+       {
+         if (read (fdesc, buf, blocksize) < 0)
+           {
+             error (0, errno, "%s", file);
+             quit (1);
+           }
+         /* FIXME If fewer bytes were read than requested, meaning that
+            EOF was reached, POSIX wants the output file padded with NULs. */
+       }
+    }
+}
+
+/* Apply the character-set translations specified by the user
+   to the NREAD bytes in BUF.  */
+
+void
+translate_buffer (buf, nread)
+     unsigned char *buf;
+     int nread;
+{
+  register unsigned char *cp;
+  register int i;
+
+  for (i = nread, cp = buf; i; i--, cp++)
+    *cp = trans_table[*cp];
+}
+
+/* If nonnzero, the last char from the previous call to `swab_buffer'
+   is saved in `saved_char'.  */
+int char_is_saved = 0;
+
+/* Odd char from previous call.  */
+unsigned char saved_char;
+
+/* Swap NREAD bytes in BUF, plus possibly an initial char from the
+   previous call.  If NREAD is odd, save the last char for the
+   next call.   Return the new start of the BUF buffer.  */
+
+unsigned char *
+swab_buffer (buf, nread)
+     unsigned char *buf;
+     int *nread;
+{
+  unsigned char *bufstart = buf;
+  register unsigned char *cp;
+  register int i;
+
+  /* Is a char left from last time?  */
+  if (char_is_saved)
+    {
+      *--bufstart = saved_char;
+      *nread++;
+      char_is_saved = 0;
+    }
+
+  if (*nread & 1)
+    {
+      /* An odd number of chars are in the buffer.  */
+      saved_char = bufstart[--*nread];
+      char_is_saved = 1;
+    }
+
+  /* Do the byte-swapping by moving every second character two
+     positions toward the end, working from the end of the buffer
+     toward the beginning.  This way we only move half of the data.  */
+
+  cp = bufstart + *nread;      /* Start one char past the last.  */
+  for (i = *nread / 2; i; i--, cp -= 2)
+    *cp = *(cp - 2);
+
+  return ++bufstart;
+}
+
+/* Output buffer. */
+unsigned char *obuf;
+
+/* Current index into `obuf'. */
+int oc = 0;
+
+/* Index into current line, for `conv=block' and `conv=unblock'.  */
+int col = 0;
+
+/* The main loop.  */
+
+void
+copy ()
+{
+  unsigned char *ibuf, *bufstart; /* Input buffer. */
+  int nread;                   /* Bytes read in the current block. */
+  int exit_status = 0;
+
+  /* Leave an extra byte at the beginning and end of `ibuf' for conv=swab.  */
+  ibuf = (unsigned char *) xmalloc (input_blocksize + 2) + 1;
+  if (conversions_mask & C_TWOBUFS)
+    obuf = (unsigned char *) xmalloc (output_blocksize);
+  else
+    obuf = ibuf;
+
+  if (skip_records > 0)
+    skip (input_fd, input_file, skip_records, input_blocksize, ibuf);
+
+  if (seek_record > 0)
+    skip (output_fd, output_file, seek_record, output_blocksize, obuf);
+
+  if (max_records == 0)
+    quit (exit_status);
+
+  while (1)
+    {
+      if (max_records >= 0 && r_partial + r_full >= max_records)
+       break;
+
+      /* Zero the buffer before reading, so that if we get a read error,
+        whatever data we are able to read is followed by zeros.
+        This minimizes data loss. */
+      if ((conversions_mask & C_SYNC) && (conversions_mask & C_NOERROR))
+       bzero (ibuf, input_blocksize);
+
+      nread = read (input_fd, ibuf, input_blocksize);
+
+      if (nread == 0)
+       break;                  /* EOF.  */
+
+      if (nread < 0)
+       {
+         error (0, errno, "%s", input_file);
+         if (conversions_mask & C_NOERROR)
+           {
+             print_stats ();
+             /* Seek past the bad block if possible. */
+             lseek (input_fd, input_blocksize, SEEK_CUR);
+             if (conversions_mask & C_SYNC)
+               /* Replace the missing input with null bytes and
+                  proceed normally.  */
+               nread = 0;
+             else
+               continue;
+           }
+         else
+           {
+             /* Write any partial block. */
+             exit_status = 2;
+             break;
+           }
+       }
+
+      if (nread < input_blocksize)
+       {
+         r_partial++;
+         if (conversions_mask & C_SYNC)
+           {
+             if (!(conversions_mask & C_NOERROR))
+               /* If C_NOERROR, we zeroed the block before reading. */
+               bzero (ibuf + nread, input_blocksize - nread);
+             nread = input_blocksize;
+           }
+       }
+      else
+       r_full++;
+
+      if (ibuf == obuf)                /* If not C_TWOBUFS. */
+       {
+         int nwritten = write (output_fd, obuf, nread);
+         if (nwritten != nread)
+           {
+             error (0, errno, "%s", output_file);
+             if (nwritten > 0)
+               w_partial++;
+             quit (1);
+           }
+         else if (nread == input_blocksize)
+           w_full++;
+         else
+           w_partial++;
+         continue;
+       }
+
+      /* Do any translations on the whole buffer at once.  */
+
+      if (translation_needed)
+       translate_buffer (ibuf, nread);
+
+      if (conversions_mask & C_SWAB)
+       bufstart = swab_buffer (ibuf, &nread);
+      else
+       bufstart = ibuf;
+
+      if (conversions_mask & C_BLOCK)
+        copy_with_block (bufstart, nread);
+      else if (conversions_mask & C_UNBLOCK)
+       copy_with_unblock (bufstart, nread);
+      else
+       copy_simple (bufstart, nread);
+    }
+
+  /* If we have a char left as a result of conv=swab, output it.  */
+  if (char_is_saved)
+    {
+      if (conversions_mask & C_BLOCK)
+        copy_with_block (&saved_char, 1);
+      else if (conversions_mask & C_UNBLOCK)
+       copy_with_unblock (&saved_char, 1);
+      else
+       output_char (saved_char);
+    }
+
+  if ((conversions_mask & C_BLOCK) && col > 0)
+    {
+      /* If the final input line didn't end with a '\n', pad
+        the output block to `conversion_blocksize' chars.  */
+      int pending_spaces = max (0, conversion_blocksize - col);
+      while (pending_spaces--)
+       output_char (space_character);
+    }
+
+  if ((conversions_mask & C_UNBLOCK) && col == conversion_blocksize)
+    /* Add a final '\n' if there are exactly `conversion_blocksize'
+       characters in the final record. */
+    output_char (newline_character);
+
+  /* Write out the last block. */
+  if (oc > 0)
+    {
+      int nwritten = write (output_fd, obuf, oc);
+      if (nwritten > 0)
+       w_partial++;
+      if (nwritten != oc)
+       {
+         error (0, errno, "%s", output_file);
+         quit (1);
+       }
+    }
+
+  free (ibuf - 1);
+  if (obuf != ibuf)
+    free (obuf);
+
+  quit (exit_status);
+}
+
+/* Copy NREAD bytes of BUF, with no conversions.  */
+
+void
+copy_simple (buf, nread)
+     unsigned char *buf;
+     int nread;
+{
+  int nfree;                   /* Number of unused bytes in `obuf'.  */
+  unsigned char *start = buf; /* First uncopied char in BUF.  */
+
+  do
+    {
+      nfree = output_blocksize - oc;
+      if (nfree > nread)
+       nfree = nread;
+
+      bcopy (start, obuf + oc, nfree);
+           
+      nread -= nfree;          /* Update the number of bytes left to copy. */
+      start += nfree;
+      oc += nfree;
+      if (oc >= output_blocksize)
+       write_output ();
+    }
+  while (nread > 0);
+}
+
+/* Copy NREAD bytes of BUF, doing conv=block
+   (pad newline-terminated records to `conversion_blocksize',
+   replacing the newline with trailing spaces).  */
+
+void
+copy_with_block (buf, nread)
+     unsigned char *buf;
+     int nread;
+{
+  register int i;
+
+  for (i = nread; i; i--, buf++)
+    {
+      if (*buf == newline_character)
+       {
+         int pending_spaces = max (0, conversion_blocksize - col);
+         while (pending_spaces--)
+           output_char (space_character);
+         col = 0;
+       }
+      else
+       {
+         if (col == conversion_blocksize)
+           r_truncate++;
+         else if (col < conversion_blocksize)
+           output_char (*buf);
+         col++;
+       }
+    }
+}
+
+/* Copy NREAD bytes of BUF, doing conv=unblock
+   (replace trailing spaces in `conversion_blocksize'-sized records
+   with a newline).  */
+
+void
+copy_with_unblock (buf, nread)
+     unsigned char *buf;
+     int nread;
+{
+  register int i;
+  register unsigned char c;
+  static int pending_spaces = 0;
+
+  for (i = 0; i < nread; i++)
+    {
+      c = buf[i];
+
+      if (col++ >= conversion_blocksize)
+       {
+         col = pending_spaces = 0; /* Wipe out any pending spaces.  */
+         i--;                  /* Push the char back; get it later. */
+         output_char (newline_character);
+       }
+      else if (c == space_character)
+       pending_spaces++;
+      else
+       {
+         if (pending_spaces)
+           {
+             /* `c' is the character after a run of spaces that were not
+                at the end of the conversion buffer.  Output them.  */
+             while (pending_spaces--)
+               output_char (space_character);
+           }
+         output_char (c);
+       }
+    }
+}
+
+/* Write, then empty, the output buffer `obuf'. */
+
+void
+write_output ()
+{
+  int nwritten = write (output_fd, obuf, output_blocksize);
+  if (nwritten != output_blocksize)
+    {
+      error (0, errno, "%s", output_file);
+      if (nwritten > 0)
+       w_partial++;
+      quit (1);
+    }
+  else
+    w_full++;
+  oc = 0;
+}
+
+void
+scanargs (argc, argv)
+     int argc;
+     char **argv;
+{
+  int i, n;
+
+  for (i = 1; i < argc; i++)
+    {
+      char *name, *val;
+
+      name = argv[i];
+      val = index (name, '=');
+      if (val == NULL)
+       usage ("unrecognized option `%s'", name);
+      *val++ = '\0';
+
+      if (equal (name, "if"))
+       input_file = val;
+      else if (equal (name, "of"))
+       output_file = val;
+      else if (equal (name, "conv"))
+       parse_conversion (val);
+      else
+       {
+         n = parse_integer (val);
+         if (n < 0)
+           error (1, 0, "invalid number `%s'", val);
+
+         if (equal (name, "ibs"))
+           {
+             input_blocksize = n;
+             conversions_mask |= C_TWOBUFS;
+           }
+         else if (equal (name, "obs"))
+           {
+             output_blocksize = n;
+             conversions_mask |= C_TWOBUFS;
+           }
+         else if (equal (name, "bs"))
+           output_blocksize = input_blocksize = n;
+         else if (equal (name, "cbs"))
+           conversion_blocksize = n;
+         else if (equal (name, "skip"))
+           skip_records = n;
+         else if (equal (name, "seek"))
+           seek_record = n;
+         else if (equal (name, "count"))
+           max_records = n;
+         else
+           usage ("unrecognized option `%s=%s'", name, val);
+       }
+    }
+
+  /* If bs= was given, both `input_blocksize' and `output_blocksize' will
+     have been set to non-negative values.  If either has not been set,
+     bs= was not given, so make sure two buffers are used. */
+  if (input_blocksize == -1 || output_blocksize == -1)
+    conversions_mask |= C_TWOBUFS;
+  if (input_blocksize == -1)
+    input_blocksize = DEFAULT_BLOCKSIZE;
+  if (output_blocksize == -1)
+    output_blocksize = DEFAULT_BLOCKSIZE;
+  if (conversion_blocksize == 0)
+    conversions_mask &= ~(C_BLOCK | C_UNBLOCK);
+}
+
+/* Return the value of STR, interpreted as a non-negative decimal integer,
+   optionally multiplied by various values.
+   Return -1 if STR does not represent a number in this format. */
+
+int
+parse_integer (str)
+     char *str;
+{
+  register int n = 0;
+  register int temp;
+  register char *p = str;
+
+  while (isdigit (*p))
+    {
+      n = n * 10 + *p - '0';
+      p++;
+    }
+loop:
+  switch (*p++)
+    {
+    case '\0':
+      return n;
+    case 'b':
+      n *= 512;
+      goto loop;
+    case 'k':
+      n *= 1024;
+      goto loop;
+    case 'w':
+      n *= 2;
+      goto loop;
+    case 'x':
+      temp = parse_integer (p);
+      if (temp == -1)
+       return -1;
+      n *= temp;
+      break;
+    default:
+      return -1;
+    }
+  return n;
+}
+
+/* Interpret one "conv=..." option. */
+
+void
+parse_conversion (str)
+     char *str;
+{
+  char *new;
+  int i;
+
+  do
+    {
+      new = index (str, ',');
+      if (new != NULL)
+       *new++ = '\0';
+      for (i = 0; conversions[i].convname != NULL; i++)
+       if (equal (conversions[i].convname, str))
+         {
+           conversions_mask |= conversions[i].conversion;
+           break;
+         }
+      if (conversions[i].convname == NULL)
+       {
+         usage ("%s: invalid conversion", str);
+         exit (1);
+       }
+      str = new;
+  } while (new != NULL);
+}
+
+/* Fix up translation table. */
+
+void
+apply_translations ()
+{
+  int i;
+
+#define MX(a) (bit_count (conversions_mask & (a)))
+  if ((MX (C_ASCII | C_EBCDIC | C_IBM) > 1)
+      || (MX (C_BLOCK | C_UNBLOCK) > 1)
+      || (MX (C_LCASE | C_UCASE) > 1)
+      || (MX (C_UNBLOCK | C_SYNC) > 1))
+    {
+      error (1, 0, "\
+only one conv in {ascii,ebcdic,ibm}, {lcase,ucase}, {block,unblock}, {unblock,sync}");
+    }
+#undef MX
+
+  if (conversions_mask & C_ASCII)
+    translate_charset (ebcdic_to_ascii);
+
+  if (conversions_mask & C_UCASE)
+    {
+      for (i = 0; i < 256; i++)
+       if (ISLOWER (trans_table[i]))
+         trans_table[i] = toupper (trans_table[i]);
+      translation_needed = 1;
+    }
+  else if (conversions_mask & C_LCASE)
+    {
+      for (i = 0; i < 256; i++)
+       if (ISUPPER (trans_table[i]))
+         trans_table[i] = tolower (trans_table[i]);
+      translation_needed = 1;
+    }
+
+  if (conversions_mask & C_EBCDIC)
+    {
+      translate_charset (ascii_to_ebcdic);
+      newline_character = ascii_to_ebcdic['\n'];
+      space_character = ascii_to_ebcdic[' '];
+    }
+  else if (conversions_mask & C_IBM)
+    {
+      translate_charset (ascii_to_ibm);
+      newline_character = ascii_to_ibm['\n'];
+      space_character = ascii_to_ibm[' '];
+    }
+}
+
+void
+translate_charset (new_trans)
+     unsigned char *new_trans;
+{
+  int i;
+
+  for (i = 0; i < 256; i++)
+    trans_table[i] = new_trans[trans_table[i]];
+  translation_needed = 1;
+}
+
+/* Return the number of 1 bits in `i'. */
+
+int
+bit_count (i)
+     register unsigned int i;
+{
+  register int set_bits;
+
+  for (set_bits = 0; i != 0; set_bits++)
+    i &= i - 1;
+  return set_bits;
+}
+
+void
+print_stats ()
+{
+  fprintf (stderr, "%u+%u records in\n", r_full, r_partial);
+  fprintf (stderr, "%u+%u records out\n", w_full, w_partial);
+  if (r_truncate > 0)
+    fprintf (stderr, "%u truncated block%s\n", r_truncate,
+            r_truncate == 1 ? "" : "s");
+}
+
+void
+quit (code)
+     int code;
+{
+  int errcode = code ? code : 1;
+  print_stats ();
+  if (close (input_fd) < 0)
+    error (errcode, errno, "%s", input_file);
+  if (close (output_fd) < 0)
+    error (errcode, errno, "%s", output_file);
+  exit (code);
+}
+
+RETSIGTYPE
+interrupt_handler ()
+{
+  quit (1);
+}
+
+void
+usage (string, arg0, arg1)
+     char *string, *arg0, *arg1;
+{
+  fprintf (stderr, "%s: ", program_name);
+  fprintf (stderr, string, arg0, arg1);
+  fprintf (stderr, "\n");
+  fprintf (stderr, "\
+Usage: %s [if=file] [of=file] [ibs=bytes] [obs=bytes] [bs=bytes] [cbs=bytes]\n\
+       [skip=blocks] [seek=blocks] [count=blocks]\n\
+       [conv={ascii,ebcdic,ibm,block,unblock,lcase,ucase,swab,noerror,notrunc,\n\
+       sync}]\n\
+Numbers can be followed by a multiplier:\n\
+b=512, k=1024, w=2, xm=number m\n",
+          program_name);
+  exit (1);
+}
diff --git a/src/df.c b/src/df.c
new file mode 100644 (file)
index 0000000..e13f1ce
--- /dev/null
+++ b/src/df.c
@@ -0,0 +1,398 @@
+/* df - summarize free disk space
+   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.  */
+
+/* Usage: df [-aikP] [-t fstype] [--all] [--inodes] [--type fstype]
+   [--kilobytes] [--portability] [path...]
+
+   Options:
+   -a, --all           List all filesystems, even zero-size ones.
+   -i, --inodes                List inode usage information instead of block usage.
+   -k, --kilobytes     Print sizes in 1K blocks instead of 512-byte blocks.
+   -P, --portability   Use the POSIX output format (one line per filesystem).
+   -t, --type fstype   Limit the listing to filesystems of type `fstype'.
+                       Multiple -t options can be given.
+                       By default, all filesystem types are listed.
+
+   Written by David MacKenzie <djm@gnu.ai.mit.edu> */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+#include "mountlist.h"
+#include "fsusage.h"
+#include "system.h"
+
+char *strstr ();
+char *xmalloc ();
+char *xstrdup ();
+int fs_to_list ();
+void add_fs_type ();
+void error ();
+void print_header ();
+void show_entry ();
+void show_all_entries ();
+void show_dev ();
+void show_disk ();
+void show_point ();
+void usage ();
+
+/* If nonzero, show inode information. */
+int inode_format;
+
+/* If nonzero, show even filesystems with zero size or
+   uninteresting types. */
+int show_all_fs;
+
+/* If nonzero, use 1K blocks instead of 512-byte blocks. */
+int kilobyte_blocks;
+
+/* If nonzero, use the POSIX output format.  */
+int posix_format;
+
+/* Nonzero if errors have occurred. */
+int exit_status;
+
+/* Name this program was run with. */
+char *program_name;
+
+/* A filesystem type to display. */
+
+struct fs_select
+{
+  char *fs_name;
+  struct fs_select *fs_next;
+};
+
+/* Linked list of filesystem types to display.
+   If `fs_list' is NULL, list all types.
+   This table is generated dynamically from command-line options,
+   rather than hardcoding into the program what it thinks are the
+   valid filesystem types; let the user specify any filesystem type
+   they want to, and if there are any filesystems of that type, they
+   will be shown.
+
+   Some filesystem types:
+   4.2 4.3 ufs nfs swap ignore io vm */
+
+struct fs_select *fs_list;
+
+/* Linked list of mounted filesystems. */
+struct mount_entry *mount_list;
+
+struct option long_options[] =
+{
+  {"all", 0, &show_all_fs, 1},
+  {"inodes", 0, &inode_format, 1},
+  {"kilobytes", 0, &kilobyte_blocks, 1},
+  {"portability", 0, &posix_format, 1},
+  {"type", 1, 0, 't'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int i;
+  struct stat *stats;
+
+  program_name = argv[0];
+  fs_list = NULL;
+  inode_format = 0;
+  show_all_fs = 0;
+  kilobyte_blocks = getenv ("POSIXLY_CORRECT") == 0;
+  posix_format = 0;
+  exit_status = 0;
+
+  while ((i = getopt_long (argc, argv, "aikPt:v", long_options, (int *) 0))
+        != EOF)
+    {
+      switch (i)
+       {
+       case 0:                 /* Long option. */
+         break;
+       case 'a':
+         show_all_fs = 1;
+         break;
+       case 'i':
+         inode_format = 1;
+         break;
+       case 'k':
+         kilobyte_blocks = 1;
+         break;
+       case 'P':
+         posix_format = 1;
+         break;
+       case 't':
+         add_fs_type (optarg);
+         break;
+       case 'v':               /* For SysV compatibility. */
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (optind != argc)
+    {
+      /* Display explicitly requested empty filesystems. */
+      show_all_fs = 1;
+
+      /* stat all the given entries to make sure they get automounted,
+        if necessary, before reading the filesystem table.  */
+      stats = (struct stat *)
+       xmalloc ((argc - optind) * sizeof (struct stat));
+      for (i = optind; i < argc; ++i)
+       if (stat (argv[i], &stats[i - optind]))
+         {
+           error (0, errno, "%s", argv[i]);
+           exit_status = 1;
+           argv[i] = NULL;
+         }
+    }
+
+  mount_list = read_filesystem_list (fs_list != NULL, show_all_fs);
+  if (mount_list == NULL)
+    error (1, errno, "cannot read table of mounted filesystems");
+
+  print_header ();
+  sync ();
+
+  if (optind == argc)
+    show_all_entries ();
+  else
+    for (i = optind; i < argc; ++i)
+      if (argv[i])
+       show_entry (argv[i], &stats[i - optind]);
+
+  exit (exit_status);
+}
+\f
+void
+print_header ()
+{
+  if (inode_format)
+    printf ("Filesystem            Inodes   IUsed   IFree  %%IUsed");
+  else
+    printf ("Filesystem         %s  Used Available Capacity",
+           kilobyte_blocks ? "1024-blocks" : " 512-blocks");
+  printf (" Mounted on\n");
+}
+\f
+/* Show all mounted filesystems, except perhaps those that are of
+   an unselected type or are empty. */
+
+void
+show_all_entries ()
+{
+  struct mount_entry *me;
+
+  for (me = mount_list; me; me = me->me_next)
+    show_dev (me->me_devname, me->me_mountdir, me->me_type);
+}
+
+/* Determine what kind of node PATH is and show the disk usage
+   for it.  STATP is the results of `stat' on PATH.  */
+
+void
+show_entry (path, statp)
+     char *path;
+     struct stat *statp;
+{
+  if (S_ISBLK (statp->st_mode) || S_ISCHR (statp->st_mode))
+    show_disk (path);
+  else
+    show_point (path, statp);
+}
+
+/* Identify the directory, if any, that device
+   DISK is mounted on, and show its disk usage.  */
+
+void
+show_disk (disk)
+     char *disk;
+{
+  struct mount_entry *me;
+
+  for (me = mount_list; me; me = me->me_next)
+    if (!strcmp (disk, me->me_devname))
+      {
+       show_dev (me->me_devname, me->me_mountdir, me->me_type);
+       return;
+      }
+  /* No filesystem is mounted on DISK. */
+  show_dev (disk, (char *) NULL, (char *) NULL);
+}
+
+/* Figure out which device file or directory POINT is mounted on
+   and show its disk usage.
+   STATP is the results of `stat' on POINT.  */
+
+void
+show_point (point, statp)
+     char *point;
+     struct stat *statp;
+{
+  struct stat disk_stats;
+  struct mount_entry *me;
+
+  for (me = mount_list; me; me = me->me_next)
+    {
+      if (me->me_dev == (dev_t) -1)
+       {
+         if (stat (me->me_mountdir, &disk_stats) == 0)
+           me->me_dev = disk_stats.st_dev;
+         else
+           {
+             error (0, errno, "%s", me->me_mountdir);
+             exit_status = 1;
+             me->me_dev = -2;  /* So we won't try and fail repeatedly. */
+           }
+       }
+
+      if (statp->st_dev == me->me_dev)
+       {
+         show_dev (me->me_devname, me->me_mountdir, me->me_type);
+         return;
+       }
+    }
+  error (0, 0, "cannot find mount point for %s", point);
+  exit_status = 1;
+}
+\f
+/* Display a space listing for the disk device with absolute path DISK.
+   If MOUNT_POINT is non-NULL, it is the path of the root of the
+   filesystem on DISK.
+   If FSTYPE is non-NULL, it is the type of the filesystem on DISK. */
+
+void
+show_dev (disk, mount_point, fstype)
+     char *disk;
+     char *mount_point;
+     char *fstype;
+{
+  struct fs_usage fsu;
+  long blocks_used;
+  long blocks_percent_used;
+  long inodes_used;
+  long inodes_percent_used;
+  char *stat_file;
+
+  if (!fs_to_list (fstype))
+    return;
+
+  /* If MOUNT_POINT is NULL, then the filesystem is not mounted, and this
+     program reports on the filesystem that the special file is on.
+     It would be better to report on the unmounted filesystem,
+     but statfs doesn't do that on most systems.  */
+  stat_file = mount_point ? mount_point : disk;
+
+  if (get_fs_usage (stat_file, disk, &fsu))
+    {
+      error (0, errno, "%s", stat_file);
+      exit_status = 1;
+      return;
+    }
+
+  if (kilobyte_blocks)
+    {
+      fsu.fsu_blocks /= 2;
+      fsu.fsu_bfree /= 2;
+      fsu.fsu_bavail /= 2;
+    }
+
+  if (fsu.fsu_blocks == 0)
+    {
+      if (show_all_fs == 0)
+       return;
+      blocks_used = fsu.fsu_bavail = blocks_percent_used = 0;
+    }
+  else
+    {
+      blocks_used = fsu.fsu_blocks - fsu.fsu_bfree;
+      blocks_percent_used = (long)
+       (blocks_used * 100.0 / (blocks_used + fsu.fsu_bavail) + 0.5);
+    }
+
+  if (fsu.fsu_files == 0)
+    {
+      inodes_used = fsu.fsu_ffree = inodes_percent_used = 0;
+    }
+  else
+    {
+      inodes_used = fsu.fsu_files - fsu.fsu_ffree;
+      inodes_percent_used = (long)
+       (inodes_used * 100.0 / fsu.fsu_files + 0.5);
+    }
+
+  printf ("%-20s", disk);
+  if (strlen (disk) > 20 && !posix_format)
+    printf ("\n                    ");
+
+  if (inode_format)
+    printf (" %7ld %7ld %7ld %5ld%%",
+           fsu.fsu_files, inodes_used, fsu.fsu_ffree, inodes_percent_used);
+  else
+    printf (" %7ld %7ld  %7ld  %5ld%% ",
+           fsu.fsu_blocks, blocks_used, fsu.fsu_bavail, blocks_percent_used);
+
+  if (mount_point)
+    printf ("  %s", mount_point);
+  putchar ('\n');
+}
+\f
+/* Add FSTYPE to the list of filesystem types to display. */
+
+void
+add_fs_type (fstype)
+     char *fstype;
+{
+  struct fs_select *fsp;
+
+  fsp = (struct fs_select *) xmalloc (sizeof (struct fs_select));
+  fsp->fs_name = fstype;
+  fsp->fs_next = fs_list;
+  fs_list = fsp;
+}
+
+/* If FSTYPE is a type of filesystem that should be listed,
+   return nonzero, else zero. */
+
+int
+fs_to_list (fstype)
+     char *fstype;
+{
+  struct fs_select *fsp;
+
+  if (fs_list == NULL || fstype == NULL)
+    return 1;
+  for (fsp = fs_list; fsp; fsp = fsp->fs_next)
+    if (!strcmp (fstype, fsp->fs_name))
+      return 1;
+  return 0;
+}
+\f
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-aikPv] [-t fstype] [--all] [--inodes] [--type fstype]\n\
+       [--kilobytes] [--portability] [path...]\n",
+          program_name);
+  exit (1);
+}
diff --git a/src/du.c b/src/du.c
new file mode 100644 (file)
index 0000000..726b5d3
--- /dev/null
+++ b/src/du.c
@@ -0,0 +1,672 @@
+/* du -- summarize disk usage
+   Copyright (C) 1988, 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.  */
+
+/* Differences from the Unix du:
+   * Doesn't simply ignore the names of regular files given as arguments
+     when -a is given.
+   * Additional options:
+   -l          Count the size of all files, even if they have appeared
+               already in another hard link.
+   -x          Do not cross file-system boundaries during the recursion.
+   -c          Write a grand total of all of the arguments after all
+               arguments have been processed.  This can be used to find
+               out the disk usage of a directory, with some files excluded.
+   -k          Print sizes in kilobytes instead of 512 byte blocks
+               (the default required by POSIX).
+   -b          Print sizes in bytes.
+   -S          Count the size of each directory separately, not including
+               the sizes of subdirectories.
+   -D          Dereference only symbolic links given on the command line.
+   -L          Dereference all symbolic links.
+
+   By tege@sics.se, Torbjorn Granlund,
+   and djm@ai.mit.edu, David MacKenzie.  */
+
+#ifdef _AIX
+ #pragma alloca
+#endif
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+
+int lstat ();
+int stat ();
+
+/* Initial number of entries in each hash table entry's table of inodes.  */
+#define INITIAL_HASH_MODULE 100
+
+/* Initial number of entries in the inode hash table.  */
+#define INITIAL_ENTRY_TAB_SIZE 70
+
+/* Initial size to allocate for `path'.  */
+#define INITIAL_PATH_SIZE 100
+
+/* Hash structure for inode and device numbers.  The separate entry
+   structure makes it easier to rehash "in place".  */
+
+struct entry
+{
+  ino_t ino;
+  dev_t dev;
+  struct entry *coll_link;
+};
+
+/* Structure for a hash table for inode numbers. */
+
+struct htab
+{
+  unsigned modulus;            /* Size of the `hash' pointer vector.  */
+  struct entry *entry_tab;     /* Pointer to dynamically growing vector.  */
+  unsigned entry_tab_size;     /* Size of current `entry_tab' allocation.  */
+  unsigned first_free_entry;   /* Index in `entry_tab'.  */
+  struct entry *hash[1];       /* Vector of pointers in `entry_tab'.  */
+};
+
+
+/* Structure for dynamically resizable strings. */
+
+typedef struct
+{
+  unsigned alloc;              /* Size of allocation for the text.  */
+  unsigned length;             /* Length of the text currently.  */
+  char *text;                  /* Pointer to the text.  */
+} *string, stringstruct;
+
+char *savedir ();
+char *xgetcwd ();
+char *xmalloc ();
+char *xrealloc ();
+int hash_insert ();
+int hash_insert2 ();
+long count_entry ();
+void du_files ();
+void error ();
+void hash_init ();
+void hash_reset ();
+void str_concatc ();
+void str_copyc ();
+void str_init ();
+void str_trunc ();
+
+/* Name under which this program was invoked.  */
+char *program_name;
+
+/* If nonzero, display only a total for each argument. */
+int opt_summarize_only = 0;
+
+/* If nonzero, display counts for all files, not just directories. */
+int opt_all = 0;
+
+/* If nonzero, count each hard link of files with multiple links. */
+int opt_count_all = 0;
+
+/* If nonzero, do not cross file-system boundaries. */
+int opt_one_file_system = 0;
+
+/* If nonzero, print a grand total at the end. */
+int opt_combined_arguments = 0;
+
+/* If nonzero, do not add sizes of subdirectories. */
+int opt_separate_dirs = 0;
+
+/* If nonzero, dereference symlinks that are command line arguments. */
+int opt_dereference_arguments = 0;
+
+enum output_size
+{
+  size_blocks,                 /* 512-byte blocks. */
+  size_kilobytes,              /* 1K blocks. */
+  size_bytes                   /* 1-byte blocks. */
+};
+
+/* The units to count in. */
+enum output_size output_size;
+
+/* Accumulated path for file or directory being processed.  */
+string path;
+
+/* Pointer to hash structure, used by the hash routines.  */
+struct htab *htab;
+
+/* Globally used stat buffer.  */
+struct stat stat_buf;
+
+/* A pointer to either lstat or stat, depending on whether
+   dereferencing of all symbolic links is to be done. */
+int (*xstat) ();
+
+/* The exit status to use if we don't get any fatal errors. */
+
+int exit_status;
+
+struct option long_options[] =
+{
+  {"all", 0, &opt_all, 1},
+  {"bytes", 0, NULL, 'b'},
+  {"count-links", 0, &opt_count_all, 1},
+  {"dereference", 0, NULL, 'L'},
+  {"dereference-args", 0, &opt_dereference_arguments, 1},
+  {"kilobytes", 0, NULL, 'k'},
+  {"one-file-system", 0, &opt_one_file_system, 1},
+  {"separate-dirs", 0, &opt_separate_dirs, 1},
+  {"summarize", 0, &opt_summarize_only, 1},
+  {"total", 0, &opt_combined_arguments, 1},
+  {NULL, 0, NULL, 0}
+};
+
+void
+usage (reason)
+     char *reason;
+{
+  if (reason != NULL)
+    fprintf (stderr, "%s: %s\n", program_name, reason);
+
+  fprintf (stderr, "\
+Usage: %s [-abcklsxDLS] [--all] [--total] [--count-links] [--summarize]\n\
+       [--bytes] [--kilobytes] [--one-file-system] [--separate-dirs]\n\
+       [--dereference] [--dereference-args] [path...]\n",
+          program_name);
+
+  exit (2);
+}
+\f
+void
+main (argc, argv)
+     int argc;
+     char *argv[];
+{
+  int c;
+
+  program_name = argv[0];
+  xstat = lstat;
+  output_size = getenv ("POSIXLY_CORRECT") ? size_blocks : size_kilobytes;
+
+  while ((c = getopt_long (argc, argv, "abcklsxDLS", long_options, (int *) 0))
+        != EOF)
+    {
+      switch (c)
+       {
+       case 0:                 /* Long option. */
+         break;
+
+       case 'a':
+         opt_all = 1;
+         break;
+
+       case 'b':
+         output_size = size_bytes;
+         break;
+
+       case 'c':
+         opt_combined_arguments = 1;
+         break;
+
+       case 'k':
+         output_size = size_kilobytes;
+         break;
+
+       case 'l':
+         opt_count_all = 1;
+         break;
+
+       case 's':
+         opt_summarize_only = 1;
+         break;
+
+       case 'x':
+         opt_one_file_system = 1;
+         break;
+
+       case 'D':
+         opt_dereference_arguments = 1;
+         break;
+
+       case 'L':
+         xstat = stat;
+         break;
+
+       case 'S':
+         opt_separate_dirs = 1;
+         break;
+
+       default:
+         usage ((char *) 0);
+       }
+    }
+
+  if (opt_all && opt_summarize_only)
+    usage ("cannot both summarize and show all entries");
+
+  /* Initialize the hash structure for inode numbers.  */
+  hash_init (INITIAL_HASH_MODULE, INITIAL_ENTRY_TAB_SIZE);
+
+  str_init (&path, INITIAL_PATH_SIZE);
+
+  if (optind == argc)
+    {
+      str_copyc (path, ".");
+
+      /* Initialize the hash structure for inode numbers.  */
+      hash_reset ();
+
+      /* Get the size of the current directory only.  */
+      count_entry (".", 1, 0);
+    }
+  else
+    {
+      du_files (argv + optind);
+    }
+
+  exit (exit_status);
+}
+\f
+/* Recursively print the sizes of the directories (and, if selected, files)
+   named in FILES, the last entry of which is NULL.  */
+
+void
+du_files (files)
+     char **files;
+{
+  char *wd;
+  ino_t initial_ino;           /* Initial directory's inode. */
+  dev_t initial_dev;           /* Initial directory's device. */
+  long tot_size = 0L;          /* Grand total size of all args. */
+  int i;                       /* Index in FILES. */
+
+  wd = xgetcwd ();
+  if (wd == NULL)
+    error (1, errno, "cannot get current directory");
+
+  /* Remember the inode and device number of the current directory.  */
+  if (stat (".", &stat_buf))
+    error (1, errno, "current directory");
+  initial_ino = stat_buf.st_ino;
+  initial_dev = stat_buf.st_dev;
+
+  for (i = 0; files[i]; i++)
+    {
+      char *arg;
+      int s;
+
+      arg = files[i];
+
+      /* Delete final slash in the argument, unless the slash is alone.  */
+      s = strlen (arg) - 1;
+      if (s != 0)
+       {
+         if (arg[s] == '/')
+           arg[s] = 0;
+
+         str_copyc (path, arg);
+       }
+      else if (arg[0] == '/')
+       str_trunc (path, 0);    /* Null path for root directory.  */
+      else
+       str_copyc (path, arg);
+
+      if (!opt_combined_arguments)
+       hash_reset ();
+
+      tot_size += count_entry (arg, 1, 0);
+
+      /* chdir if `count_entry' has changed the working directory.  */
+      if (stat (".", &stat_buf))
+       error (1, errno, ".");
+      if ((stat_buf.st_ino != initial_ino || stat_buf.st_dev != initial_dev)
+         && chdir (wd) < 0)
+       error (1, errno, "cannot change to directory %s", wd);
+    }
+
+  if (opt_combined_arguments)
+    {
+      printf ("%ld\ttotal\n", output_size == size_bytes ? tot_size
+             : convert_blocks (tot_size, output_size == size_kilobytes));
+      fflush (stdout);
+    }
+
+  free (wd);
+}
+\f
+/* Print (if appropriate) and return the size
+   (in units determined by `output_size') of file or directory ENT.
+   TOP is one for external calls, zero for recursive calls.
+   LAST_DEV is the device that the parent directory of ENT is on.  */
+
+long
+count_entry (ent, top, last_dev)
+     char *ent;
+     int top;
+     dev_t last_dev;
+{
+  long size;
+
+  if ((top && opt_dereference_arguments ?
+      stat (ent, &stat_buf) :
+      (*xstat) (ent, &stat_buf)) < 0)
+    {
+      error (0, errno, "%s", path->text);
+      exit_status = 1;
+      return 0;
+    }
+
+  if (!opt_count_all
+      && stat_buf.st_nlink > 1
+      && hash_insert (stat_buf.st_ino, stat_buf.st_dev))
+    return 0;                  /* Have counted this already.  */
+
+  if (output_size == size_bytes)
+    size = stat_buf.st_size;
+  else
+    size = ST_NBLOCKS (stat_buf);
+
+  if (S_ISDIR (stat_buf.st_mode))
+    {
+      unsigned pathlen;
+      dev_t dir_dev;
+      char *name_space;
+      char *namep;
+
+      dir_dev = stat_buf.st_dev;
+
+      if (opt_one_file_system && !top && last_dev != dir_dev)
+       return 0;               /* Don't enter a new file system.  */
+
+      if (chdir (ent) < 0)
+       {
+         error (0, errno, "cannot change to directory %s", path->text);
+         exit_status = 1;
+         return 0;
+       }
+
+      errno = 0;
+      name_space = savedir (".", stat_buf.st_size);
+      if (name_space == NULL)
+       {
+         if (errno)
+           {
+             error (0, errno, "%s", path->text);
+             chdir ("..");     /* Try to return to previous directory.  */
+             exit_status = 1;
+             return 0;
+           }
+         else
+           error (1, 0, "virtual memory exhausted");
+       }
+
+      /* Remember the current path.  */
+
+      str_concatc (path, "/");
+      pathlen = path->length;
+
+      namep = name_space;
+      while (*namep != 0)
+       {
+         str_concatc (path, namep);
+
+         size += count_entry (namep, 0, dir_dev);
+
+         str_trunc (path, pathlen);
+         namep += strlen (namep) + 1;
+       }
+      free (name_space);
+      chdir ("..");
+
+      str_trunc (path, pathlen - 1); /* Remove the "/" we added.  */
+      if (!opt_summarize_only || top)
+       {
+         printf ("%ld\t%s\n", output_size == size_bytes ? size
+                 : convert_blocks (size, output_size == size_kilobytes),
+                 path->text);
+         fflush (stdout);
+       }
+      return opt_separate_dirs ? 0 : size;
+    }
+  else if (opt_all || top)
+    {
+      printf ("%ld\t%s\n", output_size == size_bytes ? size
+             : convert_blocks (size, output_size == size_kilobytes),
+             path->text);
+      fflush (stdout);
+    }
+
+  return size;
+}
+\f
+/* Allocate space for the hash structures, and set the global
+   variable `htab' to point to it.  The initial hash module is specified in
+   MODULUS, and the number of entries are specified in ENTRY_TAB_SIZE.  (The
+   hash structure will be rebuilt when ENTRY_TAB_SIZE entries have been
+   inserted, and MODULUS and ENTRY_TAB_SIZE in the global `htab' will be
+   doubled.)  */
+
+void
+hash_init (modulus, entry_tab_size)
+     unsigned modulus;
+     unsigned entry_tab_size;
+{
+  struct htab *htab_r;
+
+  htab_r = (struct htab *)
+    xmalloc (sizeof (struct htab) + sizeof (struct entry *) * modulus);
+
+  htab_r->entry_tab = (struct entry *)
+    xmalloc (sizeof (struct entry) * entry_tab_size);
+
+  htab_r->modulus = modulus;
+  htab_r->entry_tab_size = entry_tab_size;
+  htab = htab_r;
+
+  hash_reset ();
+}
+
+/* Reset the hash structure in the global variable `htab' to
+   contain no entries.  */
+
+void
+hash_reset ()
+{
+  int i;
+  struct entry **p;
+
+  htab->first_free_entry = 0;
+
+  p = htab->hash;
+  for (i = htab->modulus; i > 0; i--)
+    *p++ = NULL;
+}
+
+/* Insert an item (inode INO and device DEV) in the hash
+   structure in the global variable `htab', if an entry with the same data
+   was not found already.  Return zero if the item was inserted and non-zero
+   if it wasn't.  */
+
+int
+hash_insert (ino, dev)
+     ino_t ino;
+     dev_t dev;
+{
+  struct htab *htab_r = htab;  /* Initially a copy of the global `htab'.  */
+
+  if (htab_r->first_free_entry >= htab_r->entry_tab_size)
+    {
+      int i;
+      struct entry *ep;
+      unsigned modulus;
+      unsigned entry_tab_size;
+
+      /* Increase the number of hash entries, and re-hash the data.
+        The method of shrimping and increasing is made to compactify
+        the heap.  If twice as much data would be allocated
+        straightforwardly, we would never re-use a byte of memory.  */
+
+      /* Let `htab' shrimp.  Keep only the header, not the pointer vector.  */
+
+      htab_r = (struct htab *)
+       xrealloc ((char *) htab_r, sizeof (struct htab));
+
+      modulus = 2 * htab_r->modulus;
+      entry_tab_size = 2 * htab_r->entry_tab_size;
+
+      /* Increase the number of possible entries.  */
+
+      htab_r->entry_tab = (struct entry *)
+       xrealloc ((char *) htab_r->entry_tab,
+                sizeof (struct entry) * entry_tab_size);
+
+      /* Increase the size of htab again.  */
+
+      htab_r = (struct htab *)
+       xrealloc ((char *) htab_r,
+                sizeof (struct htab) + sizeof (struct entry *) * modulus);
+
+      htab_r->modulus = modulus;
+      htab_r->entry_tab_size = entry_tab_size;
+      htab = htab_r;
+
+      i = htab_r->first_free_entry;
+
+      /* Make the increased hash table empty.  The entries are still
+        available in htab->entry_tab.  */
+
+      hash_reset ();
+
+      /* Go through the entries and install them in the pointer vector
+        htab->hash.  The items are actually inserted in htab->entry_tab at
+        the position where they already are.  The htab->coll_link need
+        however be updated.  Could be made a little more efficient.  */
+
+      for (ep = htab_r->entry_tab; i > 0; i--)
+       {
+         hash_insert2 (htab_r, ep->ino, ep->dev);
+         ep++;
+       }
+    }
+
+  return hash_insert2 (htab_r, ino, dev);
+}
+
+/* Insert INO and DEV in the hash structure HTAB, if not
+   already present.  Return zero if inserted and non-zero if it
+   already existed.  */
+
+int
+hash_insert2 (htab, ino, dev)
+     struct htab *htab;
+     ino_t ino;
+     dev_t dev;
+{
+  struct entry **hp, *ep2, *ep;
+  hp = &htab->hash[ino % htab->modulus];
+  ep2 = *hp;
+
+  /* Collision?  */
+
+  if (ep2 != NULL)
+    {
+      ep = ep2;
+
+      /* Search for an entry with the same data.  */
+
+      do
+       {
+         if (ep->ino == ino && ep->dev == dev)
+           return 1;           /* Found an entry with the same data.  */
+         ep = ep->coll_link;
+       }
+      while (ep != NULL);
+
+      /* Did not find it.  */
+
+    }
+
+  ep = *hp = &htab->entry_tab[htab->first_free_entry++];
+  ep->ino = ino;
+  ep->dev = dev;
+  ep->coll_link = ep2;         /* `ep2' is NULL if no collision.  */
+
+  return 0;
+}
+\f
+/* Initialize the struct string S1 for holding SIZE characters.  */
+
+void
+str_init (s1, size)
+     string *s1;
+     unsigned size;
+{
+  string s;
+
+  s = (string) xmalloc (sizeof (stringstruct));
+  s->text = xmalloc (size + 1);
+
+  s->alloc = size;
+  *s1 = s;
+}
+
+static void
+ensure_space (s, size)
+     string s;
+     unsigned size;
+{
+  if (s->alloc < size)
+    {
+      s->text = xrealloc (s->text, size + 1);
+      s->alloc = size;
+    }
+}
+
+/* Assign the null-terminated C-string CSTR to S1.  */
+
+void
+str_copyc (s1, cstr)
+     string s1;
+     char *cstr;
+{
+  unsigned l = strlen (cstr);
+  ensure_space (s1, l);
+  strcpy (s1->text, cstr);
+  s1->length = l;
+}
+
+void
+str_concatc (s1, cstr)
+     string s1;
+     char *cstr;
+{
+  unsigned l1 = s1->length;
+  unsigned l2 = strlen (cstr);
+  unsigned l = l1 + l2;
+
+  ensure_space (s1, l);
+  strcpy (s1->text + l1, cstr);
+  s1->length = l;
+}
+
+/* Truncate the string S1 to have length LENGTH.  */
+
+void
+str_trunc (s1, length)
+     string s1;
+     unsigned length;
+{
+  if (s1->length > length)
+    {
+      s1->text[length] = 0;
+      s1->length = length;
+    }
+}
diff --git a/src/install.c b/src/install.c
new file mode 100644 (file)
index 0000000..473ea6d
--- /dev/null
@@ -0,0 +1,496 @@
+/* install - copy files and set attributes
+   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.  */
+
+/* Copy files and set their permission modes and, if possible,
+   their owner and group.  Used similarly to `cp'; typically
+   used in Makefiles to copy programs into their destination
+   directories.  It can also be used to create the destination
+   directories and any leading directories, and to set the final
+   directory's modes.  It refuses to copy files onto themselves.
+
+   Options:
+   -g, --group=GROUP
+       Set the group ownership of the installed file or directory
+       to the group ID of GROUP (default is process's current
+       group).  GROUP may also be a numeric group ID.
+
+   -m, --mode=MODE
+       Set the permission mode for the installed file or directory
+       to MODE, which is an octal number (default is 0755).
+
+   -o, --owner=OWNER
+       If run as root, set the ownership of the installed file to
+       the user ID of OWNER (default is root).  OWNER may also be
+       a numeric user ID.
+
+   -c  No effect.  For compatibility with old Unix versions of install.
+
+   -s, --strip
+       Strip the symbol tables from installed files.
+
+   -d, --directory
+       Create a directory and its leading directories, if they
+       do not already exist.  Set the owner, group and mode
+       as given on the command line.  Any leading directories
+       that are created are also given those attributes.
+       This is different from the SunOS 4.0 install, which gives
+       directories that it creates the default attributes.
+
+   David MacKenzie <djm@gnu.ai.mit.edu> */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include "system.h"
+#include "modechange.h"
+
+#ifdef _POSIX_VERSION
+#include <sys/wait.h>
+#else
+struct passwd *getpwnam ();
+struct group *getgrnam ();
+uid_t getuid ();
+gid_t getgid ();
+int wait ();
+#endif
+
+#ifdef _POSIX_SOURCE
+#define endgrent()
+#define endpwent()
+#endif
+
+/* True if C is an ASCII octal digit. */
+#define isodigit(c) ((c) >= '0' && c <= '7')
+
+/* Number of bytes of a file to copy at a time. */
+#define READ_SIZE (32 * 1024)
+
+char *basename ();
+char *xmalloc ();
+int change_attributes ();
+int copy_file ();
+int install_dir ();
+int install_file_in_dir ();
+int install_file_in_file ();
+int isdir ();
+int make_path ();
+int isnumber ();
+void error ();
+void get_ids ();
+void strip ();
+void usage ();
+
+/* The name this program was run with, for error messages. */
+char *program_name;
+
+/* The user name that will own the files, or NULL to make the owner
+   the current user ID. */
+char *owner_name;
+
+/* The user ID corresponding to `owner_name'. */
+uid_t owner_id;
+
+/* The group name that will own the files, or NULL to make the group
+   the current group ID. */
+char *group_name;
+
+/* The group ID corresponding to `group_name'. */
+gid_t group_id;
+
+/* The permissions to which the files will be set.  The umask has
+   no effect. */
+int mode;
+
+/* If nonzero, strip executable files after copying them. */
+int strip_files;
+
+/* If nonzero, install a directory instead of a regular file. */
+int dir_arg;
+
+struct option long_options[] =
+{
+  {"strip", 0, NULL, 's'},
+  {"directory", 0, NULL, 'd'},
+  {"group", 1, NULL, 'g'},
+  {"mode", 1, NULL, 'm'},
+  {"owner", 1, NULL, 'o'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int optc;
+  int errors = 0;
+  char *symbolic_mode = NULL;
+
+  program_name = argv[0];
+  owner_name = NULL;
+  group_name = NULL;
+  mode = 0755;
+  strip_files = 0;
+  dir_arg = 0;
+  umask (0);
+
+  while ((optc = getopt_long (argc, argv, "csdg:m:o:", long_options,
+                             (int *) 0)) != EOF)
+    {
+      switch (optc)
+       {
+       case 'c':
+         break;
+       case 's':
+         strip_files = 1;
+         break;
+       case 'd':
+         dir_arg = 1;
+         break;
+       case 'g':
+         group_name = optarg;
+         break;
+       case 'm':
+         symbolic_mode = optarg;
+         break;
+       case 'o':
+         owner_name = optarg;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  /* Check for invalid combinations of arguments. */
+  if ((dir_arg && strip_files)
+      || (optind == argc)
+      || (optind == argc - 1 && !dir_arg))
+    usage ();
+
+  if (symbolic_mode)
+    {
+      struct mode_change *change = mode_compile (symbolic_mode, 0);
+      if (change == MODE_INVALID)
+       error (1, 0, "invalid mode `%s'", symbolic_mode);
+      else if (change == MODE_MEMORY_EXHAUSTED)
+       error (1, 0, "virtual memory exhausted");
+      mode = mode_adjust (0, change);
+    }
+
+  get_ids ();
+
+  if (dir_arg)
+    {
+      for (; optind < argc; ++optind)
+       {
+         errors |=
+           make_path (argv[optind], mode, mode, owner_id, group_id, NULL);
+       }
+    }
+  else
+    {
+      if (optind == argc - 2)
+       {
+         if (!isdir (argv[argc - 1]))
+           errors = install_file_in_file (argv[argc - 2], argv[argc - 1]);
+         else
+           errors = install_file_in_dir (argv[argc - 2], argv[argc - 1]);
+       }
+      else
+       {
+         if (!isdir (argv[argc - 1]))
+           usage ();
+         for (; optind < argc - 1; ++optind)
+           {
+             errors |= install_file_in_dir (argv[optind], argv[argc - 1]);
+           }
+       }
+    }
+
+  exit (errors);
+}
+
+/* Copy file FROM onto file TO and give TO the appropriate
+   attributes.
+   Return 0 if successful, 1 if an error occurs. */
+
+int
+install_file_in_file (from, to)
+     char *from;
+     char *to;
+{
+  if (copy_file (from, to))
+    return 1;
+  if (strip_files)
+    strip (to);
+  return change_attributes (to);
+}
+
+/* Copy file FROM into directory TO_DIR, keeping its same name,
+   and give the copy the appropriate attributes.
+   Return 0 if successful, 1 if not. */
+
+int
+install_file_in_dir (from, to_dir)
+     char *from;
+     char *to_dir;
+{
+  char *from_base;
+  char *to;
+  int ret;
+
+  from_base = basename (from);
+  to = xmalloc ((unsigned) (strlen (to_dir) + strlen (from_base) + 2));
+  sprintf (to, "%s/%s", to_dir, from_base);
+  ret = install_file_in_file (from, to);
+  free (to);
+  return ret;
+}
+
+/* A chunk of a file being copied. */
+static char buffer[READ_SIZE];
+
+/* Copy file FROM onto file TO, creating TO if necessary.
+   Return 0 if the copy is successful, 1 if not. */
+
+int
+copy_file (from, to)
+     char *from;
+     char *to;
+{
+  int fromfd, tofd;
+  int bytes;
+  int ret = 0;
+  struct stat from_stats, to_stats;
+
+  if (stat (from, &from_stats))
+    {
+      error (0, errno, "%s", from);
+      return 1;
+    }
+  if (!S_ISREG (from_stats.st_mode))
+    {
+      error (0, 0, "`%s' is not a regular file", from);
+      return 1;
+    }
+  if (stat (to, &to_stats) == 0)
+    {
+      if (!S_ISREG (to_stats.st_mode))
+       {
+         error (0, 0, "`%s' is not a regular file", to);
+         return 1;
+       }
+      if (from_stats.st_dev == to_stats.st_dev
+         && from_stats.st_ino == to_stats.st_ino)
+       {
+         error (0, 0, "`%s' and `%s' are the same file", from, to);
+         return 1;
+       }
+      /* If unlink fails, try to proceed anyway.  */
+      unlink (to);
+    }
+
+  fromfd = open (from, O_RDONLY, 0);
+  if (fromfd == -1)
+    {
+      error (0, errno, "%s", from);
+      return 1;
+    }
+
+  /* Make sure to open the file in a mode that allows writing. */
+  tofd = open (to, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+  if (tofd == -1)
+    {
+      error (0, errno, "%s", to);
+      close (fromfd);
+      return 1;
+    }
+
+  while ((bytes = read (fromfd, buffer, READ_SIZE)) > 0)
+    if (write (tofd, buffer, bytes) != bytes)
+      {
+       error (0, errno, "%s", to);
+       goto copy_error;
+      }
+
+  if (bytes == -1)
+    {
+      error (0, errno, "%s", from);
+      goto copy_error;
+    }
+
+  if (close (fromfd) < 0)
+    {
+      error (0, errno, "%s", from);
+      ret = 1;
+    }
+  if (close (tofd) < 0)
+    {
+      error (0, errno, "%s", to);
+      ret = 1;
+    }
+  return ret;
+
+ copy_error:
+  close (fromfd);
+  close (tofd);
+  return 1;
+}
+
+/* Set the attributes of file or directory PATH.
+   Return 0 if successful, 1 if not. */
+
+int
+change_attributes (path)
+     char *path;
+{
+  int err = 0;
+
+  /* chown must precede chmod because on some systems,
+     chown clears the set[ug]id bits for non-superusers,
+     resulting in incorrect permissions.
+     On System V, users can give away files with chown and then not
+     be able to chmod them.  So don't give files away.
+
+     We don't pass -1 to chown to mean "don't change the value"
+     because SVR3 and earlier non-BSD systems don't support that.
+
+     We don't normally ignore errors from chown because the idea of
+     the install command is that the file is supposed to end up with
+     precisely the attributes that the user specified (or defaulted).
+     If the file doesn't end up with the group they asked for, they'll
+     want to know.  But AFS returns EPERM when you try to change a
+     file's group; thus the kludge.  */
+
+  if (chown (path, owner_id, group_id)
+#ifdef AFS
+      && errno != EPERM
+#endif
+      )
+    err = errno;
+  if (chmod (path, mode))
+    err = errno;
+  if (err)
+    {
+      error (0, err, "%s", path);
+      return 1;
+    }
+  return 0;
+}
+
+/* Strip the symbol table from the file PATH.
+   We could dig the magic number out of the file first to
+   determine whether to strip it, but the header files and
+   magic numbers vary so much from system to system that making
+   it portable would be very difficult.  Not worth the effort. */
+
+void
+strip (path)
+     char *path;
+{
+  int pid, status;
+
+  pid = fork ();
+  switch (pid)
+    {
+    case -1:
+      error (1, errno, "cannot fork");
+      break;
+    case 0:                    /* Child. */
+      execlp ("strip", "strip", path, (char *) NULL);
+      error (1, errno, "cannot run strip");
+      break;
+    default:                   /* Parent. */
+      /* Parent process. */
+      while (pid != wait (&status))    /* Wait for kid to finish. */
+       /* Do nothing. */ ;
+      break;
+    }
+}
+
+/* Initialize the user and group ownership of the files to install. */
+
+void
+get_ids ()
+{
+  struct passwd *pw;
+  struct group *gr;
+
+  if (owner_name)
+    {
+      pw = getpwnam (owner_name);
+      if (pw == NULL)
+       {
+         if (!isnumber (owner_name))
+           error (1, 0, "invalid user `%s'", owner_name);
+         owner_id = atoi (owner_name);
+       }
+      else
+       owner_id = pw->pw_uid;
+      endpwent ();
+    }
+  else
+    owner_id = getuid ();
+
+  if (group_name)
+    {
+      gr = getgrnam (group_name);
+      if (gr == NULL)
+       {
+         if (!isnumber (group_name))
+           error (1, 0, "invalid group `%s'", group_name);
+         group_id = atoi (group_name);
+       }
+      else
+       group_id = gr->gr_gid;
+      endgrent ();
+    }
+  else
+    group_id = getgid ();
+}
+
+/* Return nonzero if STR is an ASCII representation of a nonzero
+   decimal integer, zero if not. */
+
+int
+isnumber (str)
+     char *str;
+{
+  if (*str == 0)
+    return 0;
+  for (; *str; str++)
+    if (!isdigit (*str))
+      return 0;
+  return 1;
+}
+
+void
+usage ()
+{
+   fprintf (stderr, "\
+Usage: %s [options] [-s] [--strip] source dest\n\
+       %s [options] [-s] [--strip] source... directory\n\
+       %s [options] {-d,--directory} directory...\n\
+Options:\n\
+       [-c] [-g group] [-m mode] [-o owner]\n\
+       [--group=group] [--mode=mode] [--owner=owner]\n",
+           program_name, program_name, program_name);
+  exit (1);
+}
diff --git a/src/ln.c b/src/ln.c
new file mode 100644 (file)
index 0000000..781a55d
--- /dev/null
+++ b/src/ln.c
@@ -0,0 +1,293 @@
+/* `ln' program to create links between files.
+   Copyright (C) 1986, 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.  */
+\f
+/* Written by Mike Parker and David MacKenzie. */
+
+#ifdef _AIX
+ #pragma alloca
+#endif
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+#include "system.h"
+#include "backupfile.h"
+
+int link ();                   /* Some systems don't declare this anywhere. */
+
+#ifdef S_ISLNK
+int symlink ();
+#endif
+
+char *basename ();
+enum backup_type get_version ();
+int do_link ();
+int isdir ();
+int yesno ();
+void error ();
+void usage ();
+
+/* A pointer to the function used to make links.  This will point to either
+   `link' or `symlink'. */
+int (*linkfunc) ();
+
+/* If nonzero, make symbolic links; otherwise, make hard links.  */
+int symbolic_link;
+
+/* If nonzero, ask the user before removing existing files.  */
+int interactive;
+
+/* If nonzero, remove existing files unconditionally.  */
+int remove_existing_files;
+
+/* If nonzero, list each file as it is moved. */
+int verbose;
+
+/* If nonzero, allow the superuser to make hard links to directories. */
+int hard_dir_link;
+
+/* The name by which the program was run, for error messages.  */
+char *program_name;
+
+struct option long_options[] = 
+{
+  {"backup", 0, NULL, 'b'},
+  {"directory", 0, &hard_dir_link, 1},
+  {"force", 0, NULL, 'f'},
+  {"interactive", 0, NULL, 'i'},
+  {"suffix", 1, NULL, 'S'},
+  {"symbolic", 0, &symbolic_link, 1},
+  {"verbose", 0, &verbose, 1},
+  {"version-control", 1, NULL, 'V'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int c;
+  int errors;
+  int make_backups = 0;
+  char *version;
+
+  version = getenv ("SIMPLE_BACKUP_SUFFIX");
+  if (version)
+    simple_backup_suffix = version;
+  version = getenv ("VERSION_CONTROL");
+  program_name = argv[0];
+  linkfunc = link;
+  symbolic_link = remove_existing_files = interactive = verbose
+    = hard_dir_link = 0;
+  errors = 0;
+
+  while ((c = getopt_long (argc, argv, "bdfisvFS:V:", long_options, (int *) 0))
+        != EOF)
+    {
+      switch (c)
+       {
+       case 0:                 /* Long-named option. */
+         break;
+       case 'b':
+         make_backups = 1;
+         break;
+       case 'd':
+       case 'F':
+         hard_dir_link = 1;
+         break;
+       case 'f':
+         remove_existing_files = 1;
+         interactive = 0;
+         break;
+       case 'i':
+         remove_existing_files = 0;
+         interactive = 1;
+         break;
+       case 's':
+#ifdef S_ISLNK
+         symbolic_link = 1;
+#else
+         error (0, 0, "symbolic links not supported; making hard links");
+#endif
+         break;
+       case 'v':
+         verbose = 1;
+         break;
+       case 'S':
+         simple_backup_suffix = optarg;
+         break;
+       case 'V':
+         version = optarg;
+         break;
+       default:
+         usage ();
+         break;
+       }
+    }
+  if (optind == argc)
+    usage ();
+
+  if (make_backups)
+    backup_type = get_version (version);
+
+#ifdef S_ISLNK
+  if (symbolic_link)
+    linkfunc = symlink;
+#endif
+
+  if (optind == argc - 1)
+    errors = do_link (argv[optind], ".");
+  else if (optind == argc - 2)
+    {
+      errors = do_link (argv[optind], argv[optind + 1]);
+    }
+  else
+    {
+      char *to;
+
+      to = argv[argc - 1];
+      if (!isdir (to))
+       error (1, 0, "when making multiple links, last argument must be a directory");
+      for (; optind < argc - 1; ++optind)
+       errors += do_link (argv[optind], to);
+    }
+
+  exit (errors != 0);
+}
+
+/* Make a link DEST to existing file SOURCE.
+   If DEST is a directory, put the link to SOURCE in that directory.
+   Return 1 if there is an error, otherwise 0.  */
+
+int
+do_link (source, dest)
+     char *source;
+     char *dest;
+{
+  struct stat dest_stats;
+  char *dest_backup = NULL;
+
+  /* isdir uses stat instead of lstat.
+     On SVR4, link does not follow symlinks, so this check disallows
+     making hard links to symlinks that point to directories.  Big deal.
+     On other systems, link follows symlinks, so this check is right.  */
+  if (!symbolic_link && !hard_dir_link && isdir (source))
+    {
+      error (0, 0, "%s: hard link not allowed for directory", source);
+      return 1;
+    }
+  if (isdir (dest))
+    {
+      /* Target is a directory; build the full filename. */
+      char *new_dest;
+      char *source_base;
+
+      source_base = basename (source);
+      new_dest = (char *)
+       alloca (strlen (source_base) + 1 + strlen (dest) + 1);
+      sprintf (new_dest, "%s/%s", dest, source_base);
+      dest = new_dest;
+    }
+
+  if (lstat (dest, &dest_stats) == 0)
+    {
+      if (S_ISDIR (dest_stats.st_mode))
+       {
+         error (0, 0, "%s: cannot overwrite directory", dest);
+         return 1;
+       }
+      if (interactive)
+       {
+         fprintf (stderr, "%s: replace `%s'? ", program_name, dest);
+         if (!yesno ())
+           return 0;
+       }
+      else if (!remove_existing_files)
+       {
+         error (0, 0, "%s: File exists", dest);
+         return 1;
+       }
+
+      if (backup_type != none)
+       {
+         char *tmp_backup = find_backup_file_name (dest);
+         if (tmp_backup == NULL)
+           error (1, 0, "virtual memory exhausted");
+         dest_backup = alloca (strlen (tmp_backup) + 1);
+         strcpy (dest_backup, tmp_backup);
+         free (tmp_backup);
+         if (rename (dest, dest_backup))
+           {
+             if (errno != ENOENT)
+               {
+                 error (0, errno, "cannot backup `%s'", dest);
+                 return 1;
+               }
+             else
+               dest_backup = NULL;
+           }
+       }
+      else if (unlink (dest) && errno != ENOENT)
+       {
+         error (0, errno, "cannot remove old link to `%s'", dest);
+         return 1;
+       }
+    }
+  else if (errno != ENOENT)
+    {
+      error (0, errno, "%s", dest);
+      return 1;
+    }
+       
+  if (verbose)
+    printf ("%s -> %s\n", source, dest);
+
+  if ((*linkfunc) (source, dest) == 0)
+    {
+      return 0;
+    }
+
+  error (0, errno, "cannot %slink `%s' to `%s'",
+#ifdef S_ISLNK
+            linkfunc == symlink ? "symbolic " : "",
+#else
+            "",
+#endif
+            source, dest);
+
+  if (dest_backup)
+    {
+      if (rename (dest_backup, dest))
+       error (0, errno, "cannot un-backup `%s'", dest);
+    }
+  return 1;
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [options] source [dest]\n\
+       %s [options] source... directory\n\
+Options:\n\
+       [-bdfisvF] [-S backup-suffix] [-V {numbered,existing,simple}]\n\
+       [--version-control={numbered,existing,simple}] [--backup] [--directory]\n\
+       [--force] [--interactive] [--symbolic] [--verbose]\n\
+       [--suffix=backup-suffix]\n",
+          program_name, program_name);
+  exit (1);
+}
diff --git a/src/ls.c b/src/ls.c
new file mode 100644 (file)
index 0000000..3f20727
--- /dev/null
+++ b/src/ls.c
@@ -0,0 +1,1813 @@
+/* `dir', `vdir' and `ls' directory listing programs for GNU.
+   Copyright (C) 1985, 1988, 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.  */
+\f
+/* If the macro MULTI_COL is defined,
+   the multi-column format is the default regardless
+   of the type of output device.
+   This is for the `dir' program.
+
+   If the macro LONG_FORMAT is defined,
+   the long format is the default regardless of the
+   type of output device.
+   This is for the `vdir' program.
+
+   If neither is defined,
+   the output format depends on whether the output
+   device is a terminal.
+   This is for the `ls' program. */
+
+/* Written by Richard Stallman and David MacKenzie. */
+
+#ifdef _AIX
+ #pragma alloca
+#endif
+#include <sys/types.h>
+#if !defined(_POSIX_SOURCE) || defined(_AIX)
+#include <sys/ioctl.h>
+#endif
+#include <stdio.h>
+#include <grp.h>
+#include <pwd.h>
+#include <getopt.h>
+#include <fnmatch.h>
+#include "system.h"
+
+#ifndef S_IEXEC
+#define S_IEXEC S_IXUSR
+#endif
+
+/* Return an int indicating the result of comparing two longs. */
+#ifdef INT_16_BITS
+#define longdiff(a, b) ((a) < (b) ? -1 : (a) > (b) ? 1 : 0)
+#else
+#define longdiff(a, b) ((a) - (b))
+#endif
+
+#ifdef STDC_HEADERS
+#include <time.h>
+#else
+char *ctime ();
+time_t time ();
+#endif
+
+void mode_string ();
+
+char *xstrdup ();
+char *getgroup ();
+char *getuser ();
+char *make_link_path ();
+char *xmalloc ();
+char *xrealloc ();
+int argmatch ();
+int compare_atime ();
+int rev_cmp_atime ();
+int compare_ctime ();
+int rev_cmp_ctime ();
+int compare_mtime ();
+int rev_cmp_mtime ();
+int compare_size ();
+int rev_cmp_size ();
+int compare_name ();
+int rev_cmp_name ();
+int compare_extension ();
+int rev_cmp_extension ();
+int decode_switches ();
+int file_interesting ();
+int gobble_file ();
+int is_not_dot_or_dotdot ();
+int length_of_file_name_and_frills ();
+void add_ignore_pattern ();
+void attach ();
+void clear_files ();
+void error ();
+void extract_dirs_from_files ();
+void get_link_name ();
+void indent ();
+void invalid_arg ();
+void print_current_files ();
+void print_dir ();
+void print_file_name_and_frills ();
+void print_horizontal ();
+void print_long_format ();
+void print_many_per_line ();
+void print_name_with_quoting ();
+void print_type_indicator ();
+void print_with_commas ();
+void queue_directory ();
+void sort_files ();
+void usage ();
+\f
+enum filetype
+{
+  symbolic_link,
+  directory,
+  arg_directory,               /* Directory given as command line arg. */
+  normal                       /* All others. */
+};
+
+struct file
+{
+  /* The file name. */
+  char *name;
+
+  struct stat stat;
+
+  /* For symbolic link, name of the file linked to, otherwise zero. */
+  char *linkname;
+
+  /* For symbolic link and long listing, st_mode of file linked to, otherwise
+     zero. */
+  unsigned int linkmode;
+
+  enum filetype filetype;
+};
+
+/* The table of files in the current directory:
+
+   `files' points to a vector of `struct file', one per file.
+   `nfiles' is the number of elements space has been allocated for.
+   `files_index' is the number actually in use.  */
+
+/* Address of block containing the files that are described.  */
+
+struct file *files;
+
+/* Length of block that `files' points to, measured in files.  */
+
+int nfiles;
+
+/* Index of first unused in `files'.  */
+
+int files_index;
+
+/* Record of one pending directory waiting to be listed.  */
+
+struct pending
+{
+  char *name;
+  /* If the directory is actually the file pointed to by a symbolic link we
+     were told to list, `realname' will contain the name of the symbolic
+     link, otherwise zero. */
+  char *realname;
+  struct pending *next;
+};
+
+struct pending *pending_dirs;
+
+/* Current time (seconds since 1970).  When we are printing a file's time,
+   include the year if it is more than 6 months before this time.  */
+
+time_t current_time;
+
+/* The number of digits to use for block sizes.
+   4, or more if needed for bigger numbers.  */
+
+int block_size_size;
+
+/* The name the program was run with, stripped of any leading path. */
+char *program_name;
+\f
+/* Option flags */
+
+/* long_format for lots of info, one per line.
+   one_per_line for just names, one per line.
+   many_per_line for just names, many per line, sorted vertically.
+   horizontal for just names, many per line, sorted horizontally.
+   with_commas for just names, many per line, separated by commas.
+
+   -l, -1, -C, -x and -m control this parameter.  */
+
+enum format
+{
+  long_format,                 /* -l */
+  one_per_line,                        /* -1 */
+  many_per_line,               /* -C */
+  horizontal,                  /* -x */
+  with_commas                  /* -m */
+};
+
+enum format format;
+
+/* Type of time to print or sort by.  Controlled by -c and -u.  */
+
+enum time_type
+{
+  time_mtime,                  /* default */
+  time_ctime,                  /* -c */
+  time_atime                   /* -u */
+};
+
+enum time_type time_type;
+
+/* The file characteristic to sort by.  Controlled by -t, -S, -U, -X. */
+
+enum sort_type
+{
+  sort_none,                   /* -U */
+  sort_name,                   /* default */
+  sort_extension,              /* -X */
+  sort_time,                   /* -t */
+  sort_size                    /* -S */
+};
+
+enum sort_type sort_type;
+
+/* Direction of sort.
+   0 means highest first if numeric,
+   lowest first if alphabetic;
+   these are the defaults.
+   1 means the opposite order in each case.  -r  */
+
+int sort_reverse;
+
+/* Nonzero means print the user and group id's as numbers rather
+   than as names.  -n  */
+
+int numeric_users;
+
+/* Nonzero means mention the size in 512 byte blocks of each file.  -s  */
+
+int print_block_size;
+
+/* Nonzero means show file sizes in kilobytes instead of blocks
+   (the size of which is system-dependant).  -k */
+
+int kilobyte_blocks;
+
+/* none means don't mention the type of files.
+   all means mention the types of all files.
+   not_programs means do so except for executables.
+
+   Controlled by -F and -p.  */
+
+enum indicator_style
+{
+  none,                                /* default */
+  all,                         /* -F */
+  not_programs                 /* -p */
+};
+
+enum indicator_style indicator_style;
+
+/* Nonzero means mention the inode number of each file.  -i  */
+
+int print_inode;
+
+/* Nonzero means when a symbolic link is found, display info on
+   the file linked to.  -L  */
+
+int trace_links;
+
+/* Nonzero means when a directory is found, display info on its
+   contents.  -R  */
+
+int trace_dirs;
+
+/* Nonzero means when an argument is a directory name, display info
+   on it itself.  -d  */
+
+int immediate_dirs;
+
+/* Nonzero means don't omit files whose names start with `.'.  -A */
+
+int all_files;
+
+/* Nonzero means don't omit files `.' and `..'
+   This flag implies `all_files'.  -a  */
+
+int really_all_files;
+
+/* A linked list of shell-style globbing patterns.  If a non-argument
+   file name matches any of these patterns, it is omitted.
+   Controlled by -I.  Multiple -I options accumulate.
+   The -B option adds `*~' and `.*~' to this list.  */
+
+struct ignore_pattern
+{
+  char *pattern;
+  struct ignore_pattern *next;
+};
+
+struct ignore_pattern *ignore_patterns;
+
+/* Nonzero means quote nongraphic chars in file names.  -b  */
+
+int quote_funny_chars;
+
+/* Nonzero means output nongraphic chars in file names as `?'.  -q  */
+
+int qmark_funny_chars;
+
+/* Nonzero means output each file name using C syntax for a string.
+   Always accompanied by `quote_funny_chars'.
+   This mode, together with -x or -C or -m,
+   and without such frills as -F or -s,
+   is guaranteed to make it possible for a program receiving
+   the output to tell exactly what file names are present.  -Q  */
+
+int quote_as_string;
+
+/* The number of chars per hardware tab stop.  -T */
+int tabsize;
+
+/* Nonzero means we are listing the working directory because no
+   non-option arguments were given. */
+
+int dir_defaulted;
+
+/* Nonzero means print each directory name before listing it. */
+
+int print_dir_name;
+
+/* The line length to use for breaking lines in many-per-line format.
+   Can be set with -w.  */
+
+int line_length;
+
+/* If nonzero, the file listing format requires that stat be called on
+   each file. */
+
+int format_needs_stat;
+
+/* The exit status to use if we don't get any fatal errors. */
+
+int exit_status;
+\f
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  register int i;
+  register struct pending *thispend;
+
+  exit_status = 0;
+  dir_defaulted = 1;
+  print_dir_name = 1;
+  pending_dirs = 0;
+  current_time = time ((time_t *) 0);
+
+  program_name = argv[0];
+  i = decode_switches (argc, argv);
+
+  format_needs_stat = sort_type == sort_time || sort_type == sort_size
+    || format == long_format
+    || trace_links || trace_dirs || indicator_style != none
+    || print_block_size || print_inode;
+
+  nfiles = 100;
+  files = (struct file *) xmalloc (sizeof (struct file) * nfiles);
+  files_index = 0;
+
+  clear_files ();
+
+  if (i < argc)
+    dir_defaulted = 0;
+  for (; i < argc; i++)
+    gobble_file (argv[i], 1, "");
+
+  if (dir_defaulted)
+    {
+      if (immediate_dirs)
+       gobble_file (".", 1, "");
+      else
+       queue_directory (".", 0);
+    }
+
+  if (files_index)
+    {
+      sort_files ();
+      if (!immediate_dirs)
+       extract_dirs_from_files ("", 0);
+      /* `files_index' might be zero now.  */
+    }
+  if (files_index)
+    {
+      print_current_files ();
+      if (pending_dirs)
+       putchar ('\n');
+    }
+  else if (pending_dirs && pending_dirs->next == 0)
+    print_dir_name = 0;
+
+  while (pending_dirs)
+    {
+      thispend = pending_dirs;
+      pending_dirs = pending_dirs->next;
+      print_dir (thispend->name, thispend->realname);
+      free (thispend->name);
+      if (thispend->realname)
+       free (thispend->realname);
+      free (thispend);
+      print_dir_name = 1;
+    }
+
+  exit (exit_status);
+}
+\f
+struct option long_options[] =
+{
+  {"all", 0, 0, 'a'},
+  {"escape", 0, 0, 'b'},
+  {"directory", 0, 0, 'd'},
+  {"inode", 0, 0, 'i'},
+  {"kilobytes", 0, 0, 'k'},
+  {"numeric-uid-gid", 0, 0, 'n'},
+  {"hide-control-chars", 0, 0, 'q'},
+  {"reverse", 0, 0, 'r'},
+  {"size", 0, 0, 's'},
+  {"width", 1, 0, 'w'},
+  {"almost-all", 0, 0, 'A'},
+  {"ignore-backups", 0, 0, 'B'},
+  {"classify", 0, 0, 'F'},
+  {"file-type", 0, 0, 'F'},
+  {"ignore", 1, 0, 'I'},
+  {"dereference", 0, 0, 'L'},
+  {"literal", 0, 0, 'N'},
+  {"quote-name", 0, 0, 'Q'},
+  {"recursive", 0, 0, 'R'},
+  {"format", 1, 0, 12},
+  {"sort", 1, 0, 10},
+  {"tabsize", 1, 0, 'T'},
+  {"time", 1, 0, 11},
+  {0, 0, 0, 0}
+};
+
+char *format_args[] =
+{
+  "verbose", "long", "commas", "horizontal", "across",
+  "vertical", "single-column", 0
+};
+
+enum format formats[] =
+{
+  long_format, long_format, with_commas, horizontal, horizontal,
+  many_per_line, one_per_line
+};
+
+char *sort_args[] =
+{
+  "none", "time", "size", "extension", 0
+};
+
+enum sort_type sort_types[] =
+{
+  sort_none, sort_time, sort_size, sort_extension
+};
+
+char *time_args[] =
+{
+  "atime", "access", "use", "ctime", "status", 0
+};
+
+enum time_type time_types[] =
+{
+  time_atime, time_atime, time_atime, time_ctime, time_ctime
+};
+
+/* Set all the option flags according to the switches specified.
+   Return the index of the first non-option argument.  */
+
+int
+decode_switches (argc, argv)
+     int argc;
+     char **argv;
+{
+  register char *p;
+  int c;
+  int i;
+
+  qmark_funny_chars = 0;
+  quote_funny_chars = 0;
+
+  /* initialize all switches to default settings */
+
+#ifdef MULTI_COL
+  /* This is for the `dir' program.  */
+  format = many_per_line;
+  quote_funny_chars = 1;
+#else
+#ifdef LONG_FORMAT
+  /* This is for the `vdir' program.  */
+  format = long_format;
+  quote_funny_chars = 1;
+#else
+  /* This is for the `ls' program.  */
+  if (isatty (1))
+    {
+      format = many_per_line;
+      qmark_funny_chars = 1;
+    }
+  else
+    {
+      format = one_per_line;
+      qmark_funny_chars = 0;
+    }
+#endif
+#endif
+
+  time_type = time_mtime;
+  sort_type = sort_name;
+  sort_reverse = 0;
+  numeric_users = 0;
+  print_block_size = 0;
+  kilobyte_blocks = getenv ("POSIXLY_CORRECT") == 0;
+  indicator_style = none;
+  print_inode = 0;
+  trace_links = 0;
+  trace_dirs = 0;
+  immediate_dirs = 0;
+  all_files = 0;
+  really_all_files = 0;
+  ignore_patterns = 0;
+  quote_as_string = 0;
+
+  p = getenv ("COLUMNS");
+  line_length = p ? atoi (p) : 80;
+
+#ifdef TIOCGWINSZ
+  {
+    struct winsize ws;
+
+    if (ioctl (1, TIOCGWINSZ, &ws) != -1 && ws.ws_col != 0)
+      line_length = ws.ws_col;
+  }
+#endif
+
+  p = getenv ("TABSIZE");
+  tabsize = p ? atoi (p) : 8;
+
+  while ((c = getopt_long (argc, argv, "abcdgiklmnpqrstuw:xABCFI:LNQRST:UX1",
+                          long_options, (int *) 0)) != EOF)
+    {
+      switch (c)
+       {
+       case 'a':
+         all_files = 1;
+         really_all_files = 1;
+         break;
+         
+       case 'b':
+         quote_funny_chars = 1;
+         qmark_funny_chars = 0;
+         break;
+         
+       case 'c':
+         time_type = time_ctime;
+         break;
+         
+       case 'd':
+         immediate_dirs = 1;
+         break;
+         
+       case 'g':
+         /* No effect.  For BSD compatibility. */
+         break;
+
+       case 'i':
+         print_inode = 1;
+         break;
+         
+       case 'k':
+         kilobyte_blocks = 1;
+         break;
+         
+       case 'l':
+         format = long_format;
+         break;
+         
+       case 'm':
+         format = with_commas;
+         break;
+         
+       case 'n':
+         numeric_users = 1;
+         break;
+         
+       case 'p':
+         indicator_style = not_programs;
+         break;
+         
+       case 'q':
+         qmark_funny_chars = 1;
+         quote_funny_chars = 0;
+         break;
+         
+       case 'r':
+         sort_reverse = 1;
+         break;
+         
+       case 's':
+         print_block_size = 1;
+         break;
+         
+       case 't':
+         sort_type = sort_time;
+         break;
+         
+       case 'u':
+         time_type = time_atime;
+         break;
+         
+       case 'w':
+         line_length = atoi (optarg);
+         if (line_length < 1)
+           error (1, 0, "invalid line width: %s", optarg);
+         break;
+         
+       case 'x':
+         format = horizontal;
+         break;
+         
+       case 'A':
+         all_files = 1;
+         break;
+         
+       case 'B':
+         add_ignore_pattern ("*~");
+         add_ignore_pattern (".*~");
+         break;
+         
+       case 'C':
+         format = many_per_line;
+         break;
+         
+       case 'F':
+         indicator_style = all;
+         break;
+         
+       case 'I':
+         add_ignore_pattern (optarg);
+         break;
+         
+       case 'L':
+         trace_links = 1;
+         break;
+         
+       case 'N':
+         quote_funny_chars = 0;
+         qmark_funny_chars = 0;
+         break;
+         
+       case 'Q':
+         quote_as_string = 1;
+         quote_funny_chars = 1;
+         qmark_funny_chars = 0;
+         break;
+         
+       case 'R':
+         trace_dirs = 1;
+         break;
+         
+       case 'S':
+         sort_type = sort_size;
+         break;
+         
+       case 'T':
+         tabsize = atoi (optarg);
+         if (tabsize < 1)
+           error (1, 0, "invalid tab size: %s", optarg);
+         break;
+
+       case 'U':
+         sort_type = sort_none;
+         break;
+
+       case 'X':
+         sort_type = sort_extension;
+         break;
+
+       case '1':
+         format = one_per_line;
+         break;
+         
+       case 10:                /* +sort */
+         i = argmatch (optarg, sort_args);
+         if (i < 0)
+           {
+             invalid_arg ("sort type", optarg, i);
+             usage ();
+           }
+         sort_type = sort_types[i];
+         break;
+
+       case 11:                /* +time */
+         i = argmatch (optarg, time_args);
+         if (i < 0)
+           {
+             invalid_arg ("time type", optarg, i);
+             usage ();
+           }
+         time_type = time_types[i];
+         break;
+
+       case 12:                /* +format */
+         i = argmatch (optarg, format_args);
+         if (i < 0)
+           {
+             invalid_arg ("format type", optarg, i);
+             usage ();
+           }
+         format = formats[i];
+         break;
+         
+       default:
+         usage ();
+       }
+    }
+
+  return optind;
+}
+\f
+/* Request that the directory named `name' have its contents listed later.
+   If `realname' is nonzero, it will be used instead of `name' when the
+   directory name is printed.  This allows symbolic links to directories
+   to be treated as regular directories but still be listed under their
+   real names. */
+
+void
+queue_directory (name, realname)
+     char *name;
+     char *realname;
+{
+  struct pending *new;
+
+  new = (struct pending *) xmalloc (sizeof (struct pending));
+  new->next = pending_dirs;
+  pending_dirs = new;
+  new->name = xstrdup (name);
+  if (realname)
+    new->realname = xstrdup (realname);
+  else
+    new->realname = 0;
+}
+
+/* Read directory `name', and list the files in it.
+   If `realname' is nonzero, print its name instead of `name';
+   this is used for symbolic links to directories. */
+
+void
+print_dir (name, realname)
+     char *name;
+     char *realname;
+{
+  register DIR *reading;
+  register struct direct *next;
+  register int total_blocks = 0;
+
+  errno = 0;
+  reading = opendir (name);
+  if (!reading)
+    {
+      error (0, errno, "%s", name);
+      exit_status = 1;
+      return;
+    }
+
+  /* Read the directory entries, and insert the subfiles into the `files'
+     table.  */
+
+  clear_files ();
+
+  while (next = readdir (reading))
+    if (file_interesting (next))
+      total_blocks += gobble_file (next->d_name, 0, name);
+
+  if (CLOSEDIR (reading))
+    {
+      error (0, errno, "%s", name);
+      exit_status = 1;
+      /* Don't return; print whatever we got. */
+    }
+
+  /* Sort the directory contents.  */
+  sort_files ();
+
+  /* If any member files are subdirectories, perhaps they should have their
+     contents listed rather than being mentioned here as files.  */
+
+  if (trace_dirs)
+    extract_dirs_from_files (name, 1);
+
+  if (print_dir_name)
+    {
+      if (realname)
+       printf ("%s:\n", realname);
+      else
+       printf ("%s:\n", name);
+    }
+
+  if (format == long_format || print_block_size)
+    printf ("total %u\n", total_blocks);
+
+  if (files_index)
+    print_current_files ();
+
+  if (pending_dirs)
+    putchar ('\n');
+}
+\f
+/* Add `pattern' to the list of patterns for which files that match are
+   not listed.  */
+
+void
+add_ignore_pattern (pattern)
+     char *pattern;
+{
+  register struct ignore_pattern *ignore;
+
+  ignore = (struct ignore_pattern *) xmalloc (sizeof (struct ignore_pattern));
+  ignore->pattern = pattern;
+  /* Add it to the head of the linked list. */
+  ignore->next = ignore_patterns;
+  ignore_patterns = ignore;
+}
+
+/* Return nonzero if the file in `next' should be listed. */
+
+int
+file_interesting (next)
+     register struct direct *next;
+{
+  register struct ignore_pattern *ignore;
+
+  for (ignore = ignore_patterns; ignore; ignore = ignore->next)
+    if (fnmatch (ignore->pattern, next->d_name, FNM_PERIOD) == 0)
+      return 0;
+
+  if (really_all_files
+      || next->d_name[0] != '.'
+      || (all_files
+         && next->d_name[1] != '\0'
+         && (next->d_name[1] != '.' || next->d_name[2] != '\0')))
+    return 1;
+
+  return 0;
+}
+\f
+/* Enter and remove entries in the table `files'.  */
+
+/* Empty the table of files. */
+
+void
+clear_files ()
+{
+  register int i;
+
+  for (i = 0; i < files_index; i++)
+    {
+      free (files[i].name);
+      if (files[i].linkname)
+       free (files[i].linkname);
+    }
+
+  files_index = 0;
+  block_size_size = 4;
+}
+
+/* Add a file to the current table of files.
+   Verify that the file exists, and print an error message if it does not.
+   Return the number of blocks that the file occupies.  */
+
+int
+gobble_file (name, explicit_arg, dirname)
+     char *name;
+     int explicit_arg;
+     char *dirname;
+{
+  register int blocks;
+  register int val;
+  register char *path;
+
+  if (files_index == nfiles)
+    {
+      nfiles *= 2;
+      files = (struct file *) xrealloc (files, sizeof (struct file) * nfiles);
+    }
+
+  files[files_index].linkname = 0;
+  files[files_index].linkmode = 0;
+
+  if (explicit_arg || format_needs_stat)
+    {
+      /* `path' is the absolute pathname of this file. */
+
+      if (name[0] == '/' || dirname[0] == 0)
+       path = name;
+      else
+       {
+         path = (char *) alloca (strlen (name) + strlen (dirname) + 2);
+         attach (path, dirname, name);
+       }
+
+      if (trace_links)
+       {
+         val = stat (path, &files[files_index].stat);
+         if (val < 0)
+           /* Perhaps a symbolically-linked to file doesn't exist; stat
+              the link instead. */
+           val = lstat (path, &files[files_index].stat);
+       }
+      else
+       val = lstat (path, &files[files_index].stat);
+      if (val < 0)
+       {
+         error (0, errno, "%s", path);
+         exit_status = 1;
+         return 0;
+       }
+
+#ifdef S_ISLNK
+      if (S_ISLNK (files[files_index].stat.st_mode)
+         && (explicit_arg || format == long_format))
+       {
+         char *linkpath;
+         struct stat linkstats;
+
+         get_link_name (path, &files[files_index]);
+         linkpath = make_link_path (path, files[files_index].linkname);
+
+         if (linkpath && stat (linkpath, &linkstats) == 0)
+           {
+             /* Symbolic links to directories that are mentioned on the
+                command line are automatically traced if not being
+                listed as files.  */
+             if (explicit_arg && format != long_format
+                 && S_ISDIR (linkstats.st_mode))
+               {
+                 /* Substitute the linked-to directory's name, but
+                    save the real name in `linkname' for printing.  */
+                 if (!immediate_dirs)
+                   {
+                     char *tempname = name;
+                     name = linkpath;
+                     linkpath = files[files_index].linkname;
+                     files[files_index].linkname = tempname;
+                   }
+                 files[files_index].stat = linkstats;
+               }
+             else
+               /* Get the linked-to file's mode for the filetype indicator
+                  in long listings.  */
+               files[files_index].linkmode = linkstats.st_mode;
+           }
+         if (linkpath)
+           free (linkpath);
+       }
+#endif
+
+#ifdef S_ISLNK
+      if (S_ISLNK (files[files_index].stat.st_mode))
+       files[files_index].filetype = symbolic_link;
+      else
+#endif
+       if (S_ISDIR (files[files_index].stat.st_mode))
+         {
+           if (explicit_arg && !immediate_dirs)
+             files[files_index].filetype = arg_directory;
+           else
+             files[files_index].filetype = directory;
+         }
+       else
+         files[files_index].filetype = normal;
+
+      blocks = convert_blocks (ST_NBLOCKS (files[files_index].stat),
+                              kilobyte_blocks);
+      if (blocks >= 10000 && block_size_size < 5)
+       block_size_size = 5;
+      if (blocks >= 100000 && block_size_size < 6)
+       block_size_size = 6;
+      if (blocks >= 1000000 && block_size_size < 7)
+       block_size_size = 7;
+    }
+  else
+    blocks = 0;
+
+  files[files_index].name = xstrdup (name);
+  files_index++;
+
+  return blocks;
+}
+
+#ifdef S_ISLNK
+
+/* Put the name of the file that `filename' is a symbolic link to
+   into the `linkname' field of `f'. */
+
+void
+get_link_name (filename, f)
+     char *filename;
+     struct file *f;
+{
+  register char *linkbuf;
+#ifdef _AIX
+  register int bufsiz = PATH_MAX; /* st_size is wrong. */
+#else
+  register int bufsiz = f->stat.st_size;
+#endif
+  register int linksize;
+
+  linkbuf = (char *) xmalloc (bufsiz + 1);
+  /* Some automounters give incorrect st_size for mount points.
+     I can't think of a good workaround for it, though.  */
+  linksize = readlink (filename, linkbuf, bufsiz);
+  if (linksize < 0)
+    {
+      error (0, errno, "%s", filename);
+      exit_status = 1;
+      free (linkbuf);
+    }
+  else
+    {
+#ifdef _AIX
+      linkbuf = (char *) xrealloc (linkbuf, linksize + 1);
+#endif
+      linkbuf[linksize] = '\0';
+      f->linkname = linkbuf;
+    }
+}
+
+/* If `linkname' is a relative path and `path' contains one or more
+   leading directories, return `linkname' with those directories
+   prepended; otherwise, return a copy of `linkname'.
+   If `linkname' is zero, return zero. */
+
+char *
+make_link_path (path, linkname)
+     char *path;
+     char *linkname;
+{
+  char *linkbuf;
+  int bufsiz;
+
+  if (linkname == 0)
+    return 0;
+
+  if (*linkname == '/')
+    return xstrdup (linkname);
+
+  /* The link is to a relative path.  Prepend any leading path
+     in `path' to the link name. */
+  linkbuf = rindex (path, '/');
+  if (linkbuf == 0)
+    return xstrdup (linkname);
+
+  bufsiz = linkbuf - path + 1;
+  linkbuf = xmalloc (bufsiz + strlen (linkname) + 1);
+  strncpy (linkbuf, path, bufsiz);
+  strcpy (linkbuf + bufsiz, linkname);
+  return linkbuf;
+}
+#endif
+
+/* Remove any entries from `files' that are for directories,
+   and queue them to be listed as directories instead.
+   `dirname' is the prefix to prepend to each dirname
+   to make it correct relative to ls's working dir.
+   `recursive' is nonzero if we should not treat `.' and `..' as dirs.
+   This is desirable when processing directories recursively.  */
+
+void
+extract_dirs_from_files (dirname, recursive)
+     char *dirname;
+     int recursive;
+{
+  register int i, j;
+  register char *path;
+  int dirlen;
+
+  dirlen = strlen (dirname) + 2;
+  /* Queue the directories last one first, because queueing reverses the
+     order.  */
+  for (i = files_index - 1; i >= 0; i--)
+    if ((files[i].filetype == directory || files[i].filetype == arg_directory)
+       && (!recursive || is_not_dot_or_dotdot (files[i].name)))
+      {
+       if (files[i].name[0] == '/' || dirname[0] == 0)
+         {
+           queue_directory (files[i].name, files[i].linkname);
+         }
+       else
+         {
+           path = (char *) xmalloc (strlen (files[i].name) + dirlen);
+           attach (path, dirname, files[i].name);
+           queue_directory (path, files[i].linkname);
+           free (path);
+         }
+       if (files[i].filetype == arg_directory)
+         free (files[i].name);
+      }
+
+  /* Now delete the directories from the table, compacting all the remaining
+     entries.  */
+
+  for (i = 0, j = 0; i < files_index; i++)
+    if (files[i].filetype != arg_directory)
+      files[j++] = files[i];
+  files_index = j;
+}
+\f
+/* Return non-zero if `name' doesn't end in `.' or `..'
+   This is so we don't try to recurse on `././././. ...' */
+
+int
+is_not_dot_or_dotdot (name)
+     char *name;
+{
+  char *t;
+
+  t = rindex (name, '/');
+  if (t)
+    name = t + 1;
+
+  if (name[0] == '.'
+      && (name[1] == '\0'
+         || (name[1] == '.' && name[2] == '\0')))
+    return 0;
+
+  return 1;
+}
+\f
+/* Sort the files now in the table.  */
+
+void
+sort_files ()
+{
+  int (*func) ();
+
+  switch (sort_type)
+    {
+    case sort_none:
+      return;
+    case sort_time:
+      switch (time_type)
+       {
+       case time_ctime:
+         func = sort_reverse ? rev_cmp_ctime : compare_ctime;
+         break;
+       case time_mtime:
+         func = sort_reverse ? rev_cmp_mtime : compare_mtime;
+         break;
+       case time_atime:
+         func = sort_reverse ? rev_cmp_atime : compare_atime;
+         break;
+       }
+      break;
+    case sort_name:
+      func = sort_reverse ? rev_cmp_name : compare_name;
+      break;
+    case sort_extension:
+      func = sort_reverse ? rev_cmp_extension : compare_extension;
+      break;
+    case sort_size:
+      func = sort_reverse ? rev_cmp_size : compare_size;
+      break;
+    }
+
+  qsort (files, files_index, sizeof (struct file), func);
+}
+
+/* Comparison routines for sorting the files. */
+
+int
+compare_ctime (file1, file2)
+     struct file *file1, *file2;
+{
+  return longdiff (file2->stat.st_ctime, file1->stat.st_ctime);
+}
+
+int
+rev_cmp_ctime (file2, file1)
+     struct file *file1, *file2;
+{
+  return longdiff (file2->stat.st_ctime, file1->stat.st_ctime);
+}
+
+int
+compare_mtime (file1, file2)
+     struct file *file1, *file2;
+{
+  return longdiff (file2->stat.st_mtime, file1->stat.st_mtime);
+}
+
+int
+rev_cmp_mtime (file2, file1)
+     struct file *file1, *file2;
+{
+  return longdiff (file2->stat.st_mtime, file1->stat.st_mtime);
+}
+
+int
+compare_atime (file1, file2)
+     struct file *file1, *file2;
+{
+  return longdiff (file2->stat.st_atime, file1->stat.st_atime);
+}
+
+int
+rev_cmp_atime (file2, file1)
+     struct file *file1, *file2;
+{
+  return longdiff (file2->stat.st_atime, file1->stat.st_atime);
+}
+
+int
+compare_size (file1, file2)
+     struct file *file1, *file2;
+{
+  return longdiff (file2->stat.st_size, file1->stat.st_size);
+}
+
+int
+rev_cmp_size (file2, file1)
+     struct file *file1, *file2;
+{
+  return longdiff (file2->stat.st_size, file1->stat.st_size);
+}
+
+int
+compare_name (file1, file2)
+     struct file *file1, *file2;
+{
+  return strcmp (file1->name, file2->name);
+}
+
+int
+rev_cmp_name (file2, file1)
+     struct file *file1, *file2;
+{
+  return strcmp (file1->name, file2->name);
+}
+
+/* Compare file extensions.  Files with no extension are `smallest'.
+   If extensions are the same, compare by filenames instead. */
+
+int
+compare_extension (file1, file2)
+     struct file *file1, *file2;
+{
+  register char *base1, *base2;
+  register int cmp;
+
+  base1 = rindex (file1->name, '.');
+  base2 = rindex (file2->name, '.');
+  if (base1 == 0 && base2 == 0)
+    return strcmp (file1->name, file2->name);
+  if (base1 == 0)
+    return -1;
+  if (base2 == 0)
+    return 1;
+  cmp = strcmp (base1, base2);
+  if (cmp == 0)
+    return strcmp (file1->name, file2->name);
+  return cmp;
+}
+
+int
+rev_cmp_extension (file2, file1)
+     struct file *file1, *file2;
+{
+  register char *base1, *base2;
+  register int cmp;
+
+  base1 = rindex (file1->name, '.');
+  base2 = rindex (file2->name, '.');
+  if (base1 == 0 && base2 == 0)
+    return strcmp (file1->name, file2->name);
+  if (base1 == 0)
+    return -1;
+  if (base2 == 0)
+    return 1;
+  cmp = strcmp (base1, base2);
+  if (cmp == 0)
+    return strcmp (file1->name, file2->name);
+  return cmp;
+}
+\f
+/* List all the files now in the table.  */
+
+void
+print_current_files ()
+{
+  register int i;
+
+  switch (format)
+    {
+    case one_per_line:
+      for (i = 0; i < files_index; i++)
+       {
+         print_file_name_and_frills (files + i);
+         putchar ('\n');
+       }
+      break;
+
+    case many_per_line:
+      print_many_per_line ();
+      break;
+
+    case horizontal:
+      print_horizontal ();
+      break;
+
+    case with_commas:
+      print_with_commas ();
+      break;
+
+    case long_format:
+      for (i = 0; i < files_index; i++)
+       {
+         print_long_format (files + i);
+         putchar ('\n');
+       }
+      break;
+    }
+}
+
+void
+print_long_format (f)
+     struct file *f;
+{
+  char modebuf[20];
+  char timebuf[40];
+  time_t when;
+
+  mode_string (f->stat.st_mode, modebuf);
+  modebuf[10] = 0;
+
+  switch (time_type)
+    {
+    case time_ctime:
+      when = f->stat.st_ctime;
+      break;
+    case time_mtime:
+      when = f->stat.st_mtime;
+      break;
+    case time_atime:
+      when = f->stat.st_atime;
+      break;
+    }
+
+  strcpy (timebuf, ctime (&when));
+  if (current_time > when + 6L * 30L * 24L * 60L * 60L /* Old. */
+      || current_time < when - 60L * 60L) /* In the future. */
+    {
+      /* The file is fairly old or in the future.
+        POSIX says the cutoff is 6 months old;
+        approximate this by 6*30 days.
+        Allow a 1 hour slop factor for what is considered "the future",
+        to allow for NFS server/client clock disagreement.
+        Show the year instead of the time of day.  */
+      strcpy (timebuf + 11, timebuf + 19);
+    }
+  timebuf[16] = 0;
+
+  if (print_inode)
+    printf ("%6u ", f->stat.st_ino);
+
+  if (print_block_size)
+    printf ("%*u ", block_size_size,
+           convert_blocks (ST_NBLOCKS (f->stat), kilobyte_blocks));
+
+  /* The space between the mode and the number of links is the POSIX
+     "optional alternate access method flag". */
+  printf ("%s %3u ", modebuf, f->stat.st_nlink);
+
+  if (numeric_users)
+    printf ("%-8u ", (unsigned int) f->stat.st_uid);
+  else
+    printf ("%-8.8s ", getuser (f->stat.st_uid));
+
+  if (numeric_users)
+    printf ("%-8u ", (unsigned int) f->stat.st_gid);
+  else
+    printf ("%-8.8s ", getgroup (f->stat.st_gid));
+
+  if (S_ISCHR (f->stat.st_mode) || S_ISBLK (f->stat.st_mode))
+    printf ("%3u, %3u ", major (f->stat.st_rdev),
+           minor (f->stat.st_rdev));
+  else
+    printf ("%8lu ", f->stat.st_size);
+
+  printf ("%s ", timebuf + 4);
+
+  print_name_with_quoting (f->name);
+
+  if (f->filetype == symbolic_link)
+    {
+      if (f->linkname)
+       {
+         fputs (" -> ", stdout);
+         print_name_with_quoting (f->linkname);
+         if (indicator_style != none)
+           print_type_indicator (f->linkmode);
+       }
+    }
+  else if (indicator_style != none)
+    print_type_indicator (f->stat.st_mode);
+}
+\f
+void
+print_name_with_quoting (p)
+     register char *p;
+{
+  register unsigned char c;
+
+  if (quote_as_string)
+    putchar ('"');
+
+  while (c = *p++)
+    {
+      if (quote_funny_chars)
+       {
+         switch (c)
+           {
+           case '\\':
+             printf ("\\\\");
+             break;
+
+           case '\n':
+             printf ("\\n");
+             break;
+
+           case '\b':
+             printf ("\\b");
+             break;
+
+           case '\r':
+             printf ("\\r");
+             break;
+
+           case '\t':
+             printf ("\\t");
+             break;
+
+           case '\f':
+             printf ("\\f");
+             break;
+
+           case ' ':
+             printf ("\\ ");
+             break;
+
+           case '"':
+             printf ("\\\"");
+             break;
+
+           default:
+             if (c > 040 && c < 0177)
+               putchar (c);
+             else
+               printf ("\\%03o", (unsigned int) c);
+           }
+       }
+      else
+       {
+         if (c >= 040 && c < 0177)
+           putchar (c);
+         else if (!qmark_funny_chars)
+           putchar (c);
+         else
+           putchar ('?');
+       }
+    }
+
+  if (quote_as_string)
+    putchar ('"');
+}
+\f
+/* Print the file name of `f' with appropriate quoting.
+   Also print file size, inode number, and filetype indicator character,
+   as requested by switches.  */
+
+void
+print_file_name_and_frills (f)
+     struct file *f;
+{
+  if (print_inode)
+    printf ("%6u ", f->stat.st_ino);
+
+  if (print_block_size)
+    printf ("%*u ", block_size_size,
+           convert_blocks (ST_NBLOCKS (f->stat), kilobyte_blocks));
+
+  print_name_with_quoting (f->name);
+
+  if (indicator_style != none)
+    print_type_indicator (f->stat.st_mode);
+}
+
+void
+print_type_indicator (mode)
+     unsigned int mode;
+{
+  if (S_ISDIR (mode))
+    putchar ('/');
+
+#ifdef S_ISLNK
+  if (S_ISLNK (mode))
+    putchar ('@');
+#endif
+
+#ifdef S_ISFIFO
+  if (S_ISFIFO (mode))
+    putchar ('|');
+#endif
+
+#ifdef S_ISSOCK
+  if (S_ISSOCK (mode))
+    putchar ('=');
+#endif
+
+  if (S_ISREG (mode) && indicator_style == all
+      && (mode & (S_IEXEC | S_IEXEC >> 3 | S_IEXEC >> 6)))
+    putchar ('*');
+}
+
+int
+length_of_file_name_and_frills (f)
+     struct file *f;
+{
+  register char *p = f->name;
+  register char c;
+  register int len = 0;
+
+  if (print_inode)
+    len += 7;
+
+  if (print_block_size)
+    len += 1 + block_size_size;
+
+  if (quote_as_string)
+    len += 2;
+
+  while (c = *p++)
+    {
+      if (quote_funny_chars)
+       {
+         switch (c)
+           {
+           case '\\':
+           case '\n':
+           case '\b':
+           case '\r':
+           case '\t':
+           case '\f':
+           case ' ':
+             len += 2;
+             break;
+
+           case '"':
+             if (quote_as_string)
+               len += 2;
+             else
+               len += 1;
+             break;
+
+           default:
+             if (c >= 040 && c < 0177)
+               len += 1;
+             else
+               len += 4;
+           }
+       }
+      else
+       len += 1;
+    }
+
+  if (indicator_style != none)
+    {
+      unsigned filetype = f->stat.st_mode;
+
+      if (S_ISREG (filetype))
+       {
+         if (indicator_style == all
+             && (f->stat.st_mode & (S_IEXEC | S_IEXEC >> 3 | S_IEXEC >> 6)))
+           len += 1;
+       }
+      else if (S_ISDIR (filetype)
+#ifdef S_ISLNK
+              || S_ISLNK (filetype)
+#endif
+#ifdef S_ISFIFO
+              || S_ISFIFO (filetype)
+#endif
+#ifdef S_ISSOCK
+              || S_ISSOCK (filetype)
+#endif
+              )
+       len += 1;
+    }
+
+  return len;
+}
+\f
+void
+print_many_per_line ()
+{
+  int filesno;                 /* Index into files. */
+  int row;                     /* Current row. */
+  int max_name_length;         /* Length of longest file name + frills. */
+  int name_length;             /* Length of each file name + frills. */
+  int pos;                     /* Current character column. */
+  int cols;                    /* Number of files across. */
+  int rows;                    /* Maximum number of files down. */
+
+  /* Compute the maximum file name length.  */
+  max_name_length = 0;
+  for (filesno = 0; filesno < files_index; filesno++)
+    {
+      name_length = length_of_file_name_and_frills (files + filesno);
+      if (name_length > max_name_length)
+       max_name_length = name_length;
+    }
+
+  /* Allow at least two spaces between names.  */
+  max_name_length += 2;
+
+  /* Calculate the maximum number of columns that will fit. */
+  cols = line_length / max_name_length;
+  if (cols == 0)
+    cols = 1;
+  /* Calculate the number of rows that will be in each column except possibly
+     for a short column on the right. */
+  rows = files_index / cols + (files_index % cols != 0);
+  /* Recalculate columns based on rows. */
+  cols = files_index / rows + (files_index % rows != 0);
+
+  for (row = 0; row < rows; row++)
+    {
+      filesno = row;
+      pos = 0;
+      /* Print the next row.  */
+      while (1)
+       {
+         print_file_name_and_frills (files + filesno);
+         name_length = length_of_file_name_and_frills (files + filesno);
+
+         filesno += rows;
+         if (filesno >= files_index)
+           break;
+
+         indent (pos + name_length, pos + max_name_length);
+         pos += max_name_length;
+       }
+      putchar ('\n');
+    }
+}
+\f
+void
+print_horizontal ()
+{
+  int filesno;
+  int max_name_length;
+  int name_length;
+  int cols;
+  int pos;
+
+  /* Compute the maximum file name length.  */
+  max_name_length = 0;
+  for (filesno = 0; filesno < files_index; filesno++)
+    {
+      name_length = length_of_file_name_and_frills (files + filesno);
+      if (name_length > max_name_length)
+       max_name_length = name_length;
+    }
+
+  /* Allow two spaces between names.  */
+  max_name_length += 2;
+
+  cols = line_length / max_name_length;
+  if (cols == 0)
+    cols = 1;
+
+  pos = 0;
+  name_length = 0;
+
+  for (filesno = 0; filesno < files_index; filesno++)
+    {
+      if (filesno != 0)
+       {
+         if (filesno % cols == 0)
+           {
+             putchar ('\n');
+             pos = 0;
+           }
+         else
+           {
+             indent (pos + name_length, pos + max_name_length);
+             pos += max_name_length;
+           }
+       }
+
+      print_file_name_and_frills (files + filesno);
+
+      name_length = length_of_file_name_and_frills (files + filesno);
+    }
+  putchar ('\n');
+}
+\f
+void
+print_with_commas ()
+{
+  int filesno;
+  int pos, old_pos;
+
+  pos = 0;
+
+  for (filesno = 0; filesno < files_index; filesno++)
+    {
+      old_pos = pos;
+
+      pos += length_of_file_name_and_frills (files + filesno);
+      if (filesno + 1 < files_index)
+       pos += 2;               /* For the comma and space */
+
+      if (old_pos != 0 && pos >= line_length)
+       {
+         putchar ('\n');
+         pos -= old_pos;
+       }
+
+      print_file_name_and_frills (files + filesno);
+      if (filesno + 1 < files_index)
+       {
+         putchar (',');
+         putchar (' ');
+       }
+    }
+  putchar ('\n');
+}
+\f
+/* Assuming cursor is at position FROM, indent up to position TO.  */
+
+void
+indent (from, to)
+     int from, to;
+{
+  while (from < to)
+    {
+      if (to / tabsize > from / tabsize)
+       {
+         putchar ('\t');
+         from += tabsize - from % tabsize;
+       }
+      else
+       {
+         putchar (' ');
+         from++;
+       }
+    }
+}
+
+/* Put DIRNAME/NAME into DEST, handling `.' and `/' properly. */
+
+void
+attach (dest, dirname, name)
+     char *dest, *dirname, *name;
+{
+  char *dirnamep = dirname;
+
+  /* Copy dirname if it is not ".". */
+  if (dirname[0] != '.' || dirname[1] != 0)
+    {
+      while (*dirnamep)
+       *dest++ = *dirnamep++;
+      /* Add '/' if `dirname' doesn't already end with it. */
+      if (dirnamep > dirname && dirnamep[-1] != '/')
+       *dest++ = '/';
+    }
+  while (*name)
+    *dest++ = *name++;
+  *dest = 0;
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-abcdgiklmnpqrstuxABCFLNQRSUX1] [-w cols] [-T cols] [-I pattern]\n\
+       [--all] [--escape] [--directory] [--inode] [--kilobytes] [--literal]\n\
+       [--numeric-uid-gid] [--hide-control-chars] [--reverse] [--size]\n\
+       [--width=cols] [--tabsize=cols] [--almost-all] [--ignore-backups]\n",
+          program_name);
+  fprintf (stderr, "\
+       [--classify] [--file-type] [--ignore=pattern] [--dereference]\n\
+       [--quote-name] [--recursive] [--sort={none,time,size,extension}]\n\
+       [--format={long,verbose,commas,across,vertical,single-column}]\n\
+       [--time={atime,access,use,ctime,status}] [path...]\n");
+  exit (1);
+}
diff --git a/src/mkdir.c b/src/mkdir.c
new file mode 100644 (file)
index 0000000..7a5d08a
--- /dev/null
@@ -0,0 +1,121 @@
+/* mkdir -- make directories
+   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.  */
+
+/* Options:
+   -p, --path          Ensure that the given path(s) exist:
+                       Make any missing parent directories for each argument.
+                       Parent dirs default to umask modified by `u+wx'.
+                       Do not consider an argument directory that already
+                       exists to be an error.
+   -m, --mode=mode     Set the mode of created directories to `mode', which is
+                       symbolic as in chmod and uses the umask as a point of
+                       departure.
+
+   David MacKenzie <djm@ai.mit.edu>  */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+#include "modechange.h"
+
+int make_path ();
+void error ();
+void usage ();
+
+/* If nonzero, ensure that a path exists.  */
+int path_mode;
+
+/* The name this program was run with. */
+char *program_name;
+
+struct option longopts[] =
+{
+  {"mode", 1, NULL, 'm'},
+  {"path", 0, &path_mode, 1},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  unsigned int newmode;
+  unsigned int parent_mode;
+  char *symbolic_mode = NULL;
+  int errors = 0;
+  int optc;
+
+  program_name = argv[0];
+  path_mode = 0;
+
+  while ((optc = getopt_long (argc, argv, "pm:", longopts, (int *) 0)) != EOF)
+    {
+      switch (optc)
+       {
+       case 0:                 /* Long option. */
+         break;
+       case 'p':
+         path_mode = 1;
+         break;
+       case 'm':
+         symbolic_mode = optarg;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (optind == argc)
+    usage ();
+  
+  newmode = 0777 & ~umask (0);
+  parent_mode = newmode | 0300;        /* u+wx */
+  if (symbolic_mode)
+    {
+      struct mode_change *change = mode_compile (symbolic_mode, 0);
+      if (change == MODE_INVALID)
+       error (1, 0, "invalid mode `%s'", symbolic_mode);
+      else if (change == MODE_MEMORY_EXHAUSTED)
+       error (1, 0, "virtual memory exhausted");
+      newmode = mode_adjust (newmode, change);
+    }
+
+  for (; optind < argc; ++optind)
+    {
+      if (path_mode)
+       errors |= make_path (argv[optind], newmode, parent_mode, -1, -1, NULL);
+      else if (mkdir (argv[optind], newmode))
+       {
+         error (0, errno, "cannot make directory `%s'", argv[optind]);
+         errors = 1;
+       }
+    }
+
+  exit (errors);
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-p] [-m mode] [--path] [--mode=mode] dir...\n",
+          program_name);
+  exit (1);
+}
+
diff --git a/src/mkfifo.c b/src/mkfifo.c
new file mode 100644 (file)
index 0000000..71a98ce
--- /dev/null
@@ -0,0 +1,108 @@
+/* mkfifo -- make fifo's (named pipes)
+   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.  */
+
+/* Options:
+   -m, --mode=mode     Set the mode of created fifo's to MODE, which is
+                       symbolic as in chmod and uses the umask as a point of
+                       departure.
+
+   David MacKenzie <djm@ai.mit.edu>  */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+#include "modechange.h"
+
+void error ();
+void usage ();
+
+/* The name this program was run with. */
+char *program_name;
+
+struct option longopts[] =
+{
+  {"mode", 1, NULL, 'm'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  unsigned short newmode;
+  struct mode_change *change;
+  char *symbolic_mode;
+  int errors = 0;
+  int optc;
+
+  program_name = argv[0];
+  symbolic_mode = NULL;
+
+#ifndef S_ISFIFO
+  error (4, 0, "fifo files not supported");
+#else
+  while ((optc = getopt_long (argc, argv, "m:", longopts, (int *) 0)) != EOF)
+    {
+      switch (optc)
+       {
+       case 'm':
+         symbolic_mode = optarg;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (optind == argc)
+    usage ();
+  
+  newmode = 0666 & ~umask (0);
+  if (symbolic_mode)
+    {
+      change = mode_compile (symbolic_mode, 0);
+      if (change == MODE_INVALID)
+       error (1, 0, "invalid mode");
+      else if (change == MODE_MEMORY_EXHAUSTED)
+       error (1, 0, "virtual memory exhausted");
+      newmode = mode_adjust (newmode, change);
+    }
+
+  for (; optind < argc; ++optind)
+    {
+      if (mkfifo (argv[optind], newmode))
+       {
+         error (0, errno, "cannot make fifo `%s'", argv[optind]);
+         errors = 1;
+       }
+    }
+
+  exit (errors);
+#endif
+}
+
+#ifdef S_ISFIFO
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-m mode] [--mode=mode] path...\n",
+          program_name);
+  exit (1);
+}
+#endif
diff --git a/src/mknod.c b/src/mknod.c
new file mode 100644 (file)
index 0000000..1c58293
--- /dev/null
@@ -0,0 +1,143 @@
+/* mknod -- make special files
+   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: mknod [-m mode] [--mode=mode] path {bcu} major minor
+               make a block or character device node
+          mknod [-m mode] [--mode=mode] path p
+               make a FIFO (named pipe)
+
+   Options:
+   -m, --mode=mode     Set the mode of created nodes to MODE, which is
+                       symbolic as in chmod and uses the umask as a point of
+                       departure.
+
+   David MacKenzie <djm@ai.mit.edu>  */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+#include "modechange.h"
+
+void error ();
+void usage ();
+
+/* The name this program was run with. */
+char *program_name;
+
+struct option longopts[] =
+{
+  {"mode", 1, NULL, 'm'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  unsigned short newmode;
+  struct mode_change *change;
+  char *symbolic_mode;
+  int optc;
+
+  program_name = argv[0];
+  symbolic_mode = NULL;
+
+  while ((optc = getopt_long (argc, argv, "m:", longopts, (int *) 0)) != EOF)
+    {
+      switch (optc)
+       {
+       case 'm':
+         symbolic_mode = optarg;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  newmode = 0666 & ~umask (0);
+  if (symbolic_mode)
+    {
+      change = mode_compile (symbolic_mode, 0);
+      if (change == MODE_INVALID)
+       error (1, 0, "invalid mode");
+      else if (change == MODE_MEMORY_EXHAUSTED)
+       error (1, 0, "virtual memory exhausted");
+      newmode = mode_adjust (newmode, change);
+    }
+
+  if (argc - optind != 2 && argc - optind != 4)
+    usage ();
+
+  /* Only check the first character, to allow mnemonic usage like
+     `mknod /dev/rst0 character 18 0'. */
+
+  switch (argv[optind + 1][0])
+    {
+    case 'b':                  /* `block' or `buffered' */
+#ifndef S_IFBLK
+      error (4, 0, "block special files not supported");
+#else
+      if (argc - optind != 4)
+       usage ();
+      if (mknod (argv[optind], newmode | S_IFBLK,
+                makedev (atoi (argv[optind + 2]), atoi (argv[optind + 3]))))
+       error (1, errno, "%s", argv[optind]);
+#endif
+      break;
+
+    case 'c':                  /* `character' */
+    case 'u':                  /* `unbuffered' */
+#ifndef S_IFCHR
+      error (4, 0, "character special files not supported");
+#else
+      if (argc - optind != 4)
+       usage ();
+      if (mknod (argv[optind], newmode | S_IFCHR,
+                makedev (atoi (argv[optind + 2]), atoi (argv[optind + 3]))))
+       error (1, errno, "%s", argv[optind]);
+#endif
+      break;
+
+    case 'p':                  /* `pipe' */
+#ifndef S_ISFIFO
+      error (4, 0, "fifo files not supported");
+#else
+      if (argc - optind != 2)
+       usage ();
+      if (mkfifo (argv[optind], newmode))
+       error (1, errno, "%s", argv[optind]);
+#endif
+      break;
+
+    default:
+      usage ();
+    }
+
+  exit (0);
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-m mode] [--mode=mode] path {bcu} major minor\n\
+       %s [-m mode] [--mode=mode] path p\n",
+          program_name, program_name);
+  exit (1);
+}
diff --git a/src/mv.c b/src/mv.c
new file mode 100644 (file)
index 0000000..d7fcdcb
--- /dev/null
+++ b/src/mv.c
@@ -0,0 +1,437 @@
+/* mv -- move or rename files
+   Copyright (C) 1986, 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.  */
+
+/* Options:
+   -f, --force         Assume a 'y' answer to all questions it would
+                       normally ask, and not ask the questions.
+
+   -i, --interactive   Require confirmation from the user before
+                       performing any move that would destroy an
+                       existing file. 
+
+   -u, --update                Do not move a nondirectory that has an
+                       existing destination with the same or newer
+                       modification time.  
+
+   -v, --verbose               List the name of each file as it is moved, and
+                       the name it is moved to. 
+
+   -b, --backup
+   -S, --suffix
+   -V, --version-control
+                       Backup file creation.
+
+   Written by Mike Parker and David MacKenzie */
+
+#ifdef _AIX
+ #pragma alloca
+#endif
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+#include "backupfile.h"
+
+#ifndef _POSIX_VERSION
+uid_t geteuid ();
+#endif
+
+char *basename ();
+enum backup_type get_version ();
+int copy_reg ();
+int do_move ();
+int eaccess_stat ();
+int isdir ();
+int movefile ();
+int yesno ();
+void error ();
+void strip_trailing_slashes ();
+void usage ();
+
+/* The name this program was run with. */
+char *program_name;
+
+/* If nonzero, query the user before overwriting files. */
+int interactive;
+
+/* If nonzero, do not query the user before overwriting unwritable
+   files. */
+int override_mode;
+
+/* If nonzero, do not move a nondirectory that has an existing destination
+   with the same or newer modification time. */
+int update = 0;
+
+/* If nonzero, list each file as it is moved. */
+int verbose;
+
+/* If nonzero, stdin is a tty. */
+int stdin_tty;
+
+/* This process's effective user ID.  */
+uid_t myeuid;
+
+struct option long_options[] =
+{
+  {"backup", 0, NULL, 'b'},
+  {"force", 0, NULL, 'f'},
+  {"interactive", 0, NULL, 'i'},
+  {"suffix", 1, NULL, 'S'},
+  {"update", 0, &update, 1},
+  {"verbose", 0, &verbose, 1},
+  {"version-control", 1, NULL, 'V'},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int c;
+  int errors;
+  int make_backups = 0;
+  char *version;
+
+  version = getenv ("SIMPLE_BACKUP_SUFFIX");
+  if (version)
+    simple_backup_suffix = version;
+  version = getenv ("VERSION_CONTROL");
+  program_name = argv[0];
+  myeuid = geteuid ();
+  interactive = override_mode = verbose = update = 0;
+  errors = 0;
+
+  while ((c = getopt_long (argc, argv, "bfiuvS:V:", long_options, (int *) 0))
+        != EOF)
+    {
+      switch (c)
+       {
+       case 0:
+         break;
+       case 'b':
+         make_backups = 1;
+         break;
+       case 'f':
+         interactive = 0;
+         override_mode = 1;
+         break;
+       case 'i':
+         interactive = 1;
+         override_mode = 0;
+         break;
+       case 'u':
+         update = 1;
+         break;
+       case 'v':
+         verbose = 1;
+         break;
+       case 'S':
+         simple_backup_suffix = optarg;
+         break;
+       case 'V':
+         version = optarg;
+         break;
+       default:
+         usage ();
+       }
+    }
+  if (argc < optind + 2)
+    usage ();
+
+  if (make_backups)
+    backup_type = get_version (version);
+
+  stdin_tty = isatty (0);
+
+  if (argc > optind + 2 && !isdir (argv[argc - 1]))
+    error (1, 0, "when moving multiple files, last argument must be a directory");
+
+  /* Move each arg but the last onto the last. */
+  for (; optind < argc - 1; ++optind)
+    errors |= movefile (argv[optind], argv[argc - 1]);
+
+  exit (errors);
+}
+
+/* Move file SOURCE onto DEST.  Handles the case when DEST is a directory.
+   Return 0 if successful, 1 if an error occurred.  */
+
+int
+movefile (source, dest)
+     char *source;
+     char *dest;
+{
+  strip_trailing_slashes (source);
+
+  if (isdir (dest))
+    {
+      /* Target is a directory; build full target filename. */
+      char *base;
+      char *new_dest;
+
+      base = basename (source);
+      new_dest = (char *) alloca (strlen (dest) + 1 + strlen (base) + 1);
+      sprintf (new_dest, "%s/%s", dest, base);
+      return do_move (source, new_dest);
+    }
+  else
+    return do_move (source, dest);
+}
+
+struct stat dest_stats, source_stats;
+
+/* Move SOURCE onto DEST.  Handles cross-filesystem moves.
+   If DEST is a directory, SOURCE must be also.
+   Return 0 if successful, 1 if an error occurred.  */
+
+int
+do_move (source, dest)
+     char *source;
+     char *dest;
+{
+  char *dest_backup = NULL;
+
+  if (lstat (source, &source_stats) != 0)
+    {
+      error (0, errno, "%s", source);
+      return 1;
+    }
+
+  if (lstat (dest, &dest_stats) == 0)
+    {
+      if (source_stats.st_dev == dest_stats.st_dev
+         && source_stats.st_ino == dest_stats.st_ino)
+       {
+         error (0, 0, "`%s' and `%s' are the same file", source, dest);
+         return 1;
+       }
+
+      if (S_ISDIR (dest_stats.st_mode))
+       {
+         error (0, 0, "%s: cannot overwrite directory", dest);
+         return 1;
+       }
+
+      if (!S_ISDIR (source_stats.st_mode) && update
+         && source_stats.st_mtime <= dest_stats.st_mtime)
+       return 0;
+
+      if (!override_mode && (interactive || stdin_tty)
+         && eaccess_stat (&dest_stats, W_OK))
+       {
+         fprintf (stderr, "%s: replace `%s', overriding mode %04o? ",
+                  program_name, dest, dest_stats.st_mode & 07777);
+         if (!yesno ())
+           return 0;
+       }
+      else if (interactive)
+       {
+         fprintf (stderr, "%s: replace `%s'? ", program_name, dest);
+         if (!yesno ())
+           return 0;
+       }
+
+      if (backup_type != none)
+       {
+         char *tmp_backup = find_backup_file_name (dest);
+         if (tmp_backup == NULL)
+           error (1, 0, "virtual memory exhausted");
+         dest_backup = alloca (strlen (tmp_backup) + 1);
+         strcpy (dest_backup, tmp_backup);
+         free (tmp_backup);
+         if (rename (dest, dest_backup))
+           {
+             if (errno != ENOENT)
+               {
+                 error (0, errno, "cannot backup `%s'", dest);
+                 return 1;
+               }
+             else
+               dest_backup = NULL;
+           }
+       }
+    }
+  else if (errno != ENOENT)
+    {
+      error (0, errno, "%s", dest);
+      return 1;
+    }
+
+  if (verbose)
+    printf ("%s -> %s\n", source, dest);
+
+  if (rename (source, dest) == 0)
+    {
+      return 0;
+    }
+
+  if (errno != EXDEV)
+    {
+      error (0, errno, "cannot move `%s' to `%s'", source, dest);
+      goto un_backup;
+    }
+
+  /* rename failed on cross-filesystem link.  Copy the file instead. */
+
+  if (copy_reg (source, dest))
+    goto un_backup;
+  
+  if (unlink (source))
+    {
+      error (0, errno, "cannot remove `%s'", source);
+      return 1;
+    }
+
+  return 0;
+
+ un_backup:
+  if (dest_backup)
+    {
+      if (rename (dest_backup, dest))
+       error (0, errno, "cannot un-backup `%s'", dest);
+    }
+  return 1;
+}
+
+/* Copy regular file SOURCE onto file DEST.
+   Return 1 if an error occurred, 0 if successful. */
+
+int
+copy_reg (source, dest)
+     char *source, *dest;
+{
+  int ifd;
+  int ofd;
+  char buf[1024 * 8];
+  int len;                     /* Number of bytes read into `buf'. */
+  
+  if (!S_ISREG (source_stats.st_mode))
+    {
+      error (0, 0, "cannot move `%s' across filesystems: Not a regular file",
+            source);
+      return 1;
+    }
+  
+  if (unlink (dest) && errno != ENOENT)
+    {
+      error (0, errno, "cannot remove `%s'", dest);
+      return 1;
+    }
+
+  ifd = open (source, O_RDONLY, 0);
+  if (ifd < 0)
+    {
+      error (0, errno, "%s", source);
+      return 1;
+    }
+  ofd = open (dest, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+  if (ofd < 0)
+    {
+      error (0, errno, "%s", dest);
+      close (ifd);
+      return 1;
+    }
+
+  while ((len = read (ifd, buf, sizeof (buf))) > 0)
+    {
+      int wrote = 0;
+      char *bp = buf;
+      
+      do
+       {
+         wrote = write (ofd, bp, len);
+         if (wrote < 0)
+           {
+             error (0, errno, "%s", dest);
+             close (ifd);
+             close (ofd);
+             unlink (dest);
+             return 1;
+           }
+         bp += wrote;
+         len -= wrote;
+       } while (len > 0);
+    }
+  if (len < 0)
+    {
+      error (0, errno, "%s", source);
+      close (ifd);
+      close (ofd);
+      unlink (dest);
+      return 1;
+    }
+
+  if (close (ifd) < 0)
+    {
+      error (0, errno, "%s", source);
+      close (ofd);
+      return 1;
+    }
+  if (close (ofd) < 0)
+    {
+      error (0, errno, "%s", dest);
+      return 1;
+    }
+  
+  /* chown turns off set[ug]id bits for non-root,
+     so do the chmod last.  */
+
+  /* Try to copy the old file's modtime and access time.  */
+  {
+    struct utimbuf tv;
+
+    tv.actime = source_stats.st_atime;
+    tv.modtime = source_stats.st_mtime;
+    if (utime (dest, &tv))
+      {
+       error (0, errno, "%s", dest);
+       return 1;
+      }
+  }
+
+  /* Try to preserve ownership.  For non-root it might fail, but that's ok.
+     But root probably wants to know, e.g. if NFS disallows it.  */
+  if (chown (dest, source_stats.st_uid, source_stats.st_gid)
+      && (errno != EPERM || myeuid == 0))
+    {
+      error (0, errno, "%s", dest);
+      return 1;
+    }
+
+  if (chmod (dest, source_stats.st_mode & 07777))
+    {
+      error (0, errno, "%s", dest);
+      return 1;
+    }
+
+  return 0;
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [options] source dest\n\
+       %s [options] source... directory\n\
+Options:\n\
+       [-bfiuv] [-S backup-suffix] [-V {numbered,existing,simple}]\n\
+       [--backup] [--force] [--interactive] [--update] [--verbose]\n\
+       [--suffix=backup-suffix] [--version-control={numbered,existing,simple}]\n",
+          program_name, program_name);
+  exit (1);
+}
diff --git a/src/rm.c b/src/rm.c
new file mode 100644 (file)
index 0000000..b62fbbf
--- /dev/null
+++ b/src/rm.c
@@ -0,0 +1,495 @@
+/* `rm' file deletion utility for GNU.
+   Copyright (C) 1988, 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 Paul Rubin, David MacKenzie, and Richard Stallman. */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+
+#ifdef _POSIX_SOURCE
+/* POSIX.1 doesn't have inodes, so fake them to avoid lots of ifdefs. */
+#define ino_t unsigned long
+#define D_INO(dp) 1
+#else
+#define D_INO(dp) ((dp)->d_ino)
+#endif
+
+char *basename ();
+char *stpcpy ();
+char *xmalloc ();
+char *xrealloc ();
+int clear_directory ();
+int duplicate_entry ();
+int eaccess_stat ();
+int remove_dir ();
+int remove_file ();
+int rm ();
+int yesno ();
+void error ();
+void strip_trailing_slashes ();
+void usage ();
+
+/* Path of file now being processed; extended as necessary. */
+char *pathname;
+
+/* Number of bytes currently allocated for `pathname';
+   made larger when necessary, but never smaller.  */
+int pnsize;
+
+/* Name this program was run with.  */
+char *program_name;
+
+/* If nonzero, display the name of each file removed. */
+int verbose;
+
+/* If nonzero, ignore nonexistant files. */
+int ignore_missing_files;
+
+/* If nonzero, recursively remove directories. */
+int recursive;
+
+/* If nonzero, query the user about whether to remove each file. */
+int interactive;
+
+/* If nonzero, remove directories with unlink instead of rmdir, and don't
+   require a directory to be empty before trying to unlink it.
+   Only works for the super-user. */
+int unlink_dirs;
+
+/* If nonzero, stdin is a tty. */
+int stdin_tty;
+
+struct option long_opts[] =
+{
+  {"directory", 0, &unlink_dirs, 1},
+  {"force", 0, NULL, 'f'},
+  {"interactive", 0, NULL, 'i'},
+  {"recursive", 0, &recursive, 1},
+  {"verbose", 0, &verbose, 1},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int err = 0;
+  int c;
+
+  verbose = ignore_missing_files = recursive = interactive
+    = unlink_dirs = 0;
+  pnsize = 256;
+  pathname = xmalloc (pnsize);
+  program_name = argv[0];
+
+  while ((c = getopt_long (argc, argv, "dfirvR", long_opts, (int *) 0)) != EOF)
+    {
+      switch (c)
+       {
+       case 0:         /* Long option. */
+         break;
+       case 'd':
+         unlink_dirs = 1;
+         break;
+       case 'f':
+         interactive = 0;
+         ignore_missing_files = 1;
+         break;
+       case 'i':
+         interactive = 1;
+         ignore_missing_files = 0;
+         break;
+       case 'r':
+       case 'R':
+         recursive = 1;
+         break;
+       case 'v':
+         verbose = 1;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (optind == argc)
+    usage ();
+
+  stdin_tty = isatty (0);
+
+  for (; optind < argc; optind++)
+    {
+      int len;
+
+      /* Stripping slashes is harmless for rmdir;
+        if the arg is not a directory, it will fail with ENOTDIR.  */
+      strip_trailing_slashes (argv[optind]);
+      len = strlen (argv[optind]);
+      if (len + 1 > pnsize)
+       {
+         free (pathname);
+         pnsize = 2 * (len + 1);
+         pathname = xmalloc (pnsize);
+       }
+      strcpy (pathname, argv[optind]);
+      err += rm ();
+    }
+
+  exit (err > 0);
+}
+
+/* Remove file or directory `pathname' after checking appropriate things.
+   Return 0 if `pathname' is removed, 1 if not. */
+
+int
+rm ()
+{
+  struct stat path_stats;
+  char *base = basename (pathname);
+
+  if (base[0] == '.' && (base[1] == '\0'
+                            || (base[1] == '.' && base[2] == '\0')))
+    {
+      error (0, 0, "cannot remove `.' or `..'");
+      return 1;
+    }
+
+  if (lstat (pathname, &path_stats))
+    {
+      if (errno == ENOENT && ignore_missing_files)
+       return 0;
+      error (0, errno, "%s", pathname);
+      return 1;
+    }
+
+  if (S_ISDIR (path_stats.st_mode) && !unlink_dirs)
+    return remove_dir (&path_stats);
+  else
+    return remove_file (&path_stats);
+}
+
+/* Query the user if appropriate, and if ok try to remove the
+   non-directory `pathname', which STATP contains info about.
+   Return 0 if `pathname' is removed, 1 if not. */
+
+int
+remove_file (statp)
+     struct stat *statp;
+{
+  if (!ignore_missing_files && (interactive || stdin_tty)
+      && eaccess_stat (statp, W_OK))
+    {
+      fprintf (stderr, "%s: remove %s`%s', overriding mode %04o? ",
+              program_name,
+              S_ISDIR (statp->st_mode) ? "directory " : "",
+              pathname,
+              statp->st_mode & 07777);
+      if (!yesno ())
+       return 1;
+    }
+  else if (interactive)
+    {
+      fprintf (stderr, "%s: remove %s`%s'? ", program_name,
+              S_ISDIR (statp->st_mode) ? "directory " : "",
+              pathname);
+      if (!yesno ())
+       return 1;
+    }
+
+  if (verbose)
+    printf ("%s\n", pathname);
+
+  if (unlink (pathname) && (errno != ENOENT || !ignore_missing_files))
+    {
+      error (0, errno, "%s", pathname);
+      return 1;
+    }
+  return 0;
+}
+
+/* If not in recursive mode, print an error message and return 1.
+   Otherwise, query the user if appropriate, then try to recursively
+   remove directory `pathname', which STATP contains info about.
+   Return 0 if `pathname' is removed, 1 if not. */
+
+int
+remove_dir (statp)
+     struct stat *statp;
+{
+  int err;
+
+  if (!recursive)
+    {
+      error (0, 0, "%s: is a directory", pathname);
+      return 1;
+    }
+
+  if (!ignore_missing_files && (interactive || stdin_tty)
+      && eaccess_stat (statp, W_OK))
+    {
+      fprintf (stderr,
+              "%s: descend directory `%s', overriding mode %04o? ",
+              program_name, pathname, statp->st_mode & 07777);
+      if (!yesno ())
+       return 1;
+    }
+  else if (interactive)
+    {
+      fprintf (stderr, "%s: descend directory `%s'? ",
+              program_name, pathname);
+      if (!yesno ())
+       return 1;
+    }
+
+  if (verbose)
+    printf ("%s\n", pathname);
+
+  err = clear_directory (statp);
+
+  if (interactive)
+    {
+      if (err)
+       fprintf (stderr, "%s: remove directory `%s' (might be nonempty)? ",
+                program_name, pathname);
+      else
+       fprintf (stderr, "%s: remove directory `%s'? ",
+                program_name, pathname);
+      if (!yesno ())
+       return 1;
+    }
+
+  if (rmdir (pathname) && (errno != ENOENT || !ignore_missing_files))
+    {
+      error (0, errno, "%s", pathname);
+      return 1;
+    }
+  return 0;
+}
+
+/* An element in a stack of pointers into `pathname'.
+   `pathp' points to where in `pathname' the terminating '\0' goes
+   for this level's directory name. */
+struct pathstack
+{
+  struct pathstack *next;
+  char *pathp;
+  ino_t inum;
+};
+
+/* Linked list of pathnames of directories in progress in recursive rm.
+   The entries actually contain pointers into `pathname'.
+   `pathstack' is the current deepest level. */
+static struct pathstack *pathstack = NULL;
+
+/* Read directory `pathname' and remove all of its entries,
+   avoiding use of chdir.
+   On entry, STATP points to the results of stat on `pathname'.
+   Return 0 for success, error count for failure.
+   Upon return, `pathname' will have the same contents as before,
+   but its address might be different; in that case, `pnsize' will
+   be larger, as well. */
+
+int
+clear_directory (statp)
+     struct stat *statp;
+{
+  DIR *dirp;
+  struct direct *dp;
+  char *name_space;            /* Copy of directory's filenames. */
+  char *namep;                 /* Current entry in `name_space'. */
+  unsigned name_size;          /* Bytes allocated for `name_space'. */
+  int name_length;             /* Length of filename in `namep' plus '\0'. */
+  int pathname_length;         /* Length of `pathname'. */
+  ino_t *inode_space;          /* Copy of directory's inodes. */
+  ino_t *inodep;               /* Current entry in `inode_space'. */
+  unsigned inode_size;         /* Bytes allocated for `inode_space'. */
+  int err = 0;                 /* Return status. */
+  struct pathstack pathframe;  /* New top of stack. */
+  struct pathstack *pp;                /* Temporary. */
+
+  name_size = statp->st_size;
+  name_space = (char *) xmalloc (name_size);
+
+  inode_size = statp->st_size;
+  inode_space = (ino_t *) xmalloc (inode_size);
+
+  do
+    {
+      namep = name_space;
+      inodep = inode_space;
+
+      errno = 0;
+      dirp = opendir (pathname);
+      if (dirp == NULL)
+       {
+         if (errno != ENOENT || !ignore_missing_files)
+           {
+             error (0, errno, "%s", pathname);
+             err = 1;
+           }
+         free (name_space);
+         free (inode_space);
+         return err;
+       }
+
+      while ((dp = readdir (dirp)) != NULL)
+       {
+         /* Skip "." and ".." (some NFS filesystems' directories lack them). */
+         if (dp->d_name[0] != '.'
+             || (dp->d_name[1] != '\0'
+                 && (dp->d_name[1] != '.' || dp->d_name[2] != '\0')))
+           {
+             unsigned size_needed = (namep - name_space) + NLENGTH (dp) + 2;
+
+             if (size_needed > name_size)
+               {
+                 char *new_name_space;
+
+                 while (size_needed > name_size)
+                   name_size += 1024;
+
+                 new_name_space = xrealloc (name_space, name_size);
+                 namep += new_name_space - name_space;
+                 name_space = new_name_space;
+               }
+             namep = stpcpy (namep, dp->d_name) + 1;
+
+             if (inodep == inode_space + inode_size)
+               {
+                 ino_t *new_inode_space;
+
+                 inode_size += 1024;
+                 new_inode_space = (ino_t *) xrealloc (inode_space, inode_size);
+                 inodep += new_inode_space - inode_space;
+                 inode_space = new_inode_space;
+               }
+             *inodep++ = D_INO (dp);
+           }
+       }
+      *namep = '\0';
+      if (CLOSEDIR (dirp))
+       {
+         error (0, errno, "%s", pathname);
+         err = 1;
+       }
+
+      pathname_length = strlen (pathname);
+
+      for (namep = name_space, inodep = inode_space; *namep != '\0';
+          namep += name_length, inodep++)
+       {
+         name_length = strlen (namep) + 1;
+
+         /* Satisfy GNU requirement that filenames can be arbitrarily long. */
+         if (pathname_length + 1 + name_length > pnsize)
+           {
+             char *new_pathname;
+
+             pnsize = (pathname_length + 1 + name_length) * 2;
+             new_pathname = xrealloc (pathname, pnsize);
+             /* Update the all the pointers in the stack to use the new area. */
+             for (pp = pathstack; pp != NULL; pp = pp->next)
+               pp->pathp += new_pathname - pathname;
+             pathname = new_pathname;
+           }
+
+         /* Add a new frame to the top of the path stack. */
+         pathframe.pathp = pathname + pathname_length;
+         pathframe.inum = *inodep;
+         pathframe.next = pathstack;
+         pathstack = &pathframe;
+
+         /* Append '/' and the filename to current pathname, take care of the
+            file (which could result in recursive calls), and take the filename
+            back off. */
+
+         *pathstack->pathp = '/';
+         strcpy (pathstack->pathp + 1, namep);
+
+         /* If the i-number has already appeared, there's an error. */
+         if (duplicate_entry (pathstack->next, pathstack->inum))
+           err++;
+         else if (rm ())
+           err++;
+
+         *pathstack->pathp = '\0';
+         pathstack = pathstack->next;  /* Pop the stack. */
+       }
+    }
+  /* Keep trying while there are still files to remove. */
+  while (namep > name_space && err == 0);
+
+  free (name_space);
+  free (inode_space);
+  return err;
+}
+
+/* If STACK does not already have an entry with the same i-number as INUM,
+   return 0. Otherwise, ask the user whether to continue;
+   if yes, return 1, and if no, exit.
+   This assumes that no one tries to remove filesystem mount points;
+   doing so could cause duplication of i-numbers that would not indicate
+   a corrupted file system. */
+
+int
+duplicate_entry (stack, inum)
+     struct pathstack *stack;
+     ino_t inum;
+{
+#ifndef _POSIX_SOURCE
+  struct pathstack *p;
+
+  for (p = stack; p != NULL; p = p->next)
+    {
+      if (p->inum == inum)
+       {
+         fprintf (stderr, "\
+%s: WARNING: Circular directory structure.\n\
+This almost certainly means that you have a corrupted file system.\n\
+NOTIFY YOUR SYSTEM MANAGER.\n\
+Cycle detected:\n\
+%s\n\
+is the same file as\n", program_name, pathname);
+         *p->pathp = '\0';     /* Truncate pathname. */
+         fprintf (stderr, "%s\n", pathname);
+         *p->pathp = '/';      /* Put it back. */
+         if (interactive)
+           {
+             fprintf (stderr, "%s: continue? ", program_name);
+             if (!yesno ())
+               exit (1);
+             return 1;
+           }
+         else
+           exit (1);
+       }
+    }
+#endif
+  return 0;
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-dfirvR] [--directory] [--force] [--interactive] [--recursive]\n\
+       [--verbose] path...\n",
+          program_name);
+  exit (1);
+}
diff --git a/src/rmdir.c b/src/rmdir.c
new file mode 100644 (file)
index 0000000..59d2de7
--- /dev/null
@@ -0,0 +1,121 @@
+/* rmdir -- remove directories
+   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.  */
+
+/* Options:
+   -p, --path          Remove any parent dirs that are explicitly mentioned
+                       in an argument, if they become empty after the
+                       argument file is removed.
+
+   David MacKenzie <djm@ai.mit.edu>  */
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+
+void remove_parents ();
+void error ();
+void strip_trailing_slashes ();
+void usage ();
+
+/* If nonzero, remove empty parent directories. */
+int empty_paths;
+
+/* The name this program was run with. */
+char *program_name;
+
+struct option longopts[] =
+{
+  {"path", 0, &empty_paths, 1},
+  {NULL, 0, NULL, 0}
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int errors = 0;
+  int optc;
+
+  program_name = argv[0];
+  empty_paths = 0;
+
+  while ((optc = getopt_long (argc, argv, "p", longopts, (int *) 0)) != EOF)
+    {
+      switch (optc)
+       {
+       case 0:                 /* Long option. */
+         break;
+       case 'p':
+         empty_paths = 1;
+         break;
+       default:
+         usage ();
+       }
+    }
+
+  if (optind == argc)
+    usage ();
+  
+  for (; optind < argc; ++optind)
+    {
+      /* Stripping slashes is harmless for rmdir;
+        if the arg is not a directory, it will fail with ENOTDIR.  */
+      strip_trailing_slashes (argv[optind]);
+      if (rmdir (argv[optind]) != 0)
+       {
+         error (0, errno, "%s", argv[optind]);
+         errors = 1;
+       }
+      else if (empty_paths)
+       remove_parents (argv[optind]);
+    }
+
+  exit (errors);
+}
+
+/* Remove any empty parent directories of `path'.
+   Replaces '/' characters in `path' with NULs. */
+
+void
+remove_parents (path)
+     char *path;
+{
+  char *slash;
+
+  do
+    {
+      slash = rindex (path, '/');
+      if (slash == NULL)
+       break;
+      /* Remove any characters after the slash, skipping any extra
+        slashes in a row. */
+      while (slash > path && *slash == '/')
+       --slash;
+      slash[1] = 0;
+    }
+  while (rmdir (path) == 0);
+}
+
+void
+usage ()
+{
+  fprintf (stderr, "Usage: %s [-p] [--path] dir...\n",
+          program_name);
+  exit (1);
+}
diff --git a/src/touch.c b/src/touch.c
new file mode 100644 (file)
index 0000000..fa2d033
--- /dev/null
@@ -0,0 +1,356 @@
+/* touch -- change modification and access times of files
+   Copyright (C) 1987, 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.  */
+
+/* Options:
+   -a, --time={atime,access,use}       Change access time only.
+   -c, --no-create             Do not create files that do not exist.
+   -d, --date=TIME             Specify time and date in various formats.
+   -f                          Ignored.
+   -m, --time={mtime,modify}   Change modification time only.
+   -r, --file=FILE             Use the time and date of reference file FILE.
+   -t TIME                     Specify time and date in the form
+                               `MMDDhhmm[[CC]YY][.ss]'.
+   
+   If no options are given, -am is the default, using the current time.
+   The -r, -t, and -d options are mutually exclusive.  If a file does not
+   exist, create it unless -c is given.
+
+   Written by Paul Rubin, Arnold Robbins, Jim Kingdon, David MacKenzie,
+   and Randy Smith. */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+
+#ifdef STDC_HEADERS
+#include <time.h>
+#else
+time_t mktime ();
+time_t time ();
+#endif
+
+int argmatch ();
+int touch ();
+time_t get_date ();
+time_t posixtime ();
+void error ();
+void invalid_arg ();
+void usage ();
+#ifndef HAVE_UTIME_NULL
+int utime_now ();
+#endif
+
+/* Bitmasks for `change_times'. */
+#define CH_ATIME 1
+#define CH_MTIME 2
+
+/* Which timestamps to change. */
+int change_times;
+
+/* (-c) If nonzero, don't create if not already there. */
+int no_create;
+
+/* (-d) If nonzero, date supplied on command line in get_date formats. */
+int flexible_date;
+
+/* (-r) If nonzero, use times from a reference file. */
+int use_ref;
+
+/* (-t) If nonzero, date supplied on command line in POSIX format. */
+int posix_date;
+
+/* If nonzero, the only thing we have to do is change both the
+   modification and access time to the current time, so we don't
+   have to own the file, just be able to read and write it.  */
+int amtime_now;
+
+/* New time to use when setting time. */
+time_t newtime;
+
+/* File to use for -r. */
+char *ref_file;
+
+/* Info about the reference file. */
+struct stat ref_stats;
+
+/* The name by which this program was run. */
+char *program_name;
+
+struct option longopts[] =
+{
+  {"time", 1, 0, 130},
+  {"no-create", 0, 0, 'c'},
+  {"date", 1, 0, 'd'},
+  {"file", 1, 0, 'r'},
+  {0, 0, 0, 0}
+};
+
+/* Valid arguments to the `--time' option. */
+char *time_args[] =
+{
+  "atime", "access", "use", "mtime", "modify", 0
+};
+
+/* The bits in `change_times' that those arguments set. */
+int time_masks[] =
+{
+  CH_ATIME, CH_ATIME, CH_ATIME, CH_MTIME, CH_MTIME
+};
+
+void
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int c, i;
+  int date_set = 0;
+  int err = 0;
+
+  program_name = argv[0];
+  change_times = no_create = use_ref = posix_date = flexible_date = 0;
+  newtime = (time_t) -1;
+
+  while ((c = getopt_long (argc, argv, "acd:fmr:t:", longopts, (int *) 0))
+        != EOF)
+    {
+      switch (c)
+       {
+       case 'a':
+         change_times |= CH_ATIME;
+         break;
+
+       case 'c':
+         no_create++;
+         break;
+
+       case 'd':
+         flexible_date++;
+         newtime = get_date (optarg, NULL);
+         if (newtime == (time_t) -1)
+           error (1, 0, "invalid date format `%s'", optarg);
+         date_set++;
+         break;
+
+       case 'f':
+         break;
+
+       case 'm':
+         change_times |= CH_MTIME;
+         break;
+
+       case 'r':
+         use_ref++;
+         ref_file = optarg;
+         break;
+
+       case 't':
+         posix_date++;
+         newtime = posixtime (optarg);
+         if (newtime == (time_t) -1)
+           error (1, 0, "invalid date format `%s'", optarg);
+         date_set++;
+         break;
+
+       case 130:
+         i = argmatch (optarg, time_args);
+         if (i < 0)
+           {
+             invalid_arg ("time selector", optarg, i);
+             usage ();
+           }
+         change_times |= time_masks[i];
+         break;
+
+       default:
+         usage ();
+       }
+    }
+
+  if (change_times == 0)
+    change_times = CH_ATIME | CH_MTIME;
+
+  if ((use_ref && (posix_date || flexible_date))
+      || (posix_date && flexible_date))
+    {
+      error (0, 0, "cannot specify times from more than one source");
+      usage ();
+    }
+
+  if (use_ref)
+    {
+      if (stat (ref_file, &ref_stats))
+       error (1, errno, "%s", ref_file);
+      date_set++;
+    }
+
+  if (!date_set && optind < argc && strcmp (argv[optind - 1], "--"))
+    {
+      newtime = posixtime (argv[optind]);
+      if (newtime != (time_t) -1)
+       {
+         optind++;
+         date_set++;
+       }
+    }
+  if (!date_set)
+    {
+      if ((change_times & (CH_ATIME | CH_MTIME)) == (CH_ATIME | CH_MTIME))
+       amtime_now = 1;
+      else
+       time (&newtime);
+    }
+
+  if (optind == argc)
+    {
+      error (0, 0, "file arguments missing");
+      usage ();
+    }
+
+  for (; optind < argc; ++optind)
+    err += touch (argv[optind]);
+
+  exit (err != 0);
+}
+
+/* Update the time of file FILE according to the options given.
+   Return 0 if successful, 1 if an error occurs. */
+
+int
+touch (file)
+     char *file;
+{
+  int status;
+  struct stat sbuf;
+  int fd;
+
+  if (stat (file, &sbuf))
+    {
+      if (errno != ENOENT)
+       {
+         error (0, errno, "%s", file);
+         return 1;
+       }
+      if (no_create)
+       return 0;
+      fd = creat (file, 0666);
+      if (fd == -1)
+       {
+         error (0, errno, "%s", file);
+         return 1;
+       }
+      if (amtime_now)
+       {
+         if (close (fd) < 0)
+           {
+             error (0, errno, "%s", file);
+             return 1;
+           }
+         return 0;             /* We've done all we have to. */
+       }
+      if (fstat (fd, &sbuf))
+       {
+         error (0, errno, "%s", file);
+         close (fd);
+         return 1;
+       }
+      if (close (fd) < 0)
+       {
+         error (0, errno, "%s", file);
+         return 1;
+       }       
+    }
+
+  if (amtime_now)
+    {
+#ifndef HAVE_UTIME_NULL
+      status = utime_now (file, sbuf.st_size);
+#else
+      /* Pass NULL to utime so it will not fail if we just have
+        write access to the file, but don't own it.  */
+      status = utime (file, NULL);
+#endif
+    }
+  else
+    {
+      struct utimbuf utb;
+
+      if (use_ref)
+       {
+         utb.actime = ref_stats.st_atime;
+         utb.modtime = ref_stats.st_mtime;
+       }
+      else
+       utb.actime = utb.modtime = newtime;
+
+      if (!(change_times & CH_ATIME))
+       utb.actime = sbuf.st_atime;
+
+      if (!(change_times & CH_MTIME))
+       utb.modtime = sbuf.st_mtime;
+
+      status = utime (file, &utb);
+    }
+  
+  if (status)
+    {
+      error (0, errno, "%s", file);
+      return 1;
+    }
+
+  return 0;
+}
+
+#ifndef HAVE_UTIME_NULL
+/* Emulate utime (file, NULL) for systems (like 4.3BSD) that do not
+   interpret it to set the access and modification times of FILE to
+   the current time.  FILESIZE is the correct size of FILE, used to
+   make sure empty files are not lengthened to 1 byte.
+   Return 0 if successful, -1 if not. */
+
+int
+utime_now (file, filesize)
+     char *file;
+     off_t filesize;
+{
+  int fd;
+  char c;
+  int status = 0;
+
+  fd = open (file, O_RDWR, 0666);
+  if (fd < 0
+      || read (fd, &c, sizeof (char)) < 0
+      || lseek (fd, (off_t) 0, SEEK_SET) < 0
+      || write (fd, &c, sizeof (char)) < 0
+      || ftruncate (fd, filesize) < 0
+      || close (fd) < 0)
+    status = -1;
+  return status;
+}
+#endif
+
+void
+usage ()
+{
+  fprintf (stderr, "\
+Usage: %s [-acfm] [-r reference-file] [-t MMDDhhmm[[CC]YY][.ss]]\n\
+       [-d time] [--time={atime,access,use,mtime,modify}] [--date=time]\n\
+       [--file=reference-file] [--no-create] file...\n",
+          program_name);
+  exit (1);
+}