summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJim Meyering <jim@meyering.net>1992-10-31 20:42:48 +0000
committerJim Meyering <jim@meyering.net>1992-10-31 20:42:48 +0000
commit14fd34b78818660e05806b6eda178e3f846c5c21 (patch)
treeb40038aa2684b5cd95ae2d5fcef564bcf8e05cc3 /src
downloadcoreutils-14fd34b78818660e05806b6eda178e3f846c5c21.tar.xz
Initial revision
Diffstat (limited to 'src')
-rw-r--r--src/chgrp.c275
-rw-r--r--src/chmod.c268
-rw-r--r--src/chown.c271
-rw-r--r--src/cp-hash.c217
-rw-r--r--src/cp.c1226
-rw-r--r--src/dd.c1020
-rw-r--r--src/df.c398
-rw-r--r--src/du.c672
-rw-r--r--src/install.c496
-rw-r--r--src/ln.c293
-rw-r--r--src/ls.c1813
-rw-r--r--src/mkdir.c121
-rw-r--r--src/mkfifo.c108
-rw-r--r--src/mknod.c143
-rw-r--r--src/mv.c437
-rw-r--r--src/rm.c495
-rw-r--r--src/rmdir.c121
-rw-r--r--src/touch.c356
18 files changed, 8730 insertions, 0 deletions
diff --git a/src/chgrp.c b/src/chgrp.c
new file mode 100644
index 000000000..aeb638f0a
--- /dev/null
+++ b/src/chgrp.c
@@ -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
index 000000000..1d44c38f7
--- /dev/null
+++ b/src/chmod.c
@@ -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
index 000000000..2bc6987af
--- /dev/null
+++ b/src/chown.c
@@ -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
index 000000000..a0afcfcf3
--- /dev/null
+++ b/src/cp-hash.c
@@ -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;
+}
+
+/* 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
index 000000000..d71734454
--- /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}
+};
+
+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);
+}
+
+/* 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");
+}
+
+/* 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;
+}
+
+/* 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;
+}
+
+/* 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;
+}
+
+/* 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;
+}
+
+/* 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
index 000000000..dd5606815
--- /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
index 000000000..e13f1ce1d
--- /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);
+}
+
+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");
+}
+
+/* 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;
+}
+
+/* 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');
+}
+
+/* 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;
+}
+
+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
index 000000000..726b5d30e
--- /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);
+}
+
+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);
+}
+
+/* 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);
+}
+
+/* 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;
+}
+
+/* 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;
+}
+
+/* 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
index 000000000..473ea6d39
--- /dev/null
+++ b/src/install.c
@@ -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
index 000000000..781a55d8b
--- /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. */
+
+/* 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
index 000000000..3f207274d
--- /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. */
+
+/* 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 ();
+
+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;
+
+/* 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;
+
+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);
+}
+
+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;
+}
+
+/* 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');
+}
+
+/* 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;
+}
+
+/* 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;
+}
+
+/* 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;
+}
+
+/* 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;
+}
+
+/* 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);
+}
+
+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 ('"');
+}
+
+/* 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;
+}
+
+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');
+ }
+}
+
+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');
+}
+
+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');
+}
+
+/* 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
index 000000000..7a5d08a43
--- /dev/null
+++ b/src/mkdir.c
@@ -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
index 000000000..71a98cecd
--- /dev/null
+++ b/src/mkfifo.c
@@ -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
index 000000000..1c582935d
--- /dev/null
+++ b/src/mknod.c
@@ -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
index 000000000..d7fcdcb16
--- /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
index 000000000..b62fbbf07
--- /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
index 000000000..59d2de71a
--- /dev/null
+++ b/src/rmdir.c
@@ -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
index 000000000..fa2d033c2
--- /dev/null
+++ b/src/touch.c
@@ -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);
+}