From 14fd34b78818660e05806b6eda178e3f846c5c21 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Sat, 31 Oct 1992 20:42:48 +0000 Subject: Initial revision --- src/chgrp.c | 275 +++++++++ src/chmod.c | 268 +++++++++ src/chown.c | 271 +++++++++ src/cp-hash.c | 217 +++++++ src/cp.c | 1226 ++++++++++++++++++++++++++++++++++++++ src/dd.c | 1020 ++++++++++++++++++++++++++++++++ src/df.c | 398 +++++++++++++ src/du.c | 672 +++++++++++++++++++++ src/install.c | 496 ++++++++++++++++ src/ln.c | 293 ++++++++++ src/ls.c | 1813 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/mkdir.c | 121 ++++ src/mkfifo.c | 108 ++++ src/mknod.c | 143 +++++ src/mv.c | 437 ++++++++++++++ src/rm.c | 495 ++++++++++++++++ src/rmdir.c | 121 ++++ src/touch.c | 356 +++++++++++ 18 files changed, 8730 insertions(+) create mode 100644 src/chgrp.c create mode 100644 src/chmod.c create mode 100644 src/chown.c create mode 100644 src/cp-hash.c create mode 100644 src/cp.c create mode 100644 src/dd.c create mode 100644 src/df.c create mode 100644 src/du.c create mode 100644 src/install.c create mode 100644 src/ln.c create mode 100644 src/ls.c create mode 100644 src/mkdir.c create mode 100644 src/mkfifo.c create mode 100644 src/mknod.c create mode 100644 src/mv.c create mode 100644 src/rm.c create mode 100644 src/rmdir.c create mode 100644 src/touch.c (limited to 'src') 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 . */ + +#include +#include +#include +#include +#include +#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 */ + +#include +#include +#include +#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 . */ + +#include +#include +#include +#include +#include +#include +#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 +#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 +#include +#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 +#include +#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 +#include +#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 */ + +#include +#include +#include +#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 +#include +#include +#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 */ + +#include +#include +#include +#include +#include +#include +#include "system.h" +#include "modechange.h" + +#ifdef _POSIX_VERSION +#include +#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 +#include +#include +#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 +#if !defined(_POSIX_SOURCE) || defined(_AIX) +#include +#endif +#include +#include +#include +#include +#include +#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 +#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 */ + +#include +#include +#include +#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 */ + +#include +#include +#include +#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 */ + +#include +#include +#include +#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 +#include +#include +#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 +#include +#include +#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 */ + +#include +#include +#include +#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 +#include +#include +#include +#include "system.h" + +#ifdef STDC_HEADERS +#include +#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); +} -- cgit v1.2.3-54-g00ecf