From cbf5993c43f49281173f185863577d86bfac6eae Mon Sep 17 00:00:00 2001 From: Lorry Tar Creator Date: Thu, 22 Mar 2007 21:23:21 +0000 Subject: coreutils-6.9 --- src/Makefile.am | 365 +++++ src/Makefile.in | 2065 ++++++++++++++++++++++++ src/base64.c | 322 ++++ src/basename.c | 144 ++ src/c99-to-c89.diff | 118 ++ src/cat.c | 788 +++++++++ src/chgrp.c | 314 ++++ src/chmod.c | 532 +++++++ src/chown-core.c | 514 ++++++ src/chown-core.h | 87 + src/chown.c | 338 ++++ src/chroot.c | 118 ++ src/cksum.c | 316 ++++ src/comm.c | 285 ++++ src/copy.c | 2007 +++++++++++++++++++++++ src/copy.h | 218 +++ src/cp-hash.c | 187 +++ src/cp-hash.h | 6 + src/cp.c | 1072 +++++++++++++ src/csplit.c | 1515 ++++++++++++++++++ src/cut.c | 884 ++++++++++ src/date.c | 563 +++++++ src/dcgen | 57 + src/dd.c | 1744 ++++++++++++++++++++ src/df.c | 967 +++++++++++ src/dircolors.c | 512 ++++++ src/dircolors.h | 164 ++ src/dircolors.hin | 171 ++ src/dirname.c | 117 ++ src/du.c | 1021 ++++++++++++ src/echo.c | 276 ++++ src/env.c | 205 +++ src/expand.c | 438 +++++ src/expr.c | 873 ++++++++++ src/extract-magic | 135 ++ src/factor.c | 220 +++ src/false.c | 2 + src/fmt.c | 1015 ++++++++++++ src/fold.c | 318 ++++ src/fs.h | 43 + src/groups.sh | 83 + src/head.c | 1064 +++++++++++++ src/hostid.c | 96 ++ src/hostname.c | 125 ++ src/id.c | 388 +++++ src/install.c | 708 ++++++++ src/join.c | 940 +++++++++++ src/kill.c | 373 +++++ src/lbracket.c | 2 + src/link.c | 99 ++ src/ln.c | 534 +++++++ src/logname.c | 91 ++ src/ls-dir.c | 2 + src/ls-ls.c | 2 + src/ls-vdir.c | 2 + src/ls.c | 4430 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/ls.h | 10 + src/md5sum.c | 723 +++++++++ src/mkdir.c | 205 +++ src/mkfifo.c | 129 ++ src/mknod.c | 221 +++ src/mv.c | 486 ++++++ src/nice.c | 195 +++ src/nl.c | 617 +++++++ src/nohup.c | 216 +++ src/od.c | 1937 ++++++++++++++++++++++ src/paste.c | 497 ++++++ src/pathchk.c | 433 +++++ src/pinky.c | 623 ++++++++ src/pr.c | 2878 +++++++++++++++++++++++++++++++++ src/printenv.c | 134 ++ src/printf.c | 687 ++++++++ src/ptx.c | 2224 ++++++++++++++++++++++++++ src/pwd.c | 323 ++++ src/readlink.c | 174 ++ src/remove.c | 1565 ++++++++++++++++++ src/remove.h | 96 ++ src/rm.c | 374 +++++ src/rmdir.c | 226 +++ src/seq.c | 375 +++++ src/setuidgid.c | 129 ++ src/shred.c | 1214 ++++++++++++++ src/shuf.c | 421 +++++ src/sleep.c | 152 ++ src/sort.c | 3174 ++++++++++++++++++++++++++++++++++++ src/split.c | 582 +++++++ src/stat.c | 979 ++++++++++++ src/stty.c | 1892 ++++++++++++++++++++++ src/su.c | 526 ++++++ src/sum.c | 269 ++++ src/sync.c | 78 + src/system.h | 583 +++++++ src/tac-pipe.c | 263 +++ src/tac.c | 666 ++++++++ src/tail.c | 1697 ++++++++++++++++++++ src/tee.c | 220 +++ src/test.c | 849 ++++++++++ src/touch.c | 420 +++++ src/tr.c | 1896 ++++++++++++++++++++++ src/true.c | 82 + src/tsort.c | 559 +++++++ src/tty.c | 129 ++ src/uname.c | 325 ++++ src/unexpand.c | 540 +++++++ src/uniq.c | 552 +++++++ src/unlink.c | 94 ++ src/uptime.c | 245 +++ src/users.c | 154 ++ src/wc.c | 704 ++++++++ src/wheel-gen.pl | 115 ++ src/wheel-size.h | 1 + src/wheel.h | 491 ++++++ src/who.c | 827 ++++++++++ src/whoami.c | 98 ++ src/yes.c | 96 ++ 115 files changed, 66345 insertions(+) create mode 100644 src/Makefile.am create mode 100644 src/Makefile.in create mode 100644 src/base64.c create mode 100644 src/basename.c create mode 100644 src/c99-to-c89.diff create mode 100644 src/cat.c create mode 100644 src/chgrp.c create mode 100644 src/chmod.c create mode 100644 src/chown-core.c create mode 100644 src/chown-core.h create mode 100644 src/chown.c create mode 100644 src/chroot.c create mode 100644 src/cksum.c create mode 100644 src/comm.c create mode 100644 src/copy.c create mode 100644 src/copy.h create mode 100644 src/cp-hash.c create mode 100644 src/cp-hash.h create mode 100644 src/cp.c create mode 100644 src/csplit.c create mode 100644 src/cut.c create mode 100644 src/date.c create mode 100755 src/dcgen create mode 100644 src/dd.c create mode 100644 src/df.c create mode 100644 src/dircolors.c create mode 100644 src/dircolors.h create mode 100644 src/dircolors.hin create mode 100644 src/dirname.c create mode 100644 src/du.c create mode 100644 src/echo.c create mode 100644 src/env.c create mode 100644 src/expand.c create mode 100644 src/expr.c create mode 100644 src/extract-magic create mode 100644 src/factor.c create mode 100644 src/false.c create mode 100644 src/fmt.c create mode 100644 src/fold.c create mode 100644 src/fs.h create mode 100755 src/groups.sh create mode 100644 src/head.c create mode 100644 src/hostid.c create mode 100644 src/hostname.c create mode 100644 src/id.c create mode 100644 src/install.c create mode 100644 src/join.c create mode 100644 src/kill.c create mode 100644 src/lbracket.c create mode 100644 src/link.c create mode 100644 src/ln.c create mode 100644 src/logname.c create mode 100644 src/ls-dir.c create mode 100644 src/ls-ls.c create mode 100644 src/ls-vdir.c create mode 100644 src/ls.c create mode 100644 src/ls.h create mode 100644 src/md5sum.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/nice.c create mode 100644 src/nl.c create mode 100644 src/nohup.c create mode 100644 src/od.c create mode 100644 src/paste.c create mode 100644 src/pathchk.c create mode 100644 src/pinky.c create mode 100644 src/pr.c create mode 100644 src/printenv.c create mode 100644 src/printf.c create mode 100644 src/ptx.c create mode 100644 src/pwd.c create mode 100644 src/readlink.c create mode 100644 src/remove.c create mode 100644 src/remove.h create mode 100644 src/rm.c create mode 100644 src/rmdir.c create mode 100644 src/seq.c create mode 100644 src/setuidgid.c create mode 100644 src/shred.c create mode 100644 src/shuf.c create mode 100644 src/sleep.c create mode 100644 src/sort.c create mode 100644 src/split.c create mode 100644 src/stat.c create mode 100644 src/stty.c create mode 100644 src/su.c create mode 100644 src/sum.c create mode 100644 src/sync.c create mode 100644 src/system.h create mode 100644 src/tac-pipe.c create mode 100644 src/tac.c create mode 100644 src/tail.c create mode 100644 src/tee.c create mode 100644 src/test.c create mode 100644 src/touch.c create mode 100644 src/tr.c create mode 100644 src/true.c create mode 100644 src/tsort.c create mode 100644 src/tty.c create mode 100644 src/uname.c create mode 100644 src/unexpand.c create mode 100644 src/uniq.c create mode 100644 src/unlink.c create mode 100644 src/uptime.c create mode 100644 src/users.c create mode 100644 src/wc.c create mode 100755 src/wheel-gen.pl create mode 100644 src/wheel-size.h create mode 100644 src/wheel.h create mode 100644 src/who.c create mode 100644 src/whoami.c create mode 100644 src/yes.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..863a32b --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,365 @@ +## Process this file with automake to produce Makefile.in -*-Makefile-*- + +## Copyright (C) 1990, 1991, 1993-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +EXTRA_PROGRAMS = chroot df hostid nice pinky stty su uname uptime users who + +bin_SCRIPTS = groups +bin_PROGRAMS = [ chgrp chown chmod cp dd dircolors du \ + ginstall link ln dir vdir ls mkdir \ + mkfifo mknod mv nohup readlink rm rmdir shred stat sync touch unlink \ + cat cksum comm csplit cut expand fmt fold head join md5sum \ + nl od paste pr ptx sha1sum sha224sum sha256sum sha384sum sha512sum \ + shuf sort split sum tac tail tr tsort unexpand uniq wc \ + basename date dirname echo env expr factor false \ + hostname id kill logname pathchk printenv printf pwd seq sleep tee \ + test true tty whoami yes \ + base64 \ + $(OPTIONAL_BIN_PROGS) $(DF_PROG) + +noinst_PROGRAMS = setuidgid + +noinst_HEADERS = \ + chown-core.h \ + copy.h \ + cp-hash.h \ + dircolors.h \ + fs.h \ + ls.h \ + remove.h \ + system.h \ + wheel-size.h \ + wheel.h + +EXTRA_DIST = dcgen dircolors.hin tac-pipe.c \ + groups.sh wheel-gen.pl extract-magic c99-to-c89.diff +BUILT_SOURCES = +CLEANFILES = $(SCRIPTS) su + +AM_CPPFLAGS = -I$(top_srcdir)/lib + +# Sometimes, the expansion of $(LIBINTL) includes -lc which may +# include modules defining variables like `optind', so libcoreutils.a +# must precede $(LIBINTL) in order to ensure we use GNU getopt. +# But libcoreutils.a must also follow $(LIBINTL), since libintl uses +# replacement functions defined in libcoreutils.a. +LDADD = ../lib/libcoreutils.a $(LIBINTL) ../lib/libcoreutils.a + +# for eaccess in lib/euidaccess.c. +cp_LDADD = $(LDADD) $(LIB_EACCESS) +ginstall_LDADD = $(LDADD) $(LIB_EACCESS) +mv_LDADD = $(LDADD) $(LIB_EACCESS) +pathchk_LDADD = $(LDADD) $(LIB_EACCESS) +rm_LDADD = $(LDADD) $(LIB_EACCESS) +test_LDADD = $(LDADD) $(LIB_EACCESS) +# This is for the '[' program. Automake transliterates '[' to '_'. +__LDADD = $(LDADD) $(LIB_EACCESS) + +# for clock_gettime and fdatasync +dd_LDADD = $(LDADD) $(LIB_GETHRXTIME) $(LIB_FDATASYNC) +dir_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) +ls_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) +pr_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) +shred_LDADD = $(LDADD) $(LIB_GETHRXTIME) $(LIB_FDATASYNC) +shuf_LDADD = $(LDADD) $(LIB_GETHRXTIME) +vdir_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) + +## If necessary, add -lm to resolve use of pow in lib/strtod.c. +sort_LDADD = $(LDADD) $(POW_LIB) $(LIB_GETHRXTIME) + +# for get_date and gettime +date_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) +touch_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) + +# If necessary, add -lm to resolve use of pow in lib/strtod.c. +# If necessary, add -liconv to resolve use of iconv in lib/unicodeio.c. +printf_LDADD = $(LDADD) $(POW_LIB) $(LIBICONV) + +# If necessary, add -lm to resolve use of pow in lib/strtod.c. +seq_LDADD = $(LDADD) $(POW_LIB) + +# If necessary, add libraries to resolve the `pow' reference in lib/strtod.c +# and the `nanosleep' reference in lib/xnanosleep.c. +nanosec_libs = $(LDADD) $(POW_LIB) $(LIB_NANOSLEEP) + +sleep_LDADD = $(nanosec_libs) +tail_LDADD = $(nanosec_libs) + +# If necessary, add -lm to resolve use of pow in lib/strtod.c. +uptime_LDADD = $(LDADD) $(POW_LIB) $(GETLOADAVG_LIBS) + +su_LDADD = $(LDADD) $(LIB_CRYPT) + +dir_LDADD += $(LIB_ACL_TRIVIAL) $(LIB_ACL) +ls_LDADD += $(LIB_ACL_TRIVIAL) $(LIB_ACL) +vdir_LDADD += $(LIB_ACL_TRIVIAL) $(LIB_ACL) +cp_LDADD += $(LIB_ACL) +mv_LDADD += $(LIB_ACL) +ginstall_LDADD += $(LIB_ACL) + +$(PROGRAMS): ../lib/libcoreutils.a + +SUFFIXES = .sh + +# Get the release year from ../lib/version-etc.c. +RELEASE_YEAR = \ + `sed -n '/.*COPYRIGHT_YEAR = \([0-9][0-9][0-9][0-9]\) };/s//\1/p' \ + $(top_srcdir)/lib/version-etc.c` + +.sh: + rm -f $@ $@-t + sed \ + -e 's!@''bindir''@!$(bindir)!' \ + -e 's/@''RELEASE_YEAR'@/$(RELEASE_YEAR)/ \ + -e 's/@''GNU_PACKAGE''@/$(GNU_PACKAGE)/' \ + -e 's/@''PACKAGE_BUGREPORT''@/$(PACKAGE_BUGREPORT)/' \ + -e 's/@''VERSION''@/$(VERSION)/' $< > $@-t + chmod +x $@-t + mv $@-t $@ + +all-local: su$(EXEEXT) + +installed_su = $(DESTDIR)$(bindir)/`echo su|sed '$(transform)'` + +setuid_root_mode = a=rx,u+s + +INSTALL_SU = \ + p=su; \ + echo " $(INSTALL_PROGRAM) $$p $(installed_su)"; \ + $(INSTALL_PROGRAM) $$p $(installed_su); \ + echo " chown root $(installed_su)"; \ + chown root $(installed_su); \ + echo " chmod $(setuid_root_mode) $(installed_su)"; \ + chmod $(setuid_root_mode) $(installed_su) + +install-root: su$(EXEEXT) + @$(INSTALL_SU) + +install-exec-local: su$(EXEEXT) + @TMPFILE=$(DESTDIR)$(bindir)/.su-$$$$; \ + rm -f $$TMPFILE; \ + echo > $$TMPFILE; \ +## See if we can create a setuid root executable in $(bindir). +## If not, then don't even try to install su. + can_create_suid_root_executable=no; \ + chown root $$TMPFILE > /dev/null 2>&1 \ + && chmod $(setuid_root_mode) $$TMPFILE > /dev/null 2>&1 \ + && can_create_suid_root_executable=yes; \ + rm -f $$TMPFILE; \ + if test $$can_create_suid_root_executable = yes; then \ + $(INSTALL_SU); \ + else \ + echo "WARNING: insufficient access; not installing su"; \ + echo "NOTE: to install su, run 'make install-root' as root"; \ + fi + +uninstall-local: +# Remove su only if it's one we installed. + @if grep '$(GNU_PACKAGE)' $(installed_su) > /dev/null 2>&1; then \ + echo " rm -f $(installed_su)"; \ + rm -f $(installed_su); \ + else :; fi + +# Use `ginstall' in the definition of PROGRAMS and in dependencies to avoid +# confusion with the `install' target. The install rule transforms `ginstall' +# to install before applying any user-specified name transformations. + +transform = s/ginstall/install/; @program_transform_name@ +ginstall_SOURCES = install.c copy.c cp-hash.c + +# This is for the '[' program. Automake transliterates '[' to '_'. +__SOURCES = lbracket.c + +cp_SOURCES = cp.c copy.c cp-hash.c +dir_SOURCES = ls.c ls-dir.c +vdir_SOURCES = ls.c ls-vdir.c +ls_SOURCES = ls.c ls-ls.c +chown_SOURCES = chown.c chown-core.c +chgrp_SOURCES = chgrp.c chown-core.c + +mv_SOURCES = mv.c copy.c cp-hash.c remove.c +rm_SOURCES = rm.c remove.c + +md5sum_SOURCES = md5sum.c +md5sum_CPPFLAGS = -DHASH_ALGO_MD5=1 $(AM_CPPFLAGS) +sha1sum_SOURCES = md5sum.c +sha1sum_CPPFLAGS = -DHASH_ALGO_SHA1=1 $(AM_CPPFLAGS) +sha224sum_SOURCES = md5sum.c +sha224sum_CPPFLAGS = -DHASH_ALGO_SHA224=1 $(AM_CPPFLAGS) +sha256sum_SOURCES = md5sum.c +sha256sum_CPPFLAGS = -DHASH_ALGO_SHA256=1 $(AM_CPPFLAGS) +sha384sum_SOURCES = md5sum.c +sha384sum_CPPFLAGS = -DHASH_ALGO_SHA384=1 $(AM_CPPFLAGS) +sha512sum_SOURCES = md5sum.c +sha512sum_CPPFLAGS = -DHASH_ALGO_SHA512=1 $(AM_CPPFLAGS) + +editpl = sed -e 's,@''PERL''@,$(PERL),g' + +BUILT_SOURCES += dircolors.h +dircolors.h: dcgen dircolors.hin + @rm -f $@ $@-t + $(PERL) -w -- $(srcdir)/dcgen $(srcdir)/dircolors.hin > $@-t + @chmod a-w $@-t + mv $@-t $@ + +wheel_size = 5 + +BUILT_SOURCES += wheel-size.h +wheel-size.h: Makefile.am + @rm -f $@ $@-t + echo '#define WHEEL_SIZE $(wheel_size)' > $@-t + @chmod a-w $@-t + mv $@-t $@ + +BUILT_SOURCES += wheel.h +wheel.h: wheel-gen.pl Makefile.am + @rm -f $@ $@-t + $(srcdir)/wheel-gen.pl $(wheel_size) > $@-t + @chmod a-w $@-t + mv $@-t $@ + +# false exits nonzero even with --help or --version. +# test doesn't support --help or --version. +# Tell automake to exempt then from that installcheck test. +AM_INSTALLCHECK_STD_OPTIONS_EXEMPT = false test + +BUILT_SOURCES += fs.h +fs.h: stat.c extract-magic + rm -f $@ + $(PERL) $(srcdir)/extract-magic $(srcdir)/stat.c > $@t + @chmod a-w $@t + mv $@t $@ + +MAINTAINERCLEANFILES = $(BUILT_SOURCES) + +# Sort in traditional ASCII order, regardless of the current locale; +# otherwise we may get into trouble with distinct strings that the +# current locale considers to be equal. +ASSORT = LC_ALL=C sort + +all_programs = \ + $(bin_PROGRAMS) \ + $(bin_SCRIPTS) \ + $(EXTRA_PROGRAMS) + +all_programs.list: + @echo $(all_programs) | tr ' ' '\n' | sed -e 's,$(EXEEXT)$$,,' \ + | $(ASSORT) -u + +pm = progs-makefile +pr = progs-readme +# Ensure that the list of programs in README matches the list +# of programs we can build. +check: check-README check-misc +.PHONY: check-README +check-README: + rm -rf $(pr) $(pm) + echo $(all_programs) \ + | tr -s ' ' '\n' | sed -e 's,$(EXEEXT)$$,,' \ + | $(ASSORT) -u > $(pm) && \ + sed -n '/^The programs .* are:/,/^[a-zA-Z]/p' $(top_srcdir)/README \ + | sed -n '/^ */s///p' | tr -s ' ' '\n' > $(pr) + diff $(pm) $(pr) && rm -rf $(pr) $(pm) + +# Ensure that the list of programs and author names is accurate. +au_dotdot = authors-dotdot +au_actual = authors-actual +.PHONY: check-AUTHORS +check-AUTHORS: $(all_programs) + rm -f $(au_actual) $(au_dotdot) + for i in `ls $(all_programs) | sed -e 's,$(EXEEXT)$$,,' \ + | $(ASSORT) -u`; do \ + test "$$i" = '[' && continue; \ + exe=$$i; \ + if test "$$i" = install; then \ + exe=ginstall; \ + elif test "$$i" = test; then \ + exe='['; \ + fi; \ + ./$$exe --version \ + |sed -n '/Written by /{ s//'"$$i"': /; s/,* and /, /; s/\.$$//; p; }'; \ + done > $(au_actual) + sed -n '/:/p' $(top_srcdir)/AUTHORS > $(au_dotdot) + diff $(au_actual) $(au_dotdot) && rm -f $(au_actual) $(au_dotdot) + +# Make sure we don't define any S_IS* macros in src/*.c files. +# Not a big deal, but they're already defined via system.h. +# +# Also make sure we don't use st_blocks. Use ST_NBLOCKS instead. +# This is a bit of a kludge, since it prevents use of the string +# even in comments, but for now it does the job with no false positives. +.PHONY: check-misc +check-misc: + cd $(srcdir); grep '^# *define *S_IS' $(SOURCES) && exit 1 || : + cd $(srcdir); grep st_blocks $(SOURCES) && exit 1 || : + cd $(srcdir); grep '^# *define .*defined' $(SOURCES) && exit 1 || : + +# Extract the list of authors from each file. +sed_filter = s/^ *//;s/N_ (//;s/^"//;s/")*$$// +# Sometimes the string is on the same line as the #define... +s1 = '/^\#define AUTHORS \([^\\]\)/{;s//\1/;$(sed_filter);p;q;}' +# Sometimes the string is on the backslash-continued line after the #define. +s2 = '/^\#define AUTHORS \\\\/{;n;$(sed_filter);p;q;}' +# FIXME: handle *.sh; and use $(all_programs), not $(SOURCES) +../AUTHORS: $(SOURCES) + rm -f $@-t + ( \ + set -e; \ + echo "Here are the names of the programs in this package,"; \ + echo "each followed by the name(s) of its author(s)."; \ + echo; \ + for i in $(SOURCES); do \ + a=`sed -n $(s1) $$i`; \ + test "$$a" && : \ + || a=`sed -n $(s2) $$i`; \ + if test "$$a"; then \ + prog=`echo $$i|sed 's/\.c$$//'`; \ + echo "$$prog: $$a"; \ + fi; \ + done | $(ASSORT) -u ) > $@-t + chmod a-w $@-t + mv $@-t $@ + +# The following rule is not designed to be portable, +# and relies on tools that not everyone has. + +# Most functions in src/*.c should have static scope. +# Any that don't must be marked with `extern', but `main' +# and `usage' are exceptions. They're always extern, but +# don't need to be marked. +# +# The second nm|grep checks for file-scope variables with `extern' scope. +.PHONY: sc_tight_scope +sc_tight_scope: $(all_programs) + @t=exceptions-$$$$; \ + trap "s=$$?; rm -f $$t; exit $$s" 0 1 2 13 15; \ + ( printf '^main$$\n^usage$$\n'; \ + grep -h -A1 '^extern .*[^;]$$' $(SOURCES) \ + | grep -vE '^(extern |--)' |sed 's/^/^/;s/ .*/$$/' ) > $$t; \ + nm -e *.$(OBJEXT) \ + | sed -n 's/.* T //p' \ + | grep -Ev -f $$t && \ + { echo 'the above functions should have static scope' 1>&2; \ + exit 1; } || : ; \ + ( printf '^program_name$$\n'; \ + sed -n 's/^extern int \([^ ][^ ]*\);$$/^\1$$/p' \ + $(noinst_HEADERS) ) > $$t; \ + nm -e *.$(OBJEXT) \ + | sed -n 's/.* [BD] //p' \ + | grep -Ev -f $$t && \ + { echo 'the above variables should have static scope' 1>&2; \ + exit 1; } || : diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..18965a9 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,2065 @@ +# Makefile.in generated by automake 1.10 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +EXTRA_PROGRAMS = chroot$(EXEEXT) df$(EXEEXT) hostid$(EXEEXT) \ + nice$(EXEEXT) pinky$(EXEEXT) stty$(EXEEXT) su$(EXEEXT) \ + uname$(EXEEXT) uptime$(EXEEXT) users$(EXEEXT) who$(EXEEXT) +bin_PROGRAMS = [$(EXEEXT) chgrp$(EXEEXT) chown$(EXEEXT) chmod$(EXEEXT) \ + cp$(EXEEXT) dd$(EXEEXT) dircolors$(EXEEXT) du$(EXEEXT) \ + ginstall$(EXEEXT) link$(EXEEXT) ln$(EXEEXT) dir$(EXEEXT) \ + vdir$(EXEEXT) ls$(EXEEXT) mkdir$(EXEEXT) mkfifo$(EXEEXT) \ + mknod$(EXEEXT) mv$(EXEEXT) nohup$(EXEEXT) readlink$(EXEEXT) \ + rm$(EXEEXT) rmdir$(EXEEXT) shred$(EXEEXT) stat$(EXEEXT) \ + sync$(EXEEXT) touch$(EXEEXT) unlink$(EXEEXT) cat$(EXEEXT) \ + cksum$(EXEEXT) comm$(EXEEXT) csplit$(EXEEXT) cut$(EXEEXT) \ + expand$(EXEEXT) fmt$(EXEEXT) fold$(EXEEXT) head$(EXEEXT) \ + join$(EXEEXT) md5sum$(EXEEXT) nl$(EXEEXT) od$(EXEEXT) \ + paste$(EXEEXT) pr$(EXEEXT) ptx$(EXEEXT) sha1sum$(EXEEXT) \ + sha224sum$(EXEEXT) sha256sum$(EXEEXT) sha384sum$(EXEEXT) \ + sha512sum$(EXEEXT) shuf$(EXEEXT) sort$(EXEEXT) split$(EXEEXT) \ + sum$(EXEEXT) tac$(EXEEXT) tail$(EXEEXT) tr$(EXEEXT) \ + tsort$(EXEEXT) unexpand$(EXEEXT) uniq$(EXEEXT) wc$(EXEEXT) \ + basename$(EXEEXT) date$(EXEEXT) dirname$(EXEEXT) echo$(EXEEXT) \ + env$(EXEEXT) expr$(EXEEXT) factor$(EXEEXT) false$(EXEEXT) \ + hostname$(EXEEXT) id$(EXEEXT) kill$(EXEEXT) logname$(EXEEXT) \ + pathchk$(EXEEXT) printenv$(EXEEXT) printf$(EXEEXT) \ + pwd$(EXEEXT) seq$(EXEEXT) sleep$(EXEEXT) tee$(EXEEXT) \ + test$(EXEEXT) true$(EXEEXT) tty$(EXEEXT) whoami$(EXEEXT) \ + yes$(EXEEXT) base64$(EXEEXT) $(OPTIONAL_BIN_PROGS) $(DF_PROG) +noinst_PROGRAMS = setuidgid$(EXEEXT) +subdir = src +DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/absolute-header.m4 \ + $(top_srcdir)/m4/acl.m4 $(top_srcdir)/m4/alloca.m4 \ + $(top_srcdir)/m4/allocsa.m4 $(top_srcdir)/m4/argmatch.m4 \ + $(top_srcdir)/m4/arpa_inet_h.m4 $(top_srcdir)/m4/assert.m4 \ + $(top_srcdir)/m4/atexit.m4 $(top_srcdir)/m4/autobuild.m4 \ + $(top_srcdir)/m4/backupfile.m4 $(top_srcdir)/m4/base64.m4 \ + $(top_srcdir)/m4/bison.m4 $(top_srcdir)/m4/boottime.m4 \ + $(top_srcdir)/m4/c-strtod.m4 $(top_srcdir)/m4/calloc.m4 \ + $(top_srcdir)/m4/canon-host.m4 \ + $(top_srcdir)/m4/canonicalize.m4 \ + $(top_srcdir)/m4/chdir-long.m4 $(top_srcdir)/m4/check-decl.m4 \ + $(top_srcdir)/m4/chown.m4 $(top_srcdir)/m4/clock_time.m4 \ + $(top_srcdir)/m4/cloexec.m4 $(top_srcdir)/m4/close-stream.m4 \ + $(top_srcdir)/m4/closeout.m4 $(top_srcdir)/m4/codeset.m4 \ + $(top_srcdir)/m4/config-h.m4 $(top_srcdir)/m4/cycle-check.m4 \ + $(top_srcdir)/m4/d-ino.m4 $(top_srcdir)/m4/d-type.m4 \ + $(top_srcdir)/m4/dirfd.m4 $(top_srcdir)/m4/dirname.m4 \ + $(top_srcdir)/m4/dos.m4 $(top_srcdir)/m4/double-slash-root.m4 \ + $(top_srcdir)/m4/dup2.m4 $(top_srcdir)/m4/eealloc.m4 \ + $(top_srcdir)/m4/eoverflow.m4 $(top_srcdir)/m4/error.m4 \ + $(top_srcdir)/m4/euidaccess-stat.m4 \ + $(top_srcdir)/m4/euidaccess.m4 $(top_srcdir)/m4/exclude.m4 \ + $(top_srcdir)/m4/exitfail.m4 $(top_srcdir)/m4/extensions.m4 \ + $(top_srcdir)/m4/fchdir.m4 $(top_srcdir)/m4/fcntl-safer.m4 \ + $(top_srcdir)/m4/fcntl_h.m4 $(top_srcdir)/m4/fd-reopen.m4 \ + $(top_srcdir)/m4/file-type.m4 $(top_srcdir)/m4/fileblocks.m4 \ + $(top_srcdir)/m4/filemode.m4 $(top_srcdir)/m4/filenamecat.m4 \ + $(top_srcdir)/m4/flexmember.m4 $(top_srcdir)/m4/fnmatch.m4 \ + $(top_srcdir)/m4/fpending.m4 $(top_srcdir)/m4/fprintftime.m4 \ + $(top_srcdir)/m4/free.m4 $(top_srcdir)/m4/fstypename.m4 \ + $(top_srcdir)/m4/fsusage.m4 $(top_srcdir)/m4/ftruncate.m4 \ + $(top_srcdir)/m4/fts.m4 $(top_srcdir)/m4/getaddrinfo.m4 \ + $(top_srcdir)/m4/getcwd-abort-bug.m4 \ + $(top_srcdir)/m4/getcwd-path-max.m4 $(top_srcdir)/m4/getcwd.m4 \ + $(top_srcdir)/m4/getdate.m4 $(top_srcdir)/m4/getdelim.m4 \ + $(top_srcdir)/m4/getgroups.m4 $(top_srcdir)/m4/gethostname.m4 \ + $(top_srcdir)/m4/gethrxtime.m4 $(top_srcdir)/m4/getline.m4 \ + $(top_srcdir)/m4/getloadavg.m4 $(top_srcdir)/m4/getndelim2.m4 \ + $(top_srcdir)/m4/getopt.m4 $(top_srcdir)/m4/getpagesize.m4 \ + $(top_srcdir)/m4/getpass.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gettime.m4 $(top_srcdir)/m4/gettimeofday.m4 \ + $(top_srcdir)/m4/getugroups.m4 \ + $(top_srcdir)/m4/getusershell.m4 $(top_srcdir)/m4/glibc21.m4 \ + $(top_srcdir)/m4/gnulib-common.m4 \ + $(top_srcdir)/m4/gnulib-comp.m4 \ + $(top_srcdir)/m4/group-member.m4 \ + $(top_srcdir)/m4/hard-locale.m4 $(top_srcdir)/m4/hash.m4 \ + $(top_srcdir)/m4/host-os.m4 $(top_srcdir)/m4/human.m4 \ + $(top_srcdir)/m4/i-ring.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/idcache.m4 $(top_srcdir)/m4/inet_ntop.m4 \ + $(top_srcdir)/m4/inline.m4 $(top_srcdir)/m4/intmax_t.m4 \ + $(top_srcdir)/m4/inttostr.m4 $(top_srcdir)/m4/inttypes-pri.m4 \ + $(top_srcdir)/m4/inttypes.m4 $(top_srcdir)/m4/inttypes_h.m4 \ + $(top_srcdir)/m4/isapipe.m4 $(top_srcdir)/m4/jm-macros.m4 \ + $(top_srcdir)/m4/jm-winsz1.m4 $(top_srcdir)/m4/jm-winsz2.m4 \ + $(top_srcdir)/m4/lchmod.m4 $(top_srcdir)/m4/lchown.m4 \ + $(top_srcdir)/m4/lib-check.m4 $(top_srcdir)/m4/lib-ignore.m4 \ + $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \ + $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/link-follow.m4 \ + $(top_srcdir)/m4/localcharset.m4 \ + $(top_srcdir)/m4/long-options.m4 \ + $(top_srcdir)/m4/longdouble.m4 $(top_srcdir)/m4/longlong.m4 \ + $(top_srcdir)/m4/ls-mntd-fs.m4 $(top_srcdir)/m4/lstat.m4 \ + $(top_srcdir)/m4/mbchar.m4 $(top_srcdir)/m4/mbiter.m4 \ + $(top_srcdir)/m4/mbrtowc.m4 $(top_srcdir)/m4/mbscasecmp.m4 \ + $(top_srcdir)/m4/mbstate_t.m4 $(top_srcdir)/m4/mbswidth.m4 \ + $(top_srcdir)/m4/md5.m4 $(top_srcdir)/m4/memcasecmp.m4 \ + $(top_srcdir)/m4/memchr.m4 $(top_srcdir)/m4/memcmp.m4 \ + $(top_srcdir)/m4/memcoll.m4 $(top_srcdir)/m4/memcpy.m4 \ + $(top_srcdir)/m4/memmove.m4 $(top_srcdir)/m4/mempcpy.m4 \ + $(top_srcdir)/m4/memrchr.m4 $(top_srcdir)/m4/memset.m4 \ + $(top_srcdir)/m4/memxfrm.m4 $(top_srcdir)/m4/mkancesdirs.m4 \ + $(top_srcdir)/m4/mkdir-p.m4 $(top_srcdir)/m4/mkdir-slash.m4 \ + $(top_srcdir)/m4/mkstemp.m4 $(top_srcdir)/m4/mktime.m4 \ + $(top_srcdir)/m4/modechange.m4 $(top_srcdir)/m4/mountlist.m4 \ + $(top_srcdir)/m4/mpsort.m4 $(top_srcdir)/m4/nanosleep.m4 \ + $(top_srcdir)/m4/netinet_in_h.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/openat.m4 $(top_srcdir)/m4/pathmax.m4 \ + $(top_srcdir)/m4/perl.m4 $(top_srcdir)/m4/physmem.m4 \ + $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/posixtm.m4 \ + $(top_srcdir)/m4/posixver.m4 $(top_srcdir)/m4/prereq.m4 \ + $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/putenv.m4 \ + $(top_srcdir)/m4/quote.m4 $(top_srcdir)/m4/quotearg.m4 \ + $(top_srcdir)/m4/randint.m4 $(top_srcdir)/m4/randperm.m4 \ + $(top_srcdir)/m4/randread.m4 $(top_srcdir)/m4/readlink.m4 \ + $(top_srcdir)/m4/readtokens.m4 $(top_srcdir)/m4/readutmp.m4 \ + $(top_srcdir)/m4/regex.m4 \ + $(top_srcdir)/m4/rename-dest-slash.m4 \ + $(top_srcdir)/m4/rename.m4 $(top_srcdir)/m4/rmdir-errno.m4 \ + $(top_srcdir)/m4/rmdir.m4 $(top_srcdir)/m4/root-dev-ino.m4 \ + $(top_srcdir)/m4/rpmatch.m4 $(top_srcdir)/m4/safe-read.m4 \ + $(top_srcdir)/m4/safe-write.m4 $(top_srcdir)/m4/same.m4 \ + $(top_srcdir)/m4/save-cwd.m4 $(top_srcdir)/m4/savedir.m4 \ + $(top_srcdir)/m4/savewd.m4 $(top_srcdir)/m4/setenv.m4 \ + $(top_srcdir)/m4/settime.m4 $(top_srcdir)/m4/sha1.m4 \ + $(top_srcdir)/m4/sha256.m4 $(top_srcdir)/m4/sha512.m4 \ + $(top_srcdir)/m4/sig2str.m4 $(top_srcdir)/m4/snprintf.m4 \ + $(top_srcdir)/m4/socklen.m4 $(top_srcdir)/m4/sockpfaf.m4 \ + $(top_srcdir)/m4/ssize_t.m4 $(top_srcdir)/m4/st_dm_mode.m4 \ + $(top_srcdir)/m4/stat-prog.m4 $(top_srcdir)/m4/stat-time.m4 \ + $(top_srcdir)/m4/stdarg.m4 $(top_srcdir)/m4/stdbool.m4 \ + $(top_srcdir)/m4/stdint.m4 $(top_srcdir)/m4/stdint_h.m4 \ + $(top_srcdir)/m4/stdio-safer.m4 $(top_srcdir)/m4/stdio_h.m4 \ + $(top_srcdir)/m4/stdlib-safer.m4 $(top_srcdir)/m4/stdlib_h.m4 \ + $(top_srcdir)/m4/stpcpy.m4 $(top_srcdir)/m4/strcspn.m4 \ + $(top_srcdir)/m4/strdup.m4 $(top_srcdir)/m4/strftime.m4 \ + $(top_srcdir)/m4/string_h.m4 $(top_srcdir)/m4/strndup.m4 \ + $(top_srcdir)/m4/strnlen.m4 $(top_srcdir)/m4/strnumcmp.m4 \ + $(top_srcdir)/m4/strpbrk.m4 $(top_srcdir)/m4/strtod.m4 \ + $(top_srcdir)/m4/strtoimax.m4 $(top_srcdir)/m4/strtol.m4 \ + $(top_srcdir)/m4/strtoll.m4 $(top_srcdir)/m4/strtoul.m4 \ + $(top_srcdir)/m4/strtoull.m4 $(top_srcdir)/m4/strtoumax.m4 \ + $(top_srcdir)/m4/strverscmp.m4 \ + $(top_srcdir)/m4/sys_socket_h.m4 \ + $(top_srcdir)/m4/sys_stat_h.m4 $(top_srcdir)/m4/sys_time_h.m4 \ + $(top_srcdir)/m4/tempname.m4 $(top_srcdir)/m4/time_h.m4 \ + $(top_srcdir)/m4/time_r.m4 $(top_srcdir)/m4/timespec.m4 \ + $(top_srcdir)/m4/tm_gmtoff.m4 $(top_srcdir)/m4/tzset.m4 \ + $(top_srcdir)/m4/unicodeio.m4 $(top_srcdir)/m4/unistd-safer.m4 \ + $(top_srcdir)/m4/unistd_h.m4 $(top_srcdir)/m4/unlink-busy.m4 \ + $(top_srcdir)/m4/unlinkdir.m4 $(top_srcdir)/m4/unlocked-io.m4 \ + $(top_srcdir)/m4/uptime.m4 $(top_srcdir)/m4/userspec.m4 \ + $(top_srcdir)/m4/utimbuf.m4 $(top_srcdir)/m4/utime.m4 \ + $(top_srcdir)/m4/utimecmp.m4 $(top_srcdir)/m4/utimens.m4 \ + $(top_srcdir)/m4/utimes-null.m4 $(top_srcdir)/m4/utimes.m4 \ + $(top_srcdir)/m4/vasnprintf.m4 $(top_srcdir)/m4/vasprintf.m4 \ + $(top_srcdir)/m4/wchar.m4 $(top_srcdir)/m4/wchar_t.m4 \ + $(top_srcdir)/m4/wctype.m4 $(top_srcdir)/m4/wcwidth.m4 \ + $(top_srcdir)/m4/wint_t.m4 $(top_srcdir)/m4/xalloc.m4 \ + $(top_srcdir)/m4/xfts.m4 $(top_srcdir)/m4/xgetcwd.m4 \ + $(top_srcdir)/m4/xnanosleep.m4 $(top_srcdir)/m4/xstrndup.m4 \ + $(top_srcdir)/m4/xstrtod.m4 $(top_srcdir)/m4/xstrtol.m4 \ + $(top_srcdir)/m4/yesno.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/lib/config.h +CONFIG_CLEAN_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(bindir)" +binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) +PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS) +am___OBJECTS = lbracket.$(OBJEXT) +__OBJECTS = $(am___OBJECTS) +am__DEPENDENCIES_1 = +am__DEPENDENCIES_2 = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +__DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +base64_SOURCES = base64.c +base64_OBJECTS = base64.$(OBJEXT) +base64_LDADD = $(LDADD) +base64_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +basename_SOURCES = basename.c +basename_OBJECTS = basename.$(OBJEXT) +basename_LDADD = $(LDADD) +basename_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +cat_SOURCES = cat.c +cat_OBJECTS = cat.$(OBJEXT) +cat_LDADD = $(LDADD) +cat_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_chgrp_OBJECTS = chgrp.$(OBJEXT) chown-core.$(OBJEXT) +chgrp_OBJECTS = $(am_chgrp_OBJECTS) +chgrp_LDADD = $(LDADD) +chgrp_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +chmod_SOURCES = chmod.c +chmod_OBJECTS = chmod.$(OBJEXT) +chmod_LDADD = $(LDADD) +chmod_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_chown_OBJECTS = chown.$(OBJEXT) chown-core.$(OBJEXT) +chown_OBJECTS = $(am_chown_OBJECTS) +chown_LDADD = $(LDADD) +chown_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +chroot_SOURCES = chroot.c +chroot_OBJECTS = chroot.$(OBJEXT) +chroot_LDADD = $(LDADD) +chroot_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +cksum_SOURCES = cksum.c +cksum_OBJECTS = cksum.$(OBJEXT) +cksum_LDADD = $(LDADD) +cksum_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +comm_SOURCES = comm.c +comm_OBJECTS = comm.$(OBJEXT) +comm_LDADD = $(LDADD) +comm_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_cp_OBJECTS = cp.$(OBJEXT) copy.$(OBJEXT) cp-hash.$(OBJEXT) +cp_OBJECTS = $(am_cp_OBJECTS) +cp_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +csplit_SOURCES = csplit.c +csplit_OBJECTS = csplit.$(OBJEXT) +csplit_LDADD = $(LDADD) +csplit_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +cut_SOURCES = cut.c +cut_OBJECTS = cut.$(OBJEXT) +cut_LDADD = $(LDADD) +cut_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +date_SOURCES = date.c +date_OBJECTS = date.$(OBJEXT) +date_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +dd_SOURCES = dd.c +dd_OBJECTS = dd.$(OBJEXT) +dd_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +df_SOURCES = df.c +df_OBJECTS = df.$(OBJEXT) +df_LDADD = $(LDADD) +df_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_dir_OBJECTS = ls.$(OBJEXT) ls-dir.$(OBJEXT) +dir_OBJECTS = $(am_dir_OBJECTS) +dir_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +dircolors_SOURCES = dircolors.c +dircolors_OBJECTS = dircolors.$(OBJEXT) +dircolors_LDADD = $(LDADD) +dircolors_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +dirname_SOURCES = dirname.c +dirname_OBJECTS = dirname.$(OBJEXT) +dirname_LDADD = $(LDADD) +dirname_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +du_SOURCES = du.c +du_OBJECTS = du.$(OBJEXT) +du_LDADD = $(LDADD) +du_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +echo_SOURCES = echo.c +echo_OBJECTS = echo.$(OBJEXT) +echo_LDADD = $(LDADD) +echo_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +env_SOURCES = env.c +env_OBJECTS = env.$(OBJEXT) +env_LDADD = $(LDADD) +env_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +expand_SOURCES = expand.c +expand_OBJECTS = expand.$(OBJEXT) +expand_LDADD = $(LDADD) +expand_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +expr_SOURCES = expr.c +expr_OBJECTS = expr.$(OBJEXT) +expr_LDADD = $(LDADD) +expr_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +factor_SOURCES = factor.c +factor_OBJECTS = factor.$(OBJEXT) +factor_LDADD = $(LDADD) +factor_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +false_SOURCES = false.c +false_OBJECTS = false.$(OBJEXT) +false_LDADD = $(LDADD) +false_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +fmt_SOURCES = fmt.c +fmt_OBJECTS = fmt.$(OBJEXT) +fmt_LDADD = $(LDADD) +fmt_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +fold_SOURCES = fold.c +fold_OBJECTS = fold.$(OBJEXT) +fold_LDADD = $(LDADD) +fold_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_ginstall_OBJECTS = install.$(OBJEXT) copy.$(OBJEXT) \ + cp-hash.$(OBJEXT) +ginstall_OBJECTS = $(am_ginstall_OBJECTS) +ginstall_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +head_SOURCES = head.c +head_OBJECTS = head.$(OBJEXT) +head_LDADD = $(LDADD) +head_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +hostid_SOURCES = hostid.c +hostid_OBJECTS = hostid.$(OBJEXT) +hostid_LDADD = $(LDADD) +hostid_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +hostname_SOURCES = hostname.c +hostname_OBJECTS = hostname.$(OBJEXT) +hostname_LDADD = $(LDADD) +hostname_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +id_SOURCES = id.c +id_OBJECTS = id.$(OBJEXT) +id_LDADD = $(LDADD) +id_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +join_SOURCES = join.c +join_OBJECTS = join.$(OBJEXT) +join_LDADD = $(LDADD) +join_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +kill_SOURCES = kill.c +kill_OBJECTS = kill.$(OBJEXT) +kill_LDADD = $(LDADD) +kill_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +link_SOURCES = link.c +link_OBJECTS = link.$(OBJEXT) +link_LDADD = $(LDADD) +link_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +ln_SOURCES = ln.c +ln_OBJECTS = ln.$(OBJEXT) +ln_LDADD = $(LDADD) +ln_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +logname_SOURCES = logname.c +logname_OBJECTS = logname.$(OBJEXT) +logname_LDADD = $(LDADD) +logname_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_ls_OBJECTS = ls.$(OBJEXT) ls-ls.$(OBJEXT) +ls_OBJECTS = $(am_ls_OBJECTS) +ls_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am_md5sum_OBJECTS = md5sum-md5sum.$(OBJEXT) +md5sum_OBJECTS = $(am_md5sum_OBJECTS) +md5sum_LDADD = $(LDADD) +md5sum_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +mkdir_SOURCES = mkdir.c +mkdir_OBJECTS = mkdir.$(OBJEXT) +mkdir_LDADD = $(LDADD) +mkdir_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +mkfifo_SOURCES = mkfifo.c +mkfifo_OBJECTS = mkfifo.$(OBJEXT) +mkfifo_LDADD = $(LDADD) +mkfifo_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +mknod_SOURCES = mknod.c +mknod_OBJECTS = mknod.$(OBJEXT) +mknod_LDADD = $(LDADD) +mknod_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_mv_OBJECTS = mv.$(OBJEXT) copy.$(OBJEXT) cp-hash.$(OBJEXT) \ + remove.$(OBJEXT) +mv_OBJECTS = $(am_mv_OBJECTS) +mv_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +nice_SOURCES = nice.c +nice_OBJECTS = nice.$(OBJEXT) +nice_LDADD = $(LDADD) +nice_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +nl_SOURCES = nl.c +nl_OBJECTS = nl.$(OBJEXT) +nl_LDADD = $(LDADD) +nl_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +nohup_SOURCES = nohup.c +nohup_OBJECTS = nohup.$(OBJEXT) +nohup_LDADD = $(LDADD) +nohup_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +od_SOURCES = od.c +od_OBJECTS = od.$(OBJEXT) +od_LDADD = $(LDADD) +od_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +paste_SOURCES = paste.c +paste_OBJECTS = paste.$(OBJEXT) +paste_LDADD = $(LDADD) +paste_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +pathchk_SOURCES = pathchk.c +pathchk_OBJECTS = pathchk.$(OBJEXT) +pathchk_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +pinky_SOURCES = pinky.c +pinky_OBJECTS = pinky.$(OBJEXT) +pinky_LDADD = $(LDADD) +pinky_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +pr_SOURCES = pr.c +pr_OBJECTS = pr.$(OBJEXT) +pr_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +printenv_SOURCES = printenv.c +printenv_OBJECTS = printenv.$(OBJEXT) +printenv_LDADD = $(LDADD) +printenv_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +printf_SOURCES = printf.c +printf_OBJECTS = printf.$(OBJEXT) +printf_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +ptx_SOURCES = ptx.c +ptx_OBJECTS = ptx.$(OBJEXT) +ptx_LDADD = $(LDADD) +ptx_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +pwd_SOURCES = pwd.c +pwd_OBJECTS = pwd.$(OBJEXT) +pwd_LDADD = $(LDADD) +pwd_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +readlink_SOURCES = readlink.c +readlink_OBJECTS = readlink.$(OBJEXT) +readlink_LDADD = $(LDADD) +readlink_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_rm_OBJECTS = rm.$(OBJEXT) remove.$(OBJEXT) +rm_OBJECTS = $(am_rm_OBJECTS) +rm_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +rmdir_SOURCES = rmdir.c +rmdir_OBJECTS = rmdir.$(OBJEXT) +rmdir_LDADD = $(LDADD) +rmdir_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +seq_SOURCES = seq.c +seq_OBJECTS = seq.$(OBJEXT) +seq_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +setuidgid_SOURCES = setuidgid.c +setuidgid_OBJECTS = setuidgid.$(OBJEXT) +setuidgid_LDADD = $(LDADD) +setuidgid_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_sha1sum_OBJECTS = sha1sum-md5sum.$(OBJEXT) +sha1sum_OBJECTS = $(am_sha1sum_OBJECTS) +sha1sum_LDADD = $(LDADD) +sha1sum_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_sha224sum_OBJECTS = sha224sum-md5sum.$(OBJEXT) +sha224sum_OBJECTS = $(am_sha224sum_OBJECTS) +sha224sum_LDADD = $(LDADD) +sha224sum_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_sha256sum_OBJECTS = sha256sum-md5sum.$(OBJEXT) +sha256sum_OBJECTS = $(am_sha256sum_OBJECTS) +sha256sum_LDADD = $(LDADD) +sha256sum_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_sha384sum_OBJECTS = sha384sum-md5sum.$(OBJEXT) +sha384sum_OBJECTS = $(am_sha384sum_OBJECTS) +sha384sum_LDADD = $(LDADD) +sha384sum_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_sha512sum_OBJECTS = sha512sum-md5sum.$(OBJEXT) +sha512sum_OBJECTS = $(am_sha512sum_OBJECTS) +sha512sum_LDADD = $(LDADD) +sha512sum_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +shred_SOURCES = shred.c +shred_OBJECTS = shred.$(OBJEXT) +shred_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +shuf_SOURCES = shuf.c +shuf_OBJECTS = shuf.$(OBJEXT) +shuf_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +sleep_SOURCES = sleep.c +sleep_OBJECTS = sleep.$(OBJEXT) +am__DEPENDENCIES_3 = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +sleep_DEPENDENCIES = $(am__DEPENDENCIES_3) +sort_SOURCES = sort.c +sort_OBJECTS = sort.$(OBJEXT) +sort_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +split_SOURCES = split.c +split_OBJECTS = split.$(OBJEXT) +split_LDADD = $(LDADD) +split_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +stat_SOURCES = stat.c +stat_OBJECTS = stat.$(OBJEXT) +stat_LDADD = $(LDADD) +stat_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +stty_SOURCES = stty.c +stty_OBJECTS = stty.$(OBJEXT) +stty_LDADD = $(LDADD) +stty_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +su_SOURCES = su.c +su_OBJECTS = su.$(OBJEXT) +su_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +sum_SOURCES = sum.c +sum_OBJECTS = sum.$(OBJEXT) +sum_LDADD = $(LDADD) +sum_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +sync_SOURCES = sync.c +sync_OBJECTS = sync.$(OBJEXT) +sync_LDADD = $(LDADD) +sync_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +tac_SOURCES = tac.c +tac_OBJECTS = tac.$(OBJEXT) +tac_LDADD = $(LDADD) +tac_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +tail_SOURCES = tail.c +tail_OBJECTS = tail.$(OBJEXT) +tail_DEPENDENCIES = $(am__DEPENDENCIES_3) +tee_SOURCES = tee.c +tee_OBJECTS = tee.$(OBJEXT) +tee_LDADD = $(LDADD) +tee_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +test_SOURCES = test.c +test_OBJECTS = test.$(OBJEXT) +test_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +touch_SOURCES = touch.c +touch_OBJECTS = touch.$(OBJEXT) +touch_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +tr_SOURCES = tr.c +tr_OBJECTS = tr.$(OBJEXT) +tr_LDADD = $(LDADD) +tr_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +true_SOURCES = true.c +true_OBJECTS = true.$(OBJEXT) +true_LDADD = $(LDADD) +true_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +tsort_SOURCES = tsort.c +tsort_OBJECTS = tsort.$(OBJEXT) +tsort_LDADD = $(LDADD) +tsort_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +tty_SOURCES = tty.c +tty_OBJECTS = tty.$(OBJEXT) +tty_LDADD = $(LDADD) +tty_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +uname_SOURCES = uname.c +uname_OBJECTS = uname.$(OBJEXT) +uname_LDADD = $(LDADD) +uname_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +unexpand_SOURCES = unexpand.c +unexpand_OBJECTS = unexpand.$(OBJEXT) +unexpand_LDADD = $(LDADD) +unexpand_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +uniq_SOURCES = uniq.c +uniq_OBJECTS = uniq.$(OBJEXT) +uniq_LDADD = $(LDADD) +uniq_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +unlink_SOURCES = unlink.c +unlink_OBJECTS = unlink.$(OBJEXT) +unlink_LDADD = $(LDADD) +unlink_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +uptime_SOURCES = uptime.c +uptime_OBJECTS = uptime.$(OBJEXT) +uptime_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +users_SOURCES = users.c +users_OBJECTS = users.$(OBJEXT) +users_LDADD = $(LDADD) +users_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +am_vdir_OBJECTS = ls.$(OBJEXT) ls-vdir.$(OBJEXT) +vdir_OBJECTS = $(am_vdir_OBJECTS) +vdir_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +wc_SOURCES = wc.c +wc_OBJECTS = wc.$(OBJEXT) +wc_LDADD = $(LDADD) +wc_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +who_SOURCES = who.c +who_OBJECTS = who.$(OBJEXT) +who_LDADD = $(LDADD) +who_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +whoami_SOURCES = whoami.c +whoami_OBJECTS = whoami.$(OBJEXT) +whoami_LDADD = $(LDADD) +whoami_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +yes_SOURCES = yes.c +yes_OBJECTS = yes.$(OBJEXT) +yes_LDADD = $(LDADD) +yes_DEPENDENCIES = ../lib/libcoreutils.a $(am__DEPENDENCIES_1) \ + ../lib/libcoreutils.a +binSCRIPT_INSTALL = $(INSTALL_SCRIPT) +SCRIPTS = $(bin_SCRIPTS) +DEFAULT_INCLUDES = -I. -I$(top_builddir)/lib@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(__SOURCES) base64.c basename.c cat.c $(chgrp_SOURCES) \ + chmod.c $(chown_SOURCES) chroot.c cksum.c comm.c $(cp_SOURCES) \ + csplit.c cut.c date.c dd.c df.c $(dir_SOURCES) dircolors.c \ + dirname.c du.c echo.c env.c expand.c expr.c factor.c false.c \ + fmt.c fold.c $(ginstall_SOURCES) head.c hostid.c hostname.c \ + id.c join.c kill.c link.c ln.c logname.c $(ls_SOURCES) \ + $(md5sum_SOURCES) mkdir.c mkfifo.c mknod.c $(mv_SOURCES) \ + nice.c nl.c nohup.c od.c paste.c pathchk.c pinky.c pr.c \ + printenv.c printf.c ptx.c pwd.c readlink.c $(rm_SOURCES) \ + rmdir.c seq.c setuidgid.c $(sha1sum_SOURCES) \ + $(sha224sum_SOURCES) $(sha256sum_SOURCES) $(sha384sum_SOURCES) \ + $(sha512sum_SOURCES) shred.c shuf.c sleep.c sort.c split.c \ + stat.c stty.c su.c sum.c sync.c tac.c tail.c tee.c test.c \ + touch.c tr.c true.c tsort.c tty.c uname.c unexpand.c uniq.c \ + unlink.c uptime.c users.c $(vdir_SOURCES) wc.c who.c whoami.c \ + yes.c +DIST_SOURCES = $(__SOURCES) base64.c basename.c cat.c $(chgrp_SOURCES) \ + chmod.c $(chown_SOURCES) chroot.c cksum.c comm.c $(cp_SOURCES) \ + csplit.c cut.c date.c dd.c df.c $(dir_SOURCES) dircolors.c \ + dirname.c du.c echo.c env.c expand.c expr.c factor.c false.c \ + fmt.c fold.c $(ginstall_SOURCES) head.c hostid.c hostname.c \ + id.c join.c kill.c link.c ln.c logname.c $(ls_SOURCES) \ + $(md5sum_SOURCES) mkdir.c mkfifo.c mknod.c $(mv_SOURCES) \ + nice.c nl.c nohup.c od.c paste.c pathchk.c pinky.c pr.c \ + printenv.c printf.c ptx.c pwd.c readlink.c $(rm_SOURCES) \ + rmdir.c seq.c setuidgid.c $(sha1sum_SOURCES) \ + $(sha224sum_SOURCES) $(sha256sum_SOURCES) $(sha384sum_SOURCES) \ + $(sha512sum_SOURCES) shred.c shuf.c sleep.c sort.c split.c \ + stat.c stty.c su.c sum.c sync.c tac.c tail.c tee.c test.c \ + touch.c tr.c true.c tsort.c tty.c uname.c unexpand.c uniq.c \ + unlink.c uptime.c users.c $(vdir_SOURCES) wc.c who.c whoami.c \ + yes.c +HEADERS = $(noinst_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) + +# Use `ginstall' in the definition of PROGRAMS and in dependencies to avoid +# confusion with the `install' target. The install rule transforms `ginstall' +# to install before applying any user-specified name transformations. +transform = s/ginstall/install/; @program_transform_name@ +ABSOLUTE_DIRENT_H = @ABSOLUTE_DIRENT_H@ +ABSOLUTE_FCNTL_H = @ABSOLUTE_FCNTL_H@ +ABSOLUTE_INTTYPES_H = @ABSOLUTE_INTTYPES_H@ +ABSOLUTE_NETINET_IN_H = @ABSOLUTE_NETINET_IN_H@ +ABSOLUTE_STDINT_H = @ABSOLUTE_STDINT_H@ +ABSOLUTE_STDIO_H = @ABSOLUTE_STDIO_H@ +ABSOLUTE_STDLIB_H = @ABSOLUTE_STDLIB_H@ +ABSOLUTE_STRING_H = @ABSOLUTE_STRING_H@ +ABSOLUTE_SYS_SOCKET_H = @ABSOLUTE_SYS_SOCKET_H@ +ABSOLUTE_SYS_STAT_H = @ABSOLUTE_SYS_STAT_H@ +ABSOLUTE_SYS_TIME_H = @ABSOLUTE_SYS_TIME_H@ +ABSOLUTE_TIME_H = @ABSOLUTE_TIME_H@ +ABSOLUTE_UNISTD_H = @ABSOLUTE_UNISTD_H@ +ABSOLUTE_WCHAR_H = @ABSOLUTE_WCHAR_H@ +ABSOLUTE_WCTYPE_H = @ABSOLUTE_WCTYPE_H@ +ACLOCAL = @ACLOCAL@ +ALLOCA = @ALLOCA@ +ALLOCA_H = @ALLOCA_H@ +AMTAR = @AMTAR@ +ARPA_INET_H = @ARPA_INET_H@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BITSIZEOF_PTRDIFF_T = @BITSIZEOF_PTRDIFF_T@ +BITSIZEOF_SIG_ATOMIC_T = @BITSIZEOF_SIG_ATOMIC_T@ +BITSIZEOF_SIZE_T = @BITSIZEOF_SIZE_T@ +BITSIZEOF_WCHAR_T = @BITSIZEOF_WCHAR_T@ +BITSIZEOF_WINT_T = @BITSIZEOF_WINT_T@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFAULT_POSIX2_VERSION = @DEFAULT_POSIX2_VERSION@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DF_PROG = @DF_PROG@ +DIRENT_H = @DIRENT_H@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EOVERFLOW = @EOVERFLOW@ +EXEEXT = @EXEEXT@ +FCNTL_H = @FCNTL_H@ +FNMATCH_H = @FNMATCH_H@ +GETLOADAVG_LIBS = @GETLOADAVG_LIBS@ +GETOPT_H = @GETOPT_H@ +GLIBC21 = @GLIBC21@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNULIB_CHOWN = @GNULIB_CHOWN@ +GNULIB_DUP2 = @GNULIB_DUP2@ +GNULIB_FCHDIR = @GNULIB_FCHDIR@ +GNULIB_FPRINTF_POSIX = @GNULIB_FPRINTF_POSIX@ +GNULIB_FTRUNCATE = @GNULIB_FTRUNCATE@ +GNULIB_GETCWD = @GNULIB_GETCWD@ +GNULIB_GETLOGIN_R = @GNULIB_GETLOGIN_R@ +GNULIB_GETSUBOPT = @GNULIB_GETSUBOPT@ +GNULIB_IMAXABS = @GNULIB_IMAXABS@ +GNULIB_IMAXDIV = @GNULIB_IMAXDIV@ +GNULIB_MBSCASECMP = @GNULIB_MBSCASECMP@ +GNULIB_MBSCASESTR = @GNULIB_MBSCASESTR@ +GNULIB_MBSCHR = @GNULIB_MBSCHR@ +GNULIB_MBSCSPN = @GNULIB_MBSCSPN@ +GNULIB_MBSLEN = @GNULIB_MBSLEN@ +GNULIB_MBSNCASECMP = @GNULIB_MBSNCASECMP@ +GNULIB_MBSPBRK = @GNULIB_MBSPBRK@ +GNULIB_MBSPCASECMP = @GNULIB_MBSPCASECMP@ +GNULIB_MBSRCHR = @GNULIB_MBSRCHR@ +GNULIB_MBSSEP = @GNULIB_MBSSEP@ +GNULIB_MBSSPN = @GNULIB_MBSSPN@ +GNULIB_MBSSTR = @GNULIB_MBSSTR@ +GNULIB_MBSTOK_R = @GNULIB_MBSTOK_R@ +GNULIB_MEMMEM = @GNULIB_MEMMEM@ +GNULIB_MEMPCPY = @GNULIB_MEMPCPY@ +GNULIB_MEMRCHR = @GNULIB_MEMRCHR@ +GNULIB_MKDTEMP = @GNULIB_MKDTEMP@ +GNULIB_MKSTEMP = @GNULIB_MKSTEMP@ +GNULIB_PRINTF_POSIX = @GNULIB_PRINTF_POSIX@ +GNULIB_READLINK = @GNULIB_READLINK@ +GNULIB_SNPRINTF = @GNULIB_SNPRINTF@ +GNULIB_SPRINTF_POSIX = @GNULIB_SPRINTF_POSIX@ +GNULIB_STPCPY = @GNULIB_STPCPY@ +GNULIB_STPNCPY = @GNULIB_STPNCPY@ +GNULIB_STRCASESTR = @GNULIB_STRCASESTR@ +GNULIB_STRCHRNUL = @GNULIB_STRCHRNUL@ +GNULIB_STRDUP = @GNULIB_STRDUP@ +GNULIB_STRNDUP = @GNULIB_STRNDUP@ +GNULIB_STRNLEN = @GNULIB_STRNLEN@ +GNULIB_STRPBRK = @GNULIB_STRPBRK@ +GNULIB_STRSEP = @GNULIB_STRSEP@ +GNULIB_STRTOIMAX = @GNULIB_STRTOIMAX@ +GNULIB_STRTOK_R = @GNULIB_STRTOK_R@ +GNULIB_STRTOUMAX = @GNULIB_STRTOUMAX@ +GNULIB_VFPRINTF_POSIX = @GNULIB_VFPRINTF_POSIX@ +GNULIB_VPRINTF_POSIX = @GNULIB_VPRINTF_POSIX@ +GNULIB_VSNPRINTF = @GNULIB_VSNPRINTF@ +GNULIB_VSPRINTF_POSIX = @GNULIB_VSPRINTF_POSIX@ +GNU_PACKAGE = @GNU_PACKAGE@ +GREP = @GREP@ +HAVE_DECL_GETLOGIN_R = @HAVE_DECL_GETLOGIN_R@ +HAVE_DECL_IMAXABS = @HAVE_DECL_IMAXABS@ +HAVE_DECL_IMAXDIV = @HAVE_DECL_IMAXDIV@ +HAVE_DECL_MEMMEM = @HAVE_DECL_MEMMEM@ +HAVE_DECL_MEMRCHR = @HAVE_DECL_MEMRCHR@ +HAVE_DECL_SNPRINTF = @HAVE_DECL_SNPRINTF@ +HAVE_DECL_STRDUP = @HAVE_DECL_STRDUP@ +HAVE_DECL_STRNCASECMP = @HAVE_DECL_STRNCASECMP@ +HAVE_DECL_STRNDUP = @HAVE_DECL_STRNDUP@ +HAVE_DECL_STRNLEN = @HAVE_DECL_STRNLEN@ +HAVE_DECL_STRTOIMAX = @HAVE_DECL_STRTOIMAX@ +HAVE_DECL_STRTOK_R = @HAVE_DECL_STRTOK_R@ +HAVE_DECL_STRTOUMAX = @HAVE_DECL_STRTOUMAX@ +HAVE_DECL_VSNPRINTF = @HAVE_DECL_VSNPRINTF@ +HAVE_DUP2 = @HAVE_DUP2@ +HAVE_FTRUNCATE = @HAVE_FTRUNCATE@ +HAVE_GETSUBOPT = @HAVE_GETSUBOPT@ +HAVE_INTTYPES_H = @HAVE_INTTYPES_H@ +HAVE_LONG_LONG_INT = @HAVE_LONG_LONG_INT@ +HAVE_MEMPCPY = @HAVE_MEMPCPY@ +HAVE_MKDTEMP = @HAVE_MKDTEMP@ +HAVE_NETINET_IN_H = @HAVE_NETINET_IN_H@ +HAVE_READLINK = @HAVE_READLINK@ +HAVE_SIGNED_SIG_ATOMIC_T = @HAVE_SIGNED_SIG_ATOMIC_T@ +HAVE_SIGNED_WCHAR_T = @HAVE_SIGNED_WCHAR_T@ +HAVE_SIGNED_WINT_T = @HAVE_SIGNED_WINT_T@ +HAVE_STDINT_H = @HAVE_STDINT_H@ +HAVE_STPCPY = @HAVE_STPCPY@ +HAVE_STPNCPY = @HAVE_STPNCPY@ +HAVE_STRCASECMP = @HAVE_STRCASECMP@ +HAVE_STRCASESTR = @HAVE_STRCASESTR@ +HAVE_STRCHRNUL = @HAVE_STRCHRNUL@ +HAVE_STRNDUP = @HAVE_STRNDUP@ +HAVE_STRPBRK = @HAVE_STRPBRK@ +HAVE_STRSEP = @HAVE_STRSEP@ +HAVE_STRUCT_TIMEVAL = @HAVE_STRUCT_TIMEVAL@ +HAVE_SYS_BITYPES_H = @HAVE_SYS_BITYPES_H@ +HAVE_SYS_INTTYPES_H = @HAVE_SYS_INTTYPES_H@ +HAVE_SYS_SOCKET_H = @HAVE_SYS_SOCKET_H@ +HAVE_SYS_TIME_H = @HAVE_SYS_TIME_H@ +HAVE_SYS_TYPES_H = @HAVE_SYS_TYPES_H@ +HAVE_UNISTD_H = @HAVE_UNISTD_H@ +HAVE_UNSIGNED_LONG_LONG_INT = @HAVE_UNSIGNED_LONG_LONG_INT@ +HAVE_WCTYPE_H = @HAVE_WCTYPE_H@ +HAVE_WINSOCK2_H = @HAVE_WINSOCK2_H@ +HAVE_WINT_T = @HAVE_WINT_T@ +HAVE_WS2TCPIP_H = @HAVE_WS2TCPIP_H@ +HAVE__BOOL = @HAVE__BOOL@ +HELP2MAN = @HELP2MAN@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +INTTYPES_H = @INTTYPES_H@ +KMEM_GROUP = @KMEM_GROUP@ +LDFLAGS = @LDFLAGS@ +LIBCOREUTILS_LIBDEPS = @LIBCOREUTILS_LIBDEPS@ +LIBCOREUTILS_LTLIBDEPS = @LIBCOREUTILS_LTLIBDEPS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIB_ACL = @LIB_ACL@ +LIB_ACL_TRIVIAL = @LIB_ACL_TRIVIAL@ +LIB_CLOCK_GETTIME = @LIB_CLOCK_GETTIME@ +LIB_CRYPT = @LIB_CRYPT@ +LIB_EACCESS = @LIB_EACCESS@ +LIB_FDATASYNC = @LIB_FDATASYNC@ +LIB_GETHRXTIME = @LIB_GETHRXTIME@ +LIB_NANOSLEEP = @LIB_NANOSLEEP@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MAN = @MAN@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NEED_SETGID = @NEED_SETGID@ +NETINET_IN_H = @NETINET_IN_H@ +OBJEXT = @OBJEXT@ +OPTIONAL_BIN_PROGS = @OPTIONAL_BIN_PROGS@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +POSUB = @POSUB@ +POW_LIB = @POW_LIB@ +PRIPTR_PREFIX = @PRIPTR_PREFIX@ +PRI_MACROS_BROKEN = @PRI_MACROS_BROKEN@ +PTRDIFF_T_SUFFIX = @PTRDIFF_T_SUFFIX@ +RANLIB = @RANLIB@ +REPLACE_CHOWN = @REPLACE_CHOWN@ +REPLACE_FCHDIR = @REPLACE_FCHDIR@ +REPLACE_FPRINTF = @REPLACE_FPRINTF@ +REPLACE_GETCWD = @REPLACE_GETCWD@ +REPLACE_GETTIMEOFDAY = @REPLACE_GETTIMEOFDAY@ +REPLACE_LOCALTIME_R = @REPLACE_LOCALTIME_R@ +REPLACE_MKSTEMP = @REPLACE_MKSTEMP@ +REPLACE_NANOSLEEP = @REPLACE_NANOSLEEP@ +REPLACE_PRINTF = @REPLACE_PRINTF@ +REPLACE_SNPRINTF = @REPLACE_SNPRINTF@ +REPLACE_SPRINTF = @REPLACE_SPRINTF@ +REPLACE_STRPTIME = @REPLACE_STRPTIME@ +REPLACE_TIMEGM = @REPLACE_TIMEGM@ +REPLACE_VFPRINTF = @REPLACE_VFPRINTF@ +REPLACE_VPRINTF = @REPLACE_VPRINTF@ +REPLACE_VSNPRINTF = @REPLACE_VSNPRINTF@ +REPLACE_VSPRINTF = @REPLACE_VSPRINTF@ +SEQ_LIBM = @SEQ_LIBM@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SIG_ATOMIC_T_SUFFIX = @SIG_ATOMIC_T_SUFFIX@ +SIZE_T_SUFFIX = @SIZE_T_SUFFIX@ +STDBOOL_H = @STDBOOL_H@ +STDINT_H = @STDINT_H@ +STRIP = @STRIP@ +SYS_SOCKET_H = @SYS_SOCKET_H@ +SYS_STAT_H = @SYS_STAT_H@ +SYS_TIME_H = @SYS_TIME_H@ +SYS_TIME_H_DEFINES_STRUCT_TIMESPEC = @SYS_TIME_H_DEFINES_STRUCT_TIMESPEC@ +TIME_H_DEFINES_STRUCT_TIMESPEC = @TIME_H_DEFINES_STRUCT_TIMESPEC@ +U = @U@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +WCHAR_H = @WCHAR_H@ +WCHAR_T_SUFFIX = @WCHAR_T_SUFFIX@ +WCTYPE_H = @WCTYPE_H@ +WINT_T_SUFFIX = @WINT_T_SUFFIX@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +YACC = @YACC@ +YFLAGS = @YFLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +gl_LIBOBJS = @gl_LIBOBJS@ +gl_LTLIBOBJS = @gl_LTLIBOBJS@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +bin_SCRIPTS = groups +noinst_HEADERS = \ + chown-core.h \ + copy.h \ + cp-hash.h \ + dircolors.h \ + fs.h \ + ls.h \ + remove.h \ + system.h \ + wheel-size.h \ + wheel.h + +EXTRA_DIST = dcgen dircolors.hin tac-pipe.c \ + groups.sh wheel-gen.pl extract-magic c99-to-c89.diff + +BUILT_SOURCES = dircolors.h wheel-size.h wheel.h fs.h +CLEANFILES = $(SCRIPTS) su +AM_CPPFLAGS = -I$(top_srcdir)/lib + +# Sometimes, the expansion of $(LIBINTL) includes -lc which may +# include modules defining variables like `optind', so libcoreutils.a +# must precede $(LIBINTL) in order to ensure we use GNU getopt. +# But libcoreutils.a must also follow $(LIBINTL), since libintl uses +# replacement functions defined in libcoreutils.a. +LDADD = ../lib/libcoreutils.a $(LIBINTL) ../lib/libcoreutils.a + +# for eaccess in lib/euidaccess.c. +cp_LDADD = $(LDADD) $(LIB_EACCESS) $(LIB_ACL) +ginstall_LDADD = $(LDADD) $(LIB_EACCESS) $(LIB_ACL) +mv_LDADD = $(LDADD) $(LIB_EACCESS) $(LIB_ACL) +pathchk_LDADD = $(LDADD) $(LIB_EACCESS) +rm_LDADD = $(LDADD) $(LIB_EACCESS) +test_LDADD = $(LDADD) $(LIB_EACCESS) +# This is for the '[' program. Automake transliterates '[' to '_'. +__LDADD = $(LDADD) $(LIB_EACCESS) + +# for clock_gettime and fdatasync +dd_LDADD = $(LDADD) $(LIB_GETHRXTIME) $(LIB_FDATASYNC) +dir_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) $(LIB_ACL_TRIVIAL) \ + $(LIB_ACL) +ls_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) $(LIB_ACL_TRIVIAL) $(LIB_ACL) +pr_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) +shred_LDADD = $(LDADD) $(LIB_GETHRXTIME) $(LIB_FDATASYNC) +shuf_LDADD = $(LDADD) $(LIB_GETHRXTIME) +vdir_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) $(LIB_ACL_TRIVIAL) \ + $(LIB_ACL) +sort_LDADD = $(LDADD) $(POW_LIB) $(LIB_GETHRXTIME) + +# for get_date and gettime +date_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) +touch_LDADD = $(LDADD) $(LIB_CLOCK_GETTIME) + +# If necessary, add -lm to resolve use of pow in lib/strtod.c. +# If necessary, add -liconv to resolve use of iconv in lib/unicodeio.c. +printf_LDADD = $(LDADD) $(POW_LIB) $(LIBICONV) + +# If necessary, add -lm to resolve use of pow in lib/strtod.c. +seq_LDADD = $(LDADD) $(POW_LIB) + +# If necessary, add libraries to resolve the `pow' reference in lib/strtod.c +# and the `nanosleep' reference in lib/xnanosleep.c. +nanosec_libs = $(LDADD) $(POW_LIB) $(LIB_NANOSLEEP) +sleep_LDADD = $(nanosec_libs) +tail_LDADD = $(nanosec_libs) + +# If necessary, add -lm to resolve use of pow in lib/strtod.c. +uptime_LDADD = $(LDADD) $(POW_LIB) $(GETLOADAVG_LIBS) +su_LDADD = $(LDADD) $(LIB_CRYPT) +SUFFIXES = .sh + +# Get the release year from ../lib/version-etc.c. +RELEASE_YEAR = \ + `sed -n '/.*COPYRIGHT_YEAR = \([0-9][0-9][0-9][0-9]\) };/s//\1/p' \ + $(top_srcdir)/lib/version-etc.c` + +installed_su = $(DESTDIR)$(bindir)/`echo su|sed '$(transform)'` +setuid_root_mode = a=rx,u+s +INSTALL_SU = \ + p=su; \ + echo " $(INSTALL_PROGRAM) $$p $(installed_su)"; \ + $(INSTALL_PROGRAM) $$p $(installed_su); \ + echo " chown root $(installed_su)"; \ + chown root $(installed_su); \ + echo " chmod $(setuid_root_mode) $(installed_su)"; \ + chmod $(setuid_root_mode) $(installed_su) + +ginstall_SOURCES = install.c copy.c cp-hash.c + +# This is for the '[' program. Automake transliterates '[' to '_'. +__SOURCES = lbracket.c +cp_SOURCES = cp.c copy.c cp-hash.c +dir_SOURCES = ls.c ls-dir.c +vdir_SOURCES = ls.c ls-vdir.c +ls_SOURCES = ls.c ls-ls.c +chown_SOURCES = chown.c chown-core.c +chgrp_SOURCES = chgrp.c chown-core.c +mv_SOURCES = mv.c copy.c cp-hash.c remove.c +rm_SOURCES = rm.c remove.c +md5sum_SOURCES = md5sum.c +md5sum_CPPFLAGS = -DHASH_ALGO_MD5=1 $(AM_CPPFLAGS) +sha1sum_SOURCES = md5sum.c +sha1sum_CPPFLAGS = -DHASH_ALGO_SHA1=1 $(AM_CPPFLAGS) +sha224sum_SOURCES = md5sum.c +sha224sum_CPPFLAGS = -DHASH_ALGO_SHA224=1 $(AM_CPPFLAGS) +sha256sum_SOURCES = md5sum.c +sha256sum_CPPFLAGS = -DHASH_ALGO_SHA256=1 $(AM_CPPFLAGS) +sha384sum_SOURCES = md5sum.c +sha384sum_CPPFLAGS = -DHASH_ALGO_SHA384=1 $(AM_CPPFLAGS) +sha512sum_SOURCES = md5sum.c +sha512sum_CPPFLAGS = -DHASH_ALGO_SHA512=1 $(AM_CPPFLAGS) +editpl = sed -e 's,@''PERL''@,$(PERL),g' +wheel_size = 5 + +# false exits nonzero even with --help or --version. +# test doesn't support --help or --version. +# Tell automake to exempt then from that installcheck test. +AM_INSTALLCHECK_STD_OPTIONS_EXEMPT = false test +MAINTAINERCLEANFILES = $(BUILT_SOURCES) + +# Sort in traditional ASCII order, regardless of the current locale; +# otherwise we may get into trouble with distinct strings that the +# current locale considers to be equal. +ASSORT = LC_ALL=C sort +all_programs = \ + $(bin_PROGRAMS) \ + $(bin_SCRIPTS) \ + $(EXTRA_PROGRAMS) + +pm = progs-makefile +pr = progs-readme + +# Ensure that the list of programs and author names is accurate. +au_dotdot = authors-dotdot +au_actual = authors-actual + +# Extract the list of authors from each file. +sed_filter = s/^ *//;s/N_ (//;s/^"//;s/")*$$// +# Sometimes the string is on the same line as the #define... +s1 = '/^\#define AUTHORS \([^\\]\)/{;s//\1/;$(sed_filter);p;q;}' +# Sometimes the string is on the backslash-continued line after the #define. +s2 = '/^\#define AUTHORS \\\\/{;n;$(sed_filter);p;q;}' +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .sh .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + if test -f $$p \ + ; then \ + f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \ + $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \ + else :; fi; \ + done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \ + rm -f "$(DESTDIR)$(bindir)/$$f"; \ + done + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) > /dev/null 2>&1 || /bin/rm -f $(bin_PROGRAMS) + +clean-noinstPROGRAMS: + -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS) +[$(EXEEXT): $(__OBJECTS) $(__DEPENDENCIES) + @rm -f [$(EXEEXT) + $(LINK) $(__OBJECTS) $(__LDADD) $(LIBS) +base64$(EXEEXT): $(base64_OBJECTS) $(base64_DEPENDENCIES) + @rm -f base64$(EXEEXT) + $(LINK) $(base64_OBJECTS) $(base64_LDADD) $(LIBS) +basename$(EXEEXT): $(basename_OBJECTS) $(basename_DEPENDENCIES) + @rm -f basename$(EXEEXT) + $(LINK) $(basename_OBJECTS) $(basename_LDADD) $(LIBS) +cat$(EXEEXT): $(cat_OBJECTS) $(cat_DEPENDENCIES) + @rm -f cat$(EXEEXT) + $(LINK) $(cat_OBJECTS) $(cat_LDADD) $(LIBS) +chgrp$(EXEEXT): $(chgrp_OBJECTS) $(chgrp_DEPENDENCIES) + @rm -f chgrp$(EXEEXT) + $(LINK) $(chgrp_OBJECTS) $(chgrp_LDADD) $(LIBS) +chmod$(EXEEXT): $(chmod_OBJECTS) $(chmod_DEPENDENCIES) + @rm -f chmod$(EXEEXT) + $(LINK) $(chmod_OBJECTS) $(chmod_LDADD) $(LIBS) +chown$(EXEEXT): $(chown_OBJECTS) $(chown_DEPENDENCIES) + @rm -f chown$(EXEEXT) + $(LINK) $(chown_OBJECTS) $(chown_LDADD) $(LIBS) +chroot$(EXEEXT): $(chroot_OBJECTS) $(chroot_DEPENDENCIES) + @rm -f chroot$(EXEEXT) + $(LINK) $(chroot_OBJECTS) $(chroot_LDADD) $(LIBS) +cksum$(EXEEXT): $(cksum_OBJECTS) $(cksum_DEPENDENCIES) + @rm -f cksum$(EXEEXT) + $(LINK) $(cksum_OBJECTS) $(cksum_LDADD) $(LIBS) +comm$(EXEEXT): $(comm_OBJECTS) $(comm_DEPENDENCIES) + @rm -f comm$(EXEEXT) + $(LINK) $(comm_OBJECTS) $(comm_LDADD) $(LIBS) +cp$(EXEEXT): $(cp_OBJECTS) $(cp_DEPENDENCIES) + @rm -f cp$(EXEEXT) + $(LINK) $(cp_OBJECTS) $(cp_LDADD) $(LIBS) +csplit$(EXEEXT): $(csplit_OBJECTS) $(csplit_DEPENDENCIES) + @rm -f csplit$(EXEEXT) + $(LINK) $(csplit_OBJECTS) $(csplit_LDADD) $(LIBS) +cut$(EXEEXT): $(cut_OBJECTS) $(cut_DEPENDENCIES) + @rm -f cut$(EXEEXT) + $(LINK) $(cut_OBJECTS) $(cut_LDADD) $(LIBS) +date$(EXEEXT): $(date_OBJECTS) $(date_DEPENDENCIES) + @rm -f date$(EXEEXT) + $(LINK) $(date_OBJECTS) $(date_LDADD) $(LIBS) +dd$(EXEEXT): $(dd_OBJECTS) $(dd_DEPENDENCIES) + @rm -f dd$(EXEEXT) + $(LINK) $(dd_OBJECTS) $(dd_LDADD) $(LIBS) +df$(EXEEXT): $(df_OBJECTS) $(df_DEPENDENCIES) + @rm -f df$(EXEEXT) + $(LINK) $(df_OBJECTS) $(df_LDADD) $(LIBS) +dir$(EXEEXT): $(dir_OBJECTS) $(dir_DEPENDENCIES) + @rm -f dir$(EXEEXT) + $(LINK) $(dir_OBJECTS) $(dir_LDADD) $(LIBS) +dircolors$(EXEEXT): $(dircolors_OBJECTS) $(dircolors_DEPENDENCIES) + @rm -f dircolors$(EXEEXT) + $(LINK) $(dircolors_OBJECTS) $(dircolors_LDADD) $(LIBS) +dirname$(EXEEXT): $(dirname_OBJECTS) $(dirname_DEPENDENCIES) + @rm -f dirname$(EXEEXT) + $(LINK) $(dirname_OBJECTS) $(dirname_LDADD) $(LIBS) +du$(EXEEXT): $(du_OBJECTS) $(du_DEPENDENCIES) + @rm -f du$(EXEEXT) + $(LINK) $(du_OBJECTS) $(du_LDADD) $(LIBS) +echo$(EXEEXT): $(echo_OBJECTS) $(echo_DEPENDENCIES) + @rm -f echo$(EXEEXT) + $(LINK) $(echo_OBJECTS) $(echo_LDADD) $(LIBS) +env$(EXEEXT): $(env_OBJECTS) $(env_DEPENDENCIES) + @rm -f env$(EXEEXT) + $(LINK) $(env_OBJECTS) $(env_LDADD) $(LIBS) +expand$(EXEEXT): $(expand_OBJECTS) $(expand_DEPENDENCIES) + @rm -f expand$(EXEEXT) + $(LINK) $(expand_OBJECTS) $(expand_LDADD) $(LIBS) +expr$(EXEEXT): $(expr_OBJECTS) $(expr_DEPENDENCIES) + @rm -f expr$(EXEEXT) + $(LINK) $(expr_OBJECTS) $(expr_LDADD) $(LIBS) +factor$(EXEEXT): $(factor_OBJECTS) $(factor_DEPENDENCIES) + @rm -f factor$(EXEEXT) + $(LINK) $(factor_OBJECTS) $(factor_LDADD) $(LIBS) +false$(EXEEXT): $(false_OBJECTS) $(false_DEPENDENCIES) + @rm -f false$(EXEEXT) + $(LINK) $(false_OBJECTS) $(false_LDADD) $(LIBS) +fmt$(EXEEXT): $(fmt_OBJECTS) $(fmt_DEPENDENCIES) + @rm -f fmt$(EXEEXT) + $(LINK) $(fmt_OBJECTS) $(fmt_LDADD) $(LIBS) +fold$(EXEEXT): $(fold_OBJECTS) $(fold_DEPENDENCIES) + @rm -f fold$(EXEEXT) + $(LINK) $(fold_OBJECTS) $(fold_LDADD) $(LIBS) +ginstall$(EXEEXT): $(ginstall_OBJECTS) $(ginstall_DEPENDENCIES) + @rm -f ginstall$(EXEEXT) + $(LINK) $(ginstall_OBJECTS) $(ginstall_LDADD) $(LIBS) +head$(EXEEXT): $(head_OBJECTS) $(head_DEPENDENCIES) + @rm -f head$(EXEEXT) + $(LINK) $(head_OBJECTS) $(head_LDADD) $(LIBS) +hostid$(EXEEXT): $(hostid_OBJECTS) $(hostid_DEPENDENCIES) + @rm -f hostid$(EXEEXT) + $(LINK) $(hostid_OBJECTS) $(hostid_LDADD) $(LIBS) +hostname$(EXEEXT): $(hostname_OBJECTS) $(hostname_DEPENDENCIES) + @rm -f hostname$(EXEEXT) + $(LINK) $(hostname_OBJECTS) $(hostname_LDADD) $(LIBS) +id$(EXEEXT): $(id_OBJECTS) $(id_DEPENDENCIES) + @rm -f id$(EXEEXT) + $(LINK) $(id_OBJECTS) $(id_LDADD) $(LIBS) +join$(EXEEXT): $(join_OBJECTS) $(join_DEPENDENCIES) + @rm -f join$(EXEEXT) + $(LINK) $(join_OBJECTS) $(join_LDADD) $(LIBS) +kill$(EXEEXT): $(kill_OBJECTS) $(kill_DEPENDENCIES) + @rm -f kill$(EXEEXT) + $(LINK) $(kill_OBJECTS) $(kill_LDADD) $(LIBS) +link$(EXEEXT): $(link_OBJECTS) $(link_DEPENDENCIES) + @rm -f link$(EXEEXT) + $(LINK) $(link_OBJECTS) $(link_LDADD) $(LIBS) +ln$(EXEEXT): $(ln_OBJECTS) $(ln_DEPENDENCIES) + @rm -f ln$(EXEEXT) + $(LINK) $(ln_OBJECTS) $(ln_LDADD) $(LIBS) +logname$(EXEEXT): $(logname_OBJECTS) $(logname_DEPENDENCIES) + @rm -f logname$(EXEEXT) + $(LINK) $(logname_OBJECTS) $(logname_LDADD) $(LIBS) +ls$(EXEEXT): $(ls_OBJECTS) $(ls_DEPENDENCIES) + @rm -f ls$(EXEEXT) + $(LINK) $(ls_OBJECTS) $(ls_LDADD) $(LIBS) +md5sum$(EXEEXT): $(md5sum_OBJECTS) $(md5sum_DEPENDENCIES) + @rm -f md5sum$(EXEEXT) + $(LINK) $(md5sum_OBJECTS) $(md5sum_LDADD) $(LIBS) +mkdir$(EXEEXT): $(mkdir_OBJECTS) $(mkdir_DEPENDENCIES) + @rm -f mkdir$(EXEEXT) + $(LINK) $(mkdir_OBJECTS) $(mkdir_LDADD) $(LIBS) +mkfifo$(EXEEXT): $(mkfifo_OBJECTS) $(mkfifo_DEPENDENCIES) + @rm -f mkfifo$(EXEEXT) + $(LINK) $(mkfifo_OBJECTS) $(mkfifo_LDADD) $(LIBS) +mknod$(EXEEXT): $(mknod_OBJECTS) $(mknod_DEPENDENCIES) + @rm -f mknod$(EXEEXT) + $(LINK) $(mknod_OBJECTS) $(mknod_LDADD) $(LIBS) +mv$(EXEEXT): $(mv_OBJECTS) $(mv_DEPENDENCIES) + @rm -f mv$(EXEEXT) + $(LINK) $(mv_OBJECTS) $(mv_LDADD) $(LIBS) +nice$(EXEEXT): $(nice_OBJECTS) $(nice_DEPENDENCIES) + @rm -f nice$(EXEEXT) + $(LINK) $(nice_OBJECTS) $(nice_LDADD) $(LIBS) +nl$(EXEEXT): $(nl_OBJECTS) $(nl_DEPENDENCIES) + @rm -f nl$(EXEEXT) + $(LINK) $(nl_OBJECTS) $(nl_LDADD) $(LIBS) +nohup$(EXEEXT): $(nohup_OBJECTS) $(nohup_DEPENDENCIES) + @rm -f nohup$(EXEEXT) + $(LINK) $(nohup_OBJECTS) $(nohup_LDADD) $(LIBS) +od$(EXEEXT): $(od_OBJECTS) $(od_DEPENDENCIES) + @rm -f od$(EXEEXT) + $(LINK) $(od_OBJECTS) $(od_LDADD) $(LIBS) +paste$(EXEEXT): $(paste_OBJECTS) $(paste_DEPENDENCIES) + @rm -f paste$(EXEEXT) + $(LINK) $(paste_OBJECTS) $(paste_LDADD) $(LIBS) +pathchk$(EXEEXT): $(pathchk_OBJECTS) $(pathchk_DEPENDENCIES) + @rm -f pathchk$(EXEEXT) + $(LINK) $(pathchk_OBJECTS) $(pathchk_LDADD) $(LIBS) +pinky$(EXEEXT): $(pinky_OBJECTS) $(pinky_DEPENDENCIES) + @rm -f pinky$(EXEEXT) + $(LINK) $(pinky_OBJECTS) $(pinky_LDADD) $(LIBS) +pr$(EXEEXT): $(pr_OBJECTS) $(pr_DEPENDENCIES) + @rm -f pr$(EXEEXT) + $(LINK) $(pr_OBJECTS) $(pr_LDADD) $(LIBS) +printenv$(EXEEXT): $(printenv_OBJECTS) $(printenv_DEPENDENCIES) + @rm -f printenv$(EXEEXT) + $(LINK) $(printenv_OBJECTS) $(printenv_LDADD) $(LIBS) +printf$(EXEEXT): $(printf_OBJECTS) $(printf_DEPENDENCIES) + @rm -f printf$(EXEEXT) + $(LINK) $(printf_OBJECTS) $(printf_LDADD) $(LIBS) +ptx$(EXEEXT): $(ptx_OBJECTS) $(ptx_DEPENDENCIES) + @rm -f ptx$(EXEEXT) + $(LINK) $(ptx_OBJECTS) $(ptx_LDADD) $(LIBS) +pwd$(EXEEXT): $(pwd_OBJECTS) $(pwd_DEPENDENCIES) + @rm -f pwd$(EXEEXT) + $(LINK) $(pwd_OBJECTS) $(pwd_LDADD) $(LIBS) +readlink$(EXEEXT): $(readlink_OBJECTS) $(readlink_DEPENDENCIES) + @rm -f readlink$(EXEEXT) + $(LINK) $(readlink_OBJECTS) $(readlink_LDADD) $(LIBS) +rm$(EXEEXT): $(rm_OBJECTS) $(rm_DEPENDENCIES) + @rm -f rm$(EXEEXT) + $(LINK) $(rm_OBJECTS) $(rm_LDADD) $(LIBS) +rmdir$(EXEEXT): $(rmdir_OBJECTS) $(rmdir_DEPENDENCIES) + @rm -f rmdir$(EXEEXT) + $(LINK) $(rmdir_OBJECTS) $(rmdir_LDADD) $(LIBS) +seq$(EXEEXT): $(seq_OBJECTS) $(seq_DEPENDENCIES) + @rm -f seq$(EXEEXT) + $(LINK) $(seq_OBJECTS) $(seq_LDADD) $(LIBS) +setuidgid$(EXEEXT): $(setuidgid_OBJECTS) $(setuidgid_DEPENDENCIES) + @rm -f setuidgid$(EXEEXT) + $(LINK) $(setuidgid_OBJECTS) $(setuidgid_LDADD) $(LIBS) +sha1sum$(EXEEXT): $(sha1sum_OBJECTS) $(sha1sum_DEPENDENCIES) + @rm -f sha1sum$(EXEEXT) + $(LINK) $(sha1sum_OBJECTS) $(sha1sum_LDADD) $(LIBS) +sha224sum$(EXEEXT): $(sha224sum_OBJECTS) $(sha224sum_DEPENDENCIES) + @rm -f sha224sum$(EXEEXT) + $(LINK) $(sha224sum_OBJECTS) $(sha224sum_LDADD) $(LIBS) +sha256sum$(EXEEXT): $(sha256sum_OBJECTS) $(sha256sum_DEPENDENCIES) + @rm -f sha256sum$(EXEEXT) + $(LINK) $(sha256sum_OBJECTS) $(sha256sum_LDADD) $(LIBS) +sha384sum$(EXEEXT): $(sha384sum_OBJECTS) $(sha384sum_DEPENDENCIES) + @rm -f sha384sum$(EXEEXT) + $(LINK) $(sha384sum_OBJECTS) $(sha384sum_LDADD) $(LIBS) +sha512sum$(EXEEXT): $(sha512sum_OBJECTS) $(sha512sum_DEPENDENCIES) + @rm -f sha512sum$(EXEEXT) + $(LINK) $(sha512sum_OBJECTS) $(sha512sum_LDADD) $(LIBS) +shred$(EXEEXT): $(shred_OBJECTS) $(shred_DEPENDENCIES) + @rm -f shred$(EXEEXT) + $(LINK) $(shred_OBJECTS) $(shred_LDADD) $(LIBS) +shuf$(EXEEXT): $(shuf_OBJECTS) $(shuf_DEPENDENCIES) + @rm -f shuf$(EXEEXT) + $(LINK) $(shuf_OBJECTS) $(shuf_LDADD) $(LIBS) +sleep$(EXEEXT): $(sleep_OBJECTS) $(sleep_DEPENDENCIES) + @rm -f sleep$(EXEEXT) + $(LINK) $(sleep_OBJECTS) $(sleep_LDADD) $(LIBS) +sort$(EXEEXT): $(sort_OBJECTS) $(sort_DEPENDENCIES) + @rm -f sort$(EXEEXT) + $(LINK) $(sort_OBJECTS) $(sort_LDADD) $(LIBS) +split$(EXEEXT): $(split_OBJECTS) $(split_DEPENDENCIES) + @rm -f split$(EXEEXT) + $(LINK) $(split_OBJECTS) $(split_LDADD) $(LIBS) +stat$(EXEEXT): $(stat_OBJECTS) $(stat_DEPENDENCIES) + @rm -f stat$(EXEEXT) + $(LINK) $(stat_OBJECTS) $(stat_LDADD) $(LIBS) +stty$(EXEEXT): $(stty_OBJECTS) $(stty_DEPENDENCIES) + @rm -f stty$(EXEEXT) + $(LINK) $(stty_OBJECTS) $(stty_LDADD) $(LIBS) +su$(EXEEXT): $(su_OBJECTS) $(su_DEPENDENCIES) + @rm -f su$(EXEEXT) + $(LINK) $(su_OBJECTS) $(su_LDADD) $(LIBS) +sum$(EXEEXT): $(sum_OBJECTS) $(sum_DEPENDENCIES) + @rm -f sum$(EXEEXT) + $(LINK) $(sum_OBJECTS) $(sum_LDADD) $(LIBS) +sync$(EXEEXT): $(sync_OBJECTS) $(sync_DEPENDENCIES) + @rm -f sync$(EXEEXT) + $(LINK) $(sync_OBJECTS) $(sync_LDADD) $(LIBS) +tac$(EXEEXT): $(tac_OBJECTS) $(tac_DEPENDENCIES) + @rm -f tac$(EXEEXT) + $(LINK) $(tac_OBJECTS) $(tac_LDADD) $(LIBS) +tail$(EXEEXT): $(tail_OBJECTS) $(tail_DEPENDENCIES) + @rm -f tail$(EXEEXT) + $(LINK) $(tail_OBJECTS) $(tail_LDADD) $(LIBS) +tee$(EXEEXT): $(tee_OBJECTS) $(tee_DEPENDENCIES) + @rm -f tee$(EXEEXT) + $(LINK) $(tee_OBJECTS) $(tee_LDADD) $(LIBS) +test$(EXEEXT): $(test_OBJECTS) $(test_DEPENDENCIES) + @rm -f test$(EXEEXT) + $(LINK) $(test_OBJECTS) $(test_LDADD) $(LIBS) +touch$(EXEEXT): $(touch_OBJECTS) $(touch_DEPENDENCIES) + @rm -f touch$(EXEEXT) + $(LINK) $(touch_OBJECTS) $(touch_LDADD) $(LIBS) +tr$(EXEEXT): $(tr_OBJECTS) $(tr_DEPENDENCIES) + @rm -f tr$(EXEEXT) + $(LINK) $(tr_OBJECTS) $(tr_LDADD) $(LIBS) +true$(EXEEXT): $(true_OBJECTS) $(true_DEPENDENCIES) + @rm -f true$(EXEEXT) + $(LINK) $(true_OBJECTS) $(true_LDADD) $(LIBS) +tsort$(EXEEXT): $(tsort_OBJECTS) $(tsort_DEPENDENCIES) + @rm -f tsort$(EXEEXT) + $(LINK) $(tsort_OBJECTS) $(tsort_LDADD) $(LIBS) +tty$(EXEEXT): $(tty_OBJECTS) $(tty_DEPENDENCIES) + @rm -f tty$(EXEEXT) + $(LINK) $(tty_OBJECTS) $(tty_LDADD) $(LIBS) +uname$(EXEEXT): $(uname_OBJECTS) $(uname_DEPENDENCIES) + @rm -f uname$(EXEEXT) + $(LINK) $(uname_OBJECTS) $(uname_LDADD) $(LIBS) +unexpand$(EXEEXT): $(unexpand_OBJECTS) $(unexpand_DEPENDENCIES) + @rm -f unexpand$(EXEEXT) + $(LINK) $(unexpand_OBJECTS) $(unexpand_LDADD) $(LIBS) +uniq$(EXEEXT): $(uniq_OBJECTS) $(uniq_DEPENDENCIES) + @rm -f uniq$(EXEEXT) + $(LINK) $(uniq_OBJECTS) $(uniq_LDADD) $(LIBS) +unlink$(EXEEXT): $(unlink_OBJECTS) $(unlink_DEPENDENCIES) + @rm -f unlink$(EXEEXT) + $(LINK) $(unlink_OBJECTS) $(unlink_LDADD) $(LIBS) +uptime$(EXEEXT): $(uptime_OBJECTS) $(uptime_DEPENDENCIES) + @rm -f uptime$(EXEEXT) + $(LINK) $(uptime_OBJECTS) $(uptime_LDADD) $(LIBS) +users$(EXEEXT): $(users_OBJECTS) $(users_DEPENDENCIES) + @rm -f users$(EXEEXT) + $(LINK) $(users_OBJECTS) $(users_LDADD) $(LIBS) +vdir$(EXEEXT): $(vdir_OBJECTS) $(vdir_DEPENDENCIES) + @rm -f vdir$(EXEEXT) + $(LINK) $(vdir_OBJECTS) $(vdir_LDADD) $(LIBS) +wc$(EXEEXT): $(wc_OBJECTS) $(wc_DEPENDENCIES) + @rm -f wc$(EXEEXT) + $(LINK) $(wc_OBJECTS) $(wc_LDADD) $(LIBS) +who$(EXEEXT): $(who_OBJECTS) $(who_DEPENDENCIES) + @rm -f who$(EXEEXT) + $(LINK) $(who_OBJECTS) $(who_LDADD) $(LIBS) +whoami$(EXEEXT): $(whoami_OBJECTS) $(whoami_DEPENDENCIES) + @rm -f whoami$(EXEEXT) + $(LINK) $(whoami_OBJECTS) $(whoami_LDADD) $(LIBS) +yes$(EXEEXT): $(yes_OBJECTS) $(yes_DEPENDENCIES) + @rm -f yes$(EXEEXT) + $(LINK) $(yes_OBJECTS) $(yes_LDADD) $(LIBS) +install-binSCRIPTS: $(bin_SCRIPTS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_SCRIPTS)'; for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + if test -f $$d$$p; then \ + f=`echo "$$p" | sed 's|^.*/||;$(transform)'`; \ + echo " $(binSCRIPT_INSTALL) '$$d$$p' '$(DESTDIR)$(bindir)/$$f'"; \ + $(binSCRIPT_INSTALL) "$$d$$p" "$(DESTDIR)$(bindir)/$$f"; \ + else :; fi; \ + done + +uninstall-binSCRIPTS: + @$(NORMAL_UNINSTALL) + @list='$(bin_SCRIPTS)'; for p in $$list; do \ + f=`echo "$$p" | sed 's|^.*/||;$(transform)'`; \ + echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \ + rm -f "$(DESTDIR)$(bindir)/$$f"; \ + done + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base64.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/basename.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cat.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chgrp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chmod.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chown-core.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chown.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chroot.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cksum.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/comm.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/copy.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cp-hash.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cp.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/csplit.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cut.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/date.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dd.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/df.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dircolors.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dirname.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/du.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/echo.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/env.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/expand.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/expr.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/factor.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/false.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fmt.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fold.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/head.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hostid.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hostname.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/id.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/install.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/join.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/kill.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lbracket.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/link.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ln.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logname.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ls-dir.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ls-ls.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ls-vdir.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ls.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5sum-md5sum.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mkdir.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mkfifo.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mknod.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mv.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nice.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nl.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nohup.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/od.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/paste.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pathchk.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pinky.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pr.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/printenv.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/printf.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ptx.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pwd.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/readlink.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/remove.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rm.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rmdir.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/seq.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/setuidgid.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sha1sum-md5sum.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sha224sum-md5sum.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sha256sum-md5sum.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sha384sum-md5sum.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sha512sum-md5sum.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/shred.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/shuf.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sleep.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sort.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/split.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stat.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/su.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sum.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sync.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tac.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tail.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tee.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/touch.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tr.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/true.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsort.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tty.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/uname.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unexpand.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/uniq.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/unlink.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/uptime.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/users.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/wc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/who.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/whoami.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/yes.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +md5sum-md5sum.o: md5sum.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(md5sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT md5sum-md5sum.o -MD -MP -MF $(DEPDIR)/md5sum-md5sum.Tpo -c -o md5sum-md5sum.o `test -f 'md5sum.c' || echo '$(srcdir)/'`md5sum.c +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/md5sum-md5sum.Tpo $(DEPDIR)/md5sum-md5sum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5sum.c' object='md5sum-md5sum.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(md5sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o md5sum-md5sum.o `test -f 'md5sum.c' || echo '$(srcdir)/'`md5sum.c + +md5sum-md5sum.obj: md5sum.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(md5sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT md5sum-md5sum.obj -MD -MP -MF $(DEPDIR)/md5sum-md5sum.Tpo -c -o md5sum-md5sum.obj `if test -f 'md5sum.c'; then $(CYGPATH_W) 'md5sum.c'; else $(CYGPATH_W) '$(srcdir)/md5sum.c'; fi` +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/md5sum-md5sum.Tpo $(DEPDIR)/md5sum-md5sum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5sum.c' object='md5sum-md5sum.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(md5sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o md5sum-md5sum.obj `if test -f 'md5sum.c'; then $(CYGPATH_W) 'md5sum.c'; else $(CYGPATH_W) '$(srcdir)/md5sum.c'; fi` + +sha1sum-md5sum.o: md5sum.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha1sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT sha1sum-md5sum.o -MD -MP -MF $(DEPDIR)/sha1sum-md5sum.Tpo -c -o sha1sum-md5sum.o `test -f 'md5sum.c' || echo '$(srcdir)/'`md5sum.c +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/sha1sum-md5sum.Tpo $(DEPDIR)/sha1sum-md5sum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5sum.c' object='sha1sum-md5sum.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha1sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o sha1sum-md5sum.o `test -f 'md5sum.c' || echo '$(srcdir)/'`md5sum.c + +sha1sum-md5sum.obj: md5sum.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha1sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT sha1sum-md5sum.obj -MD -MP -MF $(DEPDIR)/sha1sum-md5sum.Tpo -c -o sha1sum-md5sum.obj `if test -f 'md5sum.c'; then $(CYGPATH_W) 'md5sum.c'; else $(CYGPATH_W) '$(srcdir)/md5sum.c'; fi` +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/sha1sum-md5sum.Tpo $(DEPDIR)/sha1sum-md5sum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5sum.c' object='sha1sum-md5sum.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha1sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o sha1sum-md5sum.obj `if test -f 'md5sum.c'; then $(CYGPATH_W) 'md5sum.c'; else $(CYGPATH_W) '$(srcdir)/md5sum.c'; fi` + +sha224sum-md5sum.o: md5sum.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha224sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT sha224sum-md5sum.o -MD -MP -MF $(DEPDIR)/sha224sum-md5sum.Tpo -c -o sha224sum-md5sum.o `test -f 'md5sum.c' || echo '$(srcdir)/'`md5sum.c +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/sha224sum-md5sum.Tpo $(DEPDIR)/sha224sum-md5sum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5sum.c' object='sha224sum-md5sum.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha224sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o sha224sum-md5sum.o `test -f 'md5sum.c' || echo '$(srcdir)/'`md5sum.c + +sha224sum-md5sum.obj: md5sum.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha224sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT sha224sum-md5sum.obj -MD -MP -MF $(DEPDIR)/sha224sum-md5sum.Tpo -c -o sha224sum-md5sum.obj `if test -f 'md5sum.c'; then $(CYGPATH_W) 'md5sum.c'; else $(CYGPATH_W) '$(srcdir)/md5sum.c'; fi` +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/sha224sum-md5sum.Tpo $(DEPDIR)/sha224sum-md5sum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5sum.c' object='sha224sum-md5sum.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha224sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o sha224sum-md5sum.obj `if test -f 'md5sum.c'; then $(CYGPATH_W) 'md5sum.c'; else $(CYGPATH_W) '$(srcdir)/md5sum.c'; fi` + +sha256sum-md5sum.o: md5sum.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha256sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT sha256sum-md5sum.o -MD -MP -MF $(DEPDIR)/sha256sum-md5sum.Tpo -c -o sha256sum-md5sum.o `test -f 'md5sum.c' || echo '$(srcdir)/'`md5sum.c +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/sha256sum-md5sum.Tpo $(DEPDIR)/sha256sum-md5sum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5sum.c' object='sha256sum-md5sum.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha256sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o sha256sum-md5sum.o `test -f 'md5sum.c' || echo '$(srcdir)/'`md5sum.c + +sha256sum-md5sum.obj: md5sum.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha256sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT sha256sum-md5sum.obj -MD -MP -MF $(DEPDIR)/sha256sum-md5sum.Tpo -c -o sha256sum-md5sum.obj `if test -f 'md5sum.c'; then $(CYGPATH_W) 'md5sum.c'; else $(CYGPATH_W) '$(srcdir)/md5sum.c'; fi` +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/sha256sum-md5sum.Tpo $(DEPDIR)/sha256sum-md5sum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5sum.c' object='sha256sum-md5sum.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha256sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o sha256sum-md5sum.obj `if test -f 'md5sum.c'; then $(CYGPATH_W) 'md5sum.c'; else $(CYGPATH_W) '$(srcdir)/md5sum.c'; fi` + +sha384sum-md5sum.o: md5sum.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha384sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT sha384sum-md5sum.o -MD -MP -MF $(DEPDIR)/sha384sum-md5sum.Tpo -c -o sha384sum-md5sum.o `test -f 'md5sum.c' || echo '$(srcdir)/'`md5sum.c +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/sha384sum-md5sum.Tpo $(DEPDIR)/sha384sum-md5sum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5sum.c' object='sha384sum-md5sum.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha384sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o sha384sum-md5sum.o `test -f 'md5sum.c' || echo '$(srcdir)/'`md5sum.c + +sha384sum-md5sum.obj: md5sum.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha384sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT sha384sum-md5sum.obj -MD -MP -MF $(DEPDIR)/sha384sum-md5sum.Tpo -c -o sha384sum-md5sum.obj `if test -f 'md5sum.c'; then $(CYGPATH_W) 'md5sum.c'; else $(CYGPATH_W) '$(srcdir)/md5sum.c'; fi` +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/sha384sum-md5sum.Tpo $(DEPDIR)/sha384sum-md5sum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5sum.c' object='sha384sum-md5sum.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha384sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o sha384sum-md5sum.obj `if test -f 'md5sum.c'; then $(CYGPATH_W) 'md5sum.c'; else $(CYGPATH_W) '$(srcdir)/md5sum.c'; fi` + +sha512sum-md5sum.o: md5sum.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha512sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT sha512sum-md5sum.o -MD -MP -MF $(DEPDIR)/sha512sum-md5sum.Tpo -c -o sha512sum-md5sum.o `test -f 'md5sum.c' || echo '$(srcdir)/'`md5sum.c +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/sha512sum-md5sum.Tpo $(DEPDIR)/sha512sum-md5sum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5sum.c' object='sha512sum-md5sum.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha512sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o sha512sum-md5sum.o `test -f 'md5sum.c' || echo '$(srcdir)/'`md5sum.c + +sha512sum-md5sum.obj: md5sum.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha512sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT sha512sum-md5sum.obj -MD -MP -MF $(DEPDIR)/sha512sum-md5sum.Tpo -c -o sha512sum-md5sum.obj `if test -f 'md5sum.c'; then $(CYGPATH_W) 'md5sum.c'; else $(CYGPATH_W) '$(srcdir)/md5sum.c'; fi` +@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/sha512sum-md5sum.Tpo $(DEPDIR)/sha512sum-md5sum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='md5sum.c' object='sha512sum-md5sum.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sha512sum_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o sha512sum-md5sum.obj `if test -f 'md5sum.c'; then $(CYGPATH_W) 'md5sum.c'; else $(CYGPATH_W) '$(srcdir)/md5sum.c'; fi` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(PROGRAMS) $(SCRIPTS) $(HEADERS) all-local +installdirs: + for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) + -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES) +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-noinstPROGRAMS \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-exec-am: install-binPROGRAMS install-binSCRIPTS \ + install-exec-local + +install-html: install-html-am + +install-info: install-info-am + +install-man: + +install-pdf: install-pdf-am + +install-ps: install-ps-am + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-binSCRIPTS \ + uninstall-local + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am all-local check check-am clean \ + clean-binPROGRAMS clean-generic clean-noinstPROGRAMS ctags \ + distclean distclean-compile distclean-generic distclean-tags \ + distdir dvi dvi-am html html-am info info-am install \ + install-am install-binPROGRAMS install-binSCRIPTS install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-exec-local install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \ + tags uninstall uninstall-am uninstall-binPROGRAMS \ + uninstall-binSCRIPTS uninstall-local + + +$(PROGRAMS): ../lib/libcoreutils.a + +.sh: + rm -f $@ $@-t + sed \ + -e 's!@''bindir''@!$(bindir)!' \ + -e 's/@''RELEASE_YEAR'@/$(RELEASE_YEAR)/ \ + -e 's/@''GNU_PACKAGE''@/$(GNU_PACKAGE)/' \ + -e 's/@''PACKAGE_BUGREPORT''@/$(PACKAGE_BUGREPORT)/' \ + -e 's/@''VERSION''@/$(VERSION)/' $< > $@-t + chmod +x $@-t + mv $@-t $@ + +all-local: su$(EXEEXT) + +install-root: su$(EXEEXT) + @$(INSTALL_SU) + +install-exec-local: su$(EXEEXT) + @TMPFILE=$(DESTDIR)$(bindir)/.su-$$$$; \ + rm -f $$TMPFILE; \ + echo > $$TMPFILE; \ + can_create_suid_root_executable=no; \ + chown root $$TMPFILE > /dev/null 2>&1 \ + && chmod $(setuid_root_mode) $$TMPFILE > /dev/null 2>&1 \ + && can_create_suid_root_executable=yes; \ + rm -f $$TMPFILE; \ + if test $$can_create_suid_root_executable = yes; then \ + $(INSTALL_SU); \ + else \ + echo "WARNING: insufficient access; not installing su"; \ + echo "NOTE: to install su, run 'make install-root' as root"; \ + fi + +uninstall-local: +# Remove su only if it's one we installed. + @if grep '$(GNU_PACKAGE)' $(installed_su) > /dev/null 2>&1; then \ + echo " rm -f $(installed_su)"; \ + rm -f $(installed_su); \ + else :; fi +dircolors.h: dcgen dircolors.hin + @rm -f $@ $@-t + $(PERL) -w -- $(srcdir)/dcgen $(srcdir)/dircolors.hin > $@-t + @chmod a-w $@-t + mv $@-t $@ +wheel-size.h: Makefile.am + @rm -f $@ $@-t + echo '#define WHEEL_SIZE $(wheel_size)' > $@-t + @chmod a-w $@-t + mv $@-t $@ +wheel.h: wheel-gen.pl Makefile.am + @rm -f $@ $@-t + $(srcdir)/wheel-gen.pl $(wheel_size) > $@-t + @chmod a-w $@-t + mv $@-t $@ +fs.h: stat.c extract-magic + rm -f $@ + $(PERL) $(srcdir)/extract-magic $(srcdir)/stat.c > $@t + @chmod a-w $@t + mv $@t $@ + +all_programs.list: + @echo $(all_programs) | tr ' ' '\n' | sed -e 's,$(EXEEXT)$$,,' \ + | $(ASSORT) -u +# Ensure that the list of programs in README matches the list +# of programs we can build. +check: check-README check-misc +.PHONY: check-README +check-README: + rm -rf $(pr) $(pm) + echo $(all_programs) \ + | tr -s ' ' '\n' | sed -e 's,$(EXEEXT)$$,,' \ + | $(ASSORT) -u > $(pm) && \ + sed -n '/^The programs .* are:/,/^[a-zA-Z]/p' $(top_srcdir)/README \ + | sed -n '/^ */s///p' | tr -s ' ' '\n' > $(pr) + diff $(pm) $(pr) && rm -rf $(pr) $(pm) +.PHONY: check-AUTHORS +check-AUTHORS: $(all_programs) + rm -f $(au_actual) $(au_dotdot) + for i in `ls $(all_programs) | sed -e 's,$(EXEEXT)$$,,' \ + | $(ASSORT) -u`; do \ + test "$$i" = '[' && continue; \ + exe=$$i; \ + if test "$$i" = install; then \ + exe=ginstall; \ + elif test "$$i" = test; then \ + exe='['; \ + fi; \ + ./$$exe --version \ + |sed -n '/Written by /{ s//'"$$i"': /; s/,* and /, /; s/\.$$//; p; }'; \ + done > $(au_actual) + sed -n '/:/p' $(top_srcdir)/AUTHORS > $(au_dotdot) + diff $(au_actual) $(au_dotdot) && rm -f $(au_actual) $(au_dotdot) + +# Make sure we don't define any S_IS* macros in src/*.c files. +# Not a big deal, but they're already defined via system.h. +# +# Also make sure we don't use st_blocks. Use ST_NBLOCKS instead. +# This is a bit of a kludge, since it prevents use of the string +# even in comments, but for now it does the job with no false positives. +.PHONY: check-misc +check-misc: + cd $(srcdir); grep '^# *define *S_IS' $(SOURCES) && exit 1 || : + cd $(srcdir); grep st_blocks $(SOURCES) && exit 1 || : + cd $(srcdir); grep '^# *define .*defined' $(SOURCES) && exit 1 || : +# FIXME: handle *.sh; and use $(all_programs), not $(SOURCES) +../AUTHORS: $(SOURCES) + rm -f $@-t + ( \ + set -e; \ + echo "Here are the names of the programs in this package,"; \ + echo "each followed by the name(s) of its author(s)."; \ + echo; \ + for i in $(SOURCES); do \ + a=`sed -n $(s1) $$i`; \ + test "$$a" && : \ + || a=`sed -n $(s2) $$i`; \ + if test "$$a"; then \ + prog=`echo $$i|sed 's/\.c$$//'`; \ + echo "$$prog: $$a"; \ + fi; \ + done | $(ASSORT) -u ) > $@-t + chmod a-w $@-t + mv $@-t $@ + +# The following rule is not designed to be portable, +# and relies on tools that not everyone has. + +# Most functions in src/*.c should have static scope. +# Any that don't must be marked with `extern', but `main' +# and `usage' are exceptions. They're always extern, but +# don't need to be marked. +# +# The second nm|grep checks for file-scope variables with `extern' scope. +.PHONY: sc_tight_scope +sc_tight_scope: $(all_programs) + @t=exceptions-$$$$; \ + trap "s=$$?; rm -f $$t; exit $$s" 0 1 2 13 15; \ + ( printf '^main$$\n^usage$$\n'; \ + grep -h -A1 '^extern .*[^;]$$' $(SOURCES) \ + | grep -vE '^(extern |--)' |sed 's/^/^/;s/ .*/$$/' ) > $$t; \ + nm -e *.$(OBJEXT) \ + | sed -n 's/.* T //p' \ + | grep -Ev -f $$t && \ + { echo 'the above functions should have static scope' 1>&2; \ + exit 1; } || : ; \ + ( printf '^program_name$$\n'; \ + sed -n 's/^extern int \([^ ][^ ]*\);$$/^\1$$/p' \ + $(noinst_HEADERS) ) > $$t; \ + nm -e *.$(OBJEXT) \ + | sed -n 's/.* [BD] //p' \ + | grep -Ev -f $$t && \ + { echo 'the above variables should have static scope' 1>&2; \ + exit 1; } || : +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 0000000..9f8ff41 --- /dev/null +++ b/src/base64.c @@ -0,0 +1,322 @@ +/* Base64 encode/decode strings or files. + Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc. + + This file is part of Base64. + + Base64 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. + + Base64 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 Base64; see the file COPYING. If not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. */ + +/* Written by Simon Josefsson . */ + +#include + +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "xstrtol.h" +#include "quote.h" +#include "quotearg.h" + +#include "base64.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "base64" + +#define AUTHOR "Simon Josefsson" + +/* The invocation name of this program. */ +char *program_name; + +static const struct option long_options[] = { + {"decode", no_argument, 0, 'd'}, + {"wrap", required_argument, 0, 'w'}, + {"ignore-garbage", no_argument, 0, 'i'}, + {"help", no_argument, 0, GETOPT_HELP_CHAR}, + {"version", no_argument, 0, GETOPT_VERSION_CHAR}, + + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [FILE]\n\ +Base64 encode or decode FILE, or standard input, to standard output.\n\ +\n"), program_name); + fputs (_("\ + -w, --wrap=COLS Wrap encoded lines after COLS character (default 76).\n\ + Use 0 to disable line wrapping.\n\ +\n\ + -d, --decode Decode data.\n\ + -i, --ignore-garbage When decoding, ignore non-alphabet characters.\n\ +\n\ +"), stdout); + fputs (_("\ + --help Display this help and exit.\n\ + --version Output version information and exit.\n"), stdout); + fputs (_("\ +\n\ +With no FILE, or when FILE is -, read standard input.\n"), stdout); + fputs (_("\ +\n\ +The data are encoded as described for the base64 alphabet in RFC 3548.\n\ +When decoding, the input may contain newlines in addition to the bytes of\n\ +the formal base64 alphabet. Use --ignore-garbage to attempt to recover\n\ +from any other non-alphabet bytes in the encoded stream.\n"), + stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + + exit (status); +} + +/* Note that increasing this may decrease performance if --ignore-garbage + is used, because of the memmove operation below. */ +#define BLOCKSIZE 3072 +#define B64BLOCKSIZE BASE64_LENGTH (BLOCKSIZE) + +/* Ensure that BLOCKSIZE is a multiple of 3 and 4. */ +#if BLOCKSIZE % 12 != 0 +# error "invalid BLOCKSIZE" +#endif + +static void +wrap_write (const char *buffer, size_t len, + uintmax_t wrap_column, size_t *current_column, FILE *out) +{ + size_t written; + + if (wrap_column == 0) + { + /* Simple write. */ + if (fwrite (buffer, 1, len, stdout) < len) + error (EXIT_FAILURE, errno, _("write error")); + } + else + for (written = 0; written < len;) + { + uintmax_t cols_remaining = wrap_column - *current_column; + size_t to_write = MIN (cols_remaining, SIZE_MAX); + to_write = MIN (to_write, len - written); + + if (to_write == 0) + { + if (fputs ("\n", out) < 0) + error (EXIT_FAILURE, errno, _("write error")); + *current_column = 0; + } + else + { + if (fwrite (buffer + written, 1, to_write, stdout) < to_write) + error (EXIT_FAILURE, errno, _("write error")); + *current_column += to_write; + written += to_write; + } + } +} + +static void +do_encode (FILE *in, FILE *out, uintmax_t wrap_column) +{ + size_t current_column = 0; + char inbuf[BLOCKSIZE]; + char outbuf[B64BLOCKSIZE]; + size_t sum; + + do + { + size_t n; + + sum = 0; + do + { + n = fread (inbuf + sum, 1, BLOCKSIZE - sum, in); + sum += n; + } + while (!feof (in) && !ferror (in) && sum < BLOCKSIZE); + + if (sum > 0) + { + /* Process input one block at a time. Note that BLOCKSIZE % + 3 == 0, so that no base64 pads will appear in output. */ + base64_encode (inbuf, sum, outbuf, BASE64_LENGTH (sum)); + + wrap_write (outbuf, BASE64_LENGTH (sum), wrap_column, + ¤t_column, out); + } + } + while (!feof (in) && !ferror (in) && sum == BLOCKSIZE); + + /* When wrapping, terminate last line. */ + if (wrap_column && current_column > 0 && fputs ("\n", out) < 0) + error (EXIT_FAILURE, errno, _("write error")); + + if (ferror (in)) + error (EXIT_FAILURE, errno, _("read error")); +} + +static void +do_decode (FILE *in, FILE *out, bool ignore_garbage) +{ + char inbuf[B64BLOCKSIZE]; + char outbuf[BLOCKSIZE]; + size_t sum; + struct base64_decode_context ctx; + + base64_decode_ctx_init (&ctx); + + do + { + bool ok; + size_t n; + unsigned int k; + + sum = 0; + do + { + n = fread (inbuf + sum, 1, B64BLOCKSIZE - sum, in); + + if (ignore_garbage) + { + size_t i; + for (i = 0; n > 0 && i < n;) + if (isbase64 (inbuf[sum + i]) || inbuf[sum + i] == '=') + i++; + else + memmove (inbuf + sum + i, inbuf + sum + i + 1, --n - i); + } + + sum += n; + + if (ferror (in)) + error (EXIT_FAILURE, errno, _("read error")); + } + while (sum < B64BLOCKSIZE && !feof (in)); + + /* The following "loop" is usually iterated just once. + However, when it processes the final input buffer, we want + to iterate it one additional time, but with an indicator + telling it to flush what is in CTX. */ + for (k = 0; k < 1 + feof (in); k++) + { + if (k == 1 && ctx.i == 0) + break; + n = BLOCKSIZE; + ok = base64_decode (&ctx, inbuf, (k == 0 ? sum : 0), outbuf, &n); + + if (fwrite (outbuf, 1, n, out) < n) + error (EXIT_FAILURE, errno, _("write error")); + + if (!ok) + error (EXIT_FAILURE, 0, _("invalid input")); + } + } + while (!feof (in)); +} + +int +main (int argc, char **argv) +{ + int opt; + FILE *input_fh; + const char *infile; + + /* True if --decode has bene given and we should decode data. */ + bool decode = false; + /* True if we should ignore non-alphabetic characters. */ + bool ignore_garbage = false; + /* Wrap encoded base64 data around the 76:th column, by default. */ + uintmax_t wrap_column = 76; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((opt = getopt_long (argc, argv, "dqiw:", long_options, NULL)) != -1) + switch (opt) + { + case 'd': + decode = true; + break; + + case 'w': + if (xstrtoumax (optarg, NULL, 0, &wrap_column, NULL) != LONGINT_OK) + error (EXIT_FAILURE, 0, _("invalid wrap size: %s"), + quotearg (optarg)); + break; + + case 'i': + ignore_garbage = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHOR); + + default: + usage (EXIT_FAILURE); + break; + } + + if (argc - optind > 1) + { + error (0, 0, _("extra operand %s"), quote (argv[optind])); + usage (EXIT_FAILURE); + } + + if (optind < argc) + infile = argv[optind]; + else + infile = "-"; + + if (strcmp (infile, "-") == 0) + input_fh = stdin; + else + { + input_fh = fopen (infile, "r"); + if (input_fh == NULL) + error (EXIT_FAILURE, errno, "%s", infile); + } + + if (decode) + do_decode (input_fh, stdout, ignore_garbage); + else + do_encode (input_fh, stdout, wrap_column); + + if (fclose (input_fh) == EOF) + { + if (strcmp (infile, "-") == 0) + error (EXIT_FAILURE, errno, _("closing standard input")); + else + error (EXIT_FAILURE, errno, "%s", infile); + } + + exit (EXIT_SUCCESS); +} diff --git a/src/basename.c b/src/basename.c new file mode 100644 index 0000000..f2617b1 --- /dev/null +++ b/src/basename.c @@ -0,0 +1,144 @@ +/* basename -- strip directory and suffix from file names + Copyright (C) 1990-1997, 1999-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Usage: basename name [suffix] + NAME is a file name; SUFFIX is a suffix to strip from it. + + basename /usr/foo/lossage/functions.l + => functions.l + basename /usr/foo/lossage/functions.l .l + => functions + basename functions.lisp p + => functions.lis */ + +#include +#include +#include +#include + +#include "system.h" +#include "long-options.h" +#include "error.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "basename" + +#define AUTHORS "FIXME unknown" + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s NAME [SUFFIX]\n\ + or: %s OPTION\n\ +"), + program_name, program_name); + fputs (_("\ +Print NAME with any leading directory components removed.\n\ +If specified, also remove a trailing SUFFIX.\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\ +\n\ +Examples:\n\ + %s /usr/bin/sort Output \"sort\".\n\ + %s include/stdio.h .h Output \"stdio\".\n\ +"), + program_name, program_name); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Remove SUFFIX from the end of NAME if it is there, unless NAME + consists entirely of SUFFIX. */ + +static void +remove_suffix (char *name, const char *suffix) +{ + char *np; + const char *sp; + + np = name + strlen (name); + sp = suffix + strlen (suffix); + + while (np > name && sp > suffix) + if (*--np != *--sp) + return; + if (np > name) + *np = '\0'; +} + +int +main (int argc, char **argv) +{ + char *name; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "+", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (argc < optind + 1) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + if (optind + 2 < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 2])); + usage (EXIT_FAILURE); + } + + name = base_name (argv[optind]); + strip_trailing_slashes (name); + + /* Per POSIX, `basename // /' must return `//' on platforms with + distinct //. On platforms with drive letters, this generalizes + to making `basename c: :' return `c:'. This rule is captured by + skipping suffix stripping if base_name returned an absolute path + or a drive letter (only possible if name is a file-system + root). */ + if (argc == optind + 2 && IS_RELATIVE_FILE_NAME (name) + && ! FILE_SYSTEM_PREFIX_LEN (name)) + remove_suffix (name, argv[optind + 1]); + + puts (name); + free (name); + + exit (EXIT_SUCCESS); +} diff --git a/src/c99-to-c89.diff b/src/c99-to-c89.diff new file mode 100644 index 0000000..5545f6a --- /dev/null +++ b/src/c99-to-c89.diff @@ -0,0 +1,118 @@ +Index: src/remove.c +=================================================================== +RCS file: /fetish/cu/src/remove.c,v +retrieving revision 1.158 +diff --git a/src/remove.c b/src/remove.c +index 4728bdd..7477da5 100644 +--- a/src/remove.c ++++ b/src/remove.c +@@ -236,9 +236,10 @@ pop_dir (Dirstack_state *ds) + { + size_t n_lengths = obstack_object_size (&ds->len_stack) / sizeof (size_t); + size_t *length = obstack_base (&ds->len_stack); ++ size_t top_len; + + assert (n_lengths > 0); +- size_t top_len = length[n_lengths - 1]; ++ top_len = length[n_lengths - 1]; + assert (top_len >= 2); + + /* Pop the specified length of file name. */ +@@ -370,10 +371,11 @@ AD_stack_top (Dirstack_state const *ds) + static void + AD_stack_pop (Dirstack_state *ds) + { ++ struct AD_ent *top; + assert (0 < AD_stack_height (ds)); + + /* operate on Active_dir. pop and free top entry */ +- struct AD_ent *top = AD_stack_top (ds); ++ top = AD_stack_top (ds); + if (top->unremovable) + hash_free (top->unremovable); + obstack_blank (&ds->Active_dir, -(int) sizeof (struct AD_ent)); +@@ -815,6 +817,7 @@ prompt (int fd_cwd, Dirstack_state const *ds, char const *filename, + + if (write_protected || x->interactive == RMI_ALWAYS) + { ++ char const *quoted_name = quote (full_filename (filename)); + if (write_protected <= 0 + && cache_fstatat (fd_cwd, filename, sbuf, AT_SYMLINK_NOFOLLOW) != 0) + { +@@ -832,8 +835,6 @@ prompt (int fd_cwd, Dirstack_state const *ds, char const *filename, + write_protected = EISDIR; + } + +- char const *quoted_name = quote (full_filename (filename)); +- + if (0 < write_protected) + { + error (0, write_protected, _("cannot remove %s"), quoted_name); +@@ -1487,6 +1488,7 @@ rm_1 (Dirstack_state *ds, char const *filename, + return RM_ERROR; + } + ++ { + struct stat st; + cache_stat_init (&st); + cycle_check_init (&ds->cycle_check_state); +@@ -1509,6 +1511,7 @@ rm_1 (Dirstack_state *ds, char const *filename, + AD_push_initial (ds); + AD_INIT_OTHER_MEMBERS (); + ++ { + enum RM_status status = remove_entry (AT_FDCWD, ds, filename, &st, x, NULL); + if (status == RM_NONEMPTY_DIR) + { +@@ -1525,6 +1528,8 @@ rm_1 (Dirstack_state *ds, char const *filename, + + ds_clear (ds); + return status; ++ } ++ } + } + + /* Remove all files and/or directories specified by N_FILES and FILE. +Index: src/rm.c +=================================================================== +RCS file: /fetish/cu/src/rm.c,v +retrieving revision 1.140 +diff --git a/src/rm.c b/src/rm.c +index 364a21c..7a24014 100644 +--- a/src/rm.c ++++ b/src/rm.c +@@ -355,6 +355,7 @@ main (int argc, char **argv) + quote ("/")); + } + ++ { + size_t n_files = argc - optind; + char const *const *file = (char const *const *) argv + optind; + +@@ -368,7 +369,10 @@ main (int argc, char **argv) + if (!yesno ()) + exit (EXIT_SUCCESS); + } ++ { + enum RM_status status = rm (n_files, file, &x); + assert (VALID_STATUS (status)); + exit (status == RM_ERROR ? EXIT_FAILURE : EXIT_SUCCESS); ++ } ++ } + } +Index: src/shred.c +=================================================================== +RCS file: /fetish/cu/src/shred.c,v +retrieving revision 1.130 +diff -u -p -r1.130 shred.c +--- a/src/shred.c 3 Sep 2006 02:53:16 -0000 1.130 ++++ b/src/shred.c 3 Oct 2006 13:48:24 -0000 +@@ -464,7 +464,7 @@ dopass (int fd, char const *qname, off_t + out. Thus, it shouldn't give up on bad blocks. This + code works because lim is always a multiple of + SECTOR_SIZE, except at the end. */ +- verify (sizeof r % SECTOR_SIZE == 0); ++ { verify (sizeof r % SECTOR_SIZE == 0); } + if (errnum == EIO && 0 <= size && (soff | SECTOR_MASK) < lim) + { + size_t soff1 = (soff | SECTOR_MASK) + 1; diff --git a/src/cat.c b/src/cat.c new file mode 100644 index 0000000..90a73c2 --- /dev/null +++ b/src/cat.c @@ -0,0 +1,788 @@ +/* cat -- concatenate files and print on the standard output. + Copyright (C) 88, 90, 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Differences from the Unix cat: + * Always unbuffered, -u is ignored. + * Usually much faster than other versions of cat, the difference + is especially apparent when using the -v option. + + By tege@sics.se, Torbjorn Granlund, advised by rms, Richard Stallman. */ + +#include + +#include +#include +#include + +#if HAVE_STROPTS_H +# include +#endif +#if HAVE_SYS_IOCTL_H +# include +#endif + +#include "system.h" +#include "error.h" +#include "full-write.h" +#include "getpagesize.h" +#include "quote.h" +#include "safe-read.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "cat" + +#define AUTHORS "Torbjorn Granlund", "Richard M. Stallman" + +/* Undefine, to avoid warning about redefinition on some systems. */ +#undef max +#define max(h,i) ((h) > (i) ? (h) : (i)) + +/* Name under which this program was invoked. */ +char *program_name; + +/* Name of input file. May be "-". */ +static char const *infile; + +/* Descriptor on which input file is open. */ +static int input_desc; + +/* Buffer for line numbers. + An 11 digit counter may overflow within an hour on a P2/466, + an 18 digit counter needs about 1000y */ +#define LINE_COUNTER_BUF_LEN 20 +static char line_buf[LINE_COUNTER_BUF_LEN] = + { + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '0', + '\t', '\0' + }; + +/* Position in `line_buf' where printing starts. This will not change + unless the number of lines is larger than 999999. */ +static char *line_num_print = line_buf + LINE_COUNTER_BUF_LEN - 8; + +/* Position of the first digit in `line_buf'. */ +static char *line_num_start = line_buf + LINE_COUNTER_BUF_LEN - 3; + +/* Position of the last digit in `line_buf'. */ +static char *line_num_end = line_buf + LINE_COUNTER_BUF_LEN - 3; + +/* Preserves the `cat' function's local `newlines' between invocations. */ +static int newlines2 = 0; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [FILE]...\n\ +"), + program_name); + fputs (_("\ +Concatenate FILE(s), or standard input, to standard output.\n\ +\n\ + -A, --show-all equivalent to -vET\n\ + -b, --number-nonblank number nonblank output lines\n\ + -e equivalent to -vE\n\ + -E, --show-ends display $ at end of each line\n\ + -n, --number number all output lines\n\ + -s, --squeeze-blank never more than one single blank line\n\ +"), stdout); + fputs (_("\ + -t equivalent to -vT\n\ + -T, --show-tabs display TAB characters as ^I\n\ + -u (ignored)\n\ + -v, --show-nonprinting use ^ and M- notation, except for LFD and TAB\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +With no FILE, or when FILE is -, read standard input.\n\ +"), stdout); + printf (_("\ +\n\ +Examples:\n\ + %s f - g Output f's contents, then standard input, then g's contents.\n\ + %s Copy standard input to standard output.\n\ +"), + program_name, program_name); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Compute the next line number. */ + +static void +next_line_num (void) +{ + char *endp = line_num_end; + do + { + if ((*endp)++ < '9') + return; + *endp-- = '0'; + } + while (endp >= line_num_start); + if (line_num_start > line_buf) + *--line_num_start = '1'; + else + *line_buf = '>'; + if (line_num_start < line_num_print) + line_num_print--; +} + +/* Plain cat. Copies the file behind `input_desc' to STDOUT_FILENO. + Return true if successful. */ + +static bool +simple_cat ( + /* Pointer to the buffer, used by reads and writes. */ + char *buf, + + /* Number of characters preferably read or written by each read and write + call. */ + size_t bufsize) +{ + /* Actual number of characters read, and therefore written. */ + size_t n_read; + + /* Loop until the end of the file. */ + + for (;;) + { + /* Read a block of input. */ + + n_read = safe_read (input_desc, buf, bufsize); + if (n_read == SAFE_READ_ERROR) + { + error (0, errno, "%s", infile); + return false; + } + + /* End of this file? */ + + if (n_read == 0) + return true; + + /* Write this block out. */ + + { + /* The following is ok, since we know that 0 < n_read. */ + size_t n = n_read; + if (full_write (STDOUT_FILENO, buf, n) != n) + error (EXIT_FAILURE, errno, _("write error")); + } + } +} + +/* Write any pending output to STDOUT_FILENO. + Pending is defined to be the *BPOUT - OUTBUF bytes starting at OUTBUF. + Then set *BPOUT to OUTPUT if it's not already that value. */ + +static inline void +write_pending (char *outbuf, char **bpout) +{ + size_t n_write = *bpout - outbuf; + if (0 < n_write) + { + if (full_write (STDOUT_FILENO, outbuf, n_write) != n_write) + error (EXIT_FAILURE, errno, _("write error")); + *bpout = outbuf; + } +} + +/* Cat the file behind INPUT_DESC to the file behind OUTPUT_DESC. + Return true if successful. + Called if any option more than -u was specified. + + A newline character is always put at the end of the buffer, to make + an explicit test for buffer end unnecessary. */ + +static bool +cat ( + /* Pointer to the beginning of the input buffer. */ + char *inbuf, + + /* Number of characters read in each read call. */ + size_t insize, + + /* Pointer to the beginning of the output buffer. */ + char *outbuf, + + /* Number of characters written by each write call. */ + size_t outsize, + + /* Variables that have values according to the specified options. */ + bool show_nonprinting, + bool show_tabs, + bool number, + bool number_nonblank, + bool show_ends, + bool squeeze_blank) +{ + /* Last character read from the input buffer. */ + unsigned char ch; + + /* Pointer to the next character in the input buffer. */ + char *bpin; + + /* Pointer to the first non-valid byte in the input buffer, i.e. the + current end of the buffer. */ + char *eob; + + /* Pointer to the position where the next character shall be written. */ + char *bpout; + + /* Number of characters read by the last read call. */ + size_t n_read; + + /* Determines how many consecutive newlines there have been in the + input. 0 newlines makes NEWLINES -1, 1 newline makes NEWLINES 1, + etc. Initially 0 to indicate that we are at the beginning of a + new line. The "state" of the procedure is determined by + NEWLINES. */ + int newlines = newlines2; + +#ifdef FIONREAD + /* If nonzero, use the FIONREAD ioctl, as an optimization. + (On Ultrix, it is not supported on NFS file systems.) */ + bool use_fionread = true; +#endif + + /* The inbuf pointers are initialized so that BPIN > EOB, and thereby input + is read immediately. */ + + eob = inbuf; + bpin = eob + 1; + + bpout = outbuf; + + for (;;) + { + do + { + /* Write if there are at least OUTSIZE bytes in OUTBUF. */ + + if (outbuf + outsize <= bpout) + { + char *wp = outbuf; + size_t remaining_bytes; + do + { + if (full_write (STDOUT_FILENO, wp, outsize) != outsize) + error (EXIT_FAILURE, errno, _("write error")); + wp += outsize; + remaining_bytes = bpout - wp; + } + while (outsize <= remaining_bytes); + + /* Move the remaining bytes to the beginning of the + buffer. */ + + memmove (outbuf, wp, remaining_bytes); + bpout = outbuf + remaining_bytes; + } + + /* Is INBUF empty? */ + + if (bpin > eob) + { + bool input_pending = false; +#ifdef FIONREAD + int n_to_read = 0; + + /* Is there any input to read immediately? + If not, we are about to wait, + so write all buffered output before waiting. */ + + if (use_fionread + && ioctl (input_desc, FIONREAD, &n_to_read) < 0) + { + /* Ultrix returns EOPNOTSUPP on NFS; + HP-UX returns ENOTTY on pipes. + SunOS returns EINVAL and + More/BSD returns ENODEV on special files + like /dev/null. + Irix-5 returns ENOSYS on pipes. */ + if (errno == EOPNOTSUPP || errno == ENOTTY + || errno == EINVAL || errno == ENODEV + || errno == ENOSYS) + use_fionread = false; + else + { + error (0, errno, _("cannot do ioctl on %s"), quote (infile)); + newlines2 = newlines; + return false; + } + } + if (n_to_read != 0) + input_pending = true; +#endif + + if (input_pending) + write_pending (outbuf, &bpout); + + /* Read more input into INBUF. */ + + n_read = safe_read (input_desc, inbuf, insize); + if (n_read == SAFE_READ_ERROR) + { + error (0, errno, "%s", infile); + write_pending (outbuf, &bpout); + newlines2 = newlines; + return false; + } + if (n_read == 0) + { + write_pending (outbuf, &bpout); + newlines2 = newlines; + return true; + } + + /* Update the pointers and insert a sentinel at the buffer + end. */ + + bpin = inbuf; + eob = bpin + n_read; + *eob = '\n'; + } + else + { + /* It was a real (not a sentinel) newline. */ + + /* Was the last line empty? + (i.e. have two or more consecutive newlines been read?) */ + + if (++newlines > 0) + { + if (newlines >= 2) + { + /* Limit this to 2 here. Otherwise, with lots of + consecutive newlines, the counter could wrap + around at INT_MAX. */ + newlines = 2; + + /* Are multiple adjacent empty lines to be substituted + by single ditto (-s), and this was the second empty + line? */ + if (squeeze_blank) + { + ch = *bpin++; + continue; + } + } + + /* Are line numbers to be written at empty lines (-n)? */ + + if (number & !number_nonblank) + { + next_line_num (); + bpout = stpcpy (bpout, line_num_print); + } + } + + /* Output a currency symbol if requested (-e). */ + + if (show_ends) + *bpout++ = '$'; + + /* Output the newline. */ + + *bpout++ = '\n'; + } + ch = *bpin++; + } + while (ch == '\n'); + + /* Are we at the beginning of a line, and line numbers are requested? */ + + if (newlines >= 0 && number) + { + next_line_num (); + bpout = stpcpy (bpout, line_num_print); + } + + /* Here CH cannot contain a newline character. */ + + /* The loops below continue until a newline character is found, + which means that the buffer is empty or that a proper newline + has been found. */ + + /* If quoting, i.e. at least one of -v, -e, or -t specified, + scan for chars that need conversion. */ + if (show_nonprinting) + { + for (;;) + { + if (ch >= 32) + { + if (ch < 127) + *bpout++ = ch; + else if (ch == 127) + { + *bpout++ = '^'; + *bpout++ = '?'; + } + else + { + *bpout++ = 'M'; + *bpout++ = '-'; + if (ch >= 128 + 32) + { + if (ch < 128 + 127) + *bpout++ = ch - 128; + else + { + *bpout++ = '^'; + *bpout++ = '?'; + } + } + else + { + *bpout++ = '^'; + *bpout++ = ch - 128 + 64; + } + } + } + else if (ch == '\t' && !show_tabs) + *bpout++ = '\t'; + else if (ch == '\n') + { + newlines = -1; + break; + } + else + { + *bpout++ = '^'; + *bpout++ = ch + 64; + } + + ch = *bpin++; + } + } + else + { + /* Not quoting, neither of -v, -e, or -t specified. */ + for (;;) + { + if (ch == '\t' && show_tabs) + { + *bpout++ = '^'; + *bpout++ = ch + 64; + } + else if (ch != '\n') + *bpout++ = ch; + else + { + newlines = -1; + break; + } + + ch = *bpin++; + } + } + } +} + +int +main (int argc, char **argv) +{ + /* Optimal size of i/o operations of output. */ + size_t outsize; + + /* Optimal size of i/o operations of input. */ + size_t insize; + + size_t page_size = getpagesize (); + + /* Pointer to the input buffer. */ + char *inbuf; + + /* Pointer to the output buffer. */ + char *outbuf; + + bool ok = true; + int c; + + /* Index in argv to processed argument. */ + int argind; + + /* Device number of the output (file or whatever). */ + dev_t out_dev; + + /* I-node number of the output. */ + ino_t out_ino; + + /* True if the output file should not be the same as any input file. */ + bool check_redirection = true; + + /* Nonzero if we have ever read standard input. */ + bool have_read_stdin = false; + + struct stat stat_buf; + + /* Variables that are set according to the specified options. */ + bool number = false; + bool number_nonblank = false; + bool squeeze_blank = false; + bool show_ends = false; + bool show_nonprinting = false; + bool show_tabs = false; + int file_open_mode = O_RDONLY; + + static struct option const long_options[] = + { + {"number-nonblank", no_argument, NULL, 'b'}, + {"number", no_argument, NULL, 'n'}, + {"squeeze-blank", no_argument, NULL, 's'}, + {"show-nonprinting", no_argument, NULL, 'v'}, + {"show-ends", no_argument, NULL, 'E'}, + {"show-tabs", no_argument, NULL, 'T'}, + {"show-all", no_argument, NULL, 'A'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} + }; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + /* Arrange to close stdout if we exit via the + case_GETOPT_HELP_CHAR or case_GETOPT_VERSION_CHAR code. + Normally STDOUT_FILENO is used rather than stdout, so + close_stdout does nothing. */ + atexit (close_stdout); + + /* Parse command line options. */ + + while ((c = getopt_long (argc, argv, "benstuvAET", long_options, NULL)) + != -1) + { + switch (c) + { + case 'b': + number = true; + number_nonblank = true; + break; + + case 'e': + show_ends = true; + show_nonprinting = true; + break; + + case 'n': + number = true; + break; + + case 's': + squeeze_blank = true; + break; + + case 't': + show_tabs = true; + show_nonprinting = true; + break; + + case 'u': + /* We provide the -u feature unconditionally. */ + break; + + case 'v': + show_nonprinting = true; + break; + + case 'A': + show_nonprinting = true; + show_ends = true; + show_tabs = true; + break; + + case 'E': + show_ends = true; + break; + + case 'T': + show_tabs = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + /* Get device, i-node number, and optimal blocksize of output. */ + + if (fstat (STDOUT_FILENO, &stat_buf) < 0) + error (EXIT_FAILURE, errno, _("standard output")); + + outsize = ST_BLKSIZE (stat_buf); + /* Input file can be output file for non-regular files. + fstat on pipes returns S_IFSOCK on some systems, S_IFIFO + on others, so the checking should not be done for those types, + and to allow things like cat < /dev/tty > /dev/tty, checking + is not done for device files either. */ + + if (S_ISREG (stat_buf.st_mode)) + { + out_dev = stat_buf.st_dev; + out_ino = stat_buf.st_ino; + } + else + { + check_redirection = false; +#ifdef lint /* Suppress `used before initialized' warning. */ + out_dev = 0; + out_ino = 0; +#endif + } + + if (! (number | show_ends | squeeze_blank)) + { + file_open_mode |= O_BINARY; + if (O_BINARY && ! isatty (STDOUT_FILENO)) + freopen (NULL, "wb", stdout); + } + + /* Check if any of the input files are the same as the output file. */ + + /* Main loop. */ + + infile = "-"; + argind = optind; + + do + { + if (argind < argc) + infile = argv[argind]; + + if (STREQ (infile, "-")) + { + have_read_stdin = true; + input_desc = STDIN_FILENO; + if ((file_open_mode & O_BINARY) && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + } + else + { + input_desc = open (infile, file_open_mode); + if (input_desc < 0) + { + error (0, errno, "%s", infile); + ok = false; + continue; + } + } + + if (fstat (input_desc, &stat_buf) < 0) + { + error (0, errno, "%s", infile); + ok = false; + goto contin; + } + insize = ST_BLKSIZE (stat_buf); + + /* Compare the device and i-node numbers of this input file with + the corresponding values of the (output file associated with) + stdout, and skip this input file if they coincide. Input + files cannot be redirected to themselves. */ + + if (check_redirection + && stat_buf.st_dev == out_dev && stat_buf.st_ino == out_ino + && (input_desc != STDIN_FILENO)) + { + error (0, 0, _("%s: input file is output file"), infile); + ok = false; + goto contin; + } + + /* Select which version of `cat' to use. If any format-oriented + options were given use `cat'; otherwise use `simple_cat'. */ + + if (! (number | show_ends | show_nonprinting + | show_tabs | squeeze_blank)) + { + insize = max (insize, outsize); + inbuf = xmalloc (insize + page_size - 1); + + ok &= simple_cat (ptr_align (inbuf, page_size), insize); + } + else + { + inbuf = xmalloc (insize + 1 + page_size - 1); + + /* Why are + (OUTSIZE - 1 + INSIZE * 4 + LINE_COUNTER_BUF_LEN + PAGE_SIZE - 1) + bytes allocated for the output buffer? + + A test whether output needs to be written is done when the input + buffer empties or when a newline appears in the input. After + output is written, at most (OUTSIZE - 1) bytes will remain in the + buffer. Now INSIZE bytes of input is read. Each input character + may grow by a factor of 4 (by the prepending of M-^). If all + characters do, and no newlines appear in this block of input, we + will have at most (OUTSIZE - 1 + INSIZE * 4) bytes in the buffer. + If the last character in the preceding block of input was a + newline, a line number may be written (according to the given + options) as the first thing in the output buffer. (Done after the + new input is read, but before processing of the input begins.) + A line number requires seldom more than LINE_COUNTER_BUF_LEN + positions. + + Align the output buffer to a page size boundary, for efficency on + some paging implementations, so add PAGE_SIZE - 1 bytes to the + request to make room for the alignment. */ + + outbuf = xmalloc (outsize - 1 + insize * 4 + LINE_COUNTER_BUF_LEN + + page_size - 1); + + ok &= cat (ptr_align (inbuf, page_size), insize, + ptr_align (outbuf, page_size), outsize, show_nonprinting, + show_tabs, number, number_nonblank, show_ends, + squeeze_blank); + + free (outbuf); + } + + free (inbuf); + + contin: + if (!STREQ (infile, "-") && close (input_desc) < 0) + { + error (0, errno, "%s", infile); + ok = false; + } + } + while (++argind < argc); + + if (have_read_stdin && close (STDIN_FILENO) < 0) + error (EXIT_FAILURE, errno, _("closing standard input")); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/chgrp.c b/src/chgrp.c new file mode 100644 index 0000000..faf58d3 --- /dev/null +++ b/src/chgrp.c @@ -0,0 +1,314 @@ +/* chgrp -- change group ownership of files + Copyright (C) 89, 90, 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by David MacKenzie . */ + +#include +#include +#include +#include +#include + +#include "system.h" +#include "chown-core.h" +#include "error.h" +#include "fts_.h" +#include "group-member.h" +#include "quote.h" +#include "root-dev-ino.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "chgrp" + +#define AUTHORS "David MacKenzie", "Jim Meyering" + +#if ! HAVE_ENDGRENT +# define endgrent() ((void) 0) +#endif + +/* The name the program was run with. */ +char *program_name; + +/* The argument to the --reference option. Use the group ID of this file. + This file must exist. */ +static char *reference_file; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + DEREFERENCE_OPTION = CHAR_MAX + 1, + NO_PRESERVE_ROOT, + PRESERVE_ROOT, + REFERENCE_FILE_OPTION +}; + +static struct option const long_options[] = +{ + {"recursive", no_argument, NULL, 'R'}, + {"changes", no_argument, NULL, 'c'}, + {"dereference", no_argument, NULL, DEREFERENCE_OPTION}, + {"no-dereference", no_argument, NULL, 'h'}, + {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT}, + {"preserve-root", no_argument, NULL, PRESERVE_ROOT}, + {"quiet", no_argument, NULL, 'f'}, + {"silent", no_argument, NULL, 'f'}, + {"reference", required_argument, NULL, REFERENCE_FILE_OPTION}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Return the group ID of NAME, or -1 if no name was specified. */ + +static gid_t +parse_group (const char *name) +{ + gid_t gid = -1; + + if (*name) + { + struct group *grp = getgrnam (name); + if (grp) + gid = grp->gr_gid; + else + { + unsigned long int tmp; + if (! (xstrtoul (name, NULL, 10, &tmp, "") == LONGINT_OK + && tmp <= GID_T_MAX)) + error (EXIT_FAILURE, 0, _("invalid group %s"), quote (name)); + gid = tmp; + } + endgrent (); /* Save a file descriptor. */ + } + + return gid; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... GROUP FILE...\n\ + or: %s [OPTION]... --reference=RFILE FILE...\n\ +"), + program_name, program_name); + fputs (_("\ +Change the group of each FILE to GROUP.\n\ +With --reference, change the group of each FILE to that of RFILE.\n\ +\n\ + -c, --changes like verbose but report only when a change is made\n\ + --dereference affect the referent of each symbolic link (this is\n\ + the default), rather than the symbolic link itself\n\ +"), stdout); + fputs (_("\ + -h, --no-dereference affect each symbolic link instead of any referenced\n\ + file (useful only on systems that can change the\n\ + ownership of a symlink)\n\ +"), stdout); + fputs (_("\ + --no-preserve-root do not treat `/' specially (the default)\n\ + --preserve-root fail to operate recursively on `/'\n\ +"), stdout); + fputs (_("\ + -f, --silent, --quiet suppress most error messages\n\ + --reference=RFILE use RFILE's group rather than specifying a\n\ + GROUP value\n\ + -R, --recursive operate on files and directories recursively\n\ + -v, --verbose output a diagnostic for every file processed\n\ +\n\ +"), stdout); + fputs (_("\ +The following options modify how a hierarchy is traversed when the -R\n\ +option is also specified. If more than one is specified, only the final\n\ +one takes effect.\n\ +\n\ + -H if a command line argument is a symbolic link\n\ + to a directory, traverse it\n\ + -L traverse every symbolic link to a directory\n\ + encountered\n\ + -P do not traverse any symbolic links (default)\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\ +\n\ +Examples:\n\ + %s staff /u Change the group of /u to \"staff\".\n\ + %s -hR staff /u Change the group of /u and subfiles to \"staff\".\n\ +"), + program_name, program_name); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + bool preserve_root = false; + gid_t gid; + + /* Bit flags that control how fts works. */ + int bit_flags = FTS_PHYSICAL; + + /* 1 if --dereference, 0 if --no-dereference, -1 if neither has been + specified. */ + int dereference = -1; + + struct Chown_option chopt; + bool ok; + int optc; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + chopt_init (&chopt); + + while ((optc = getopt_long (argc, argv, "HLPRcfhv", long_options, NULL)) + != -1) + { + switch (optc) + { + case 'H': /* Traverse command-line symlinks-to-directories. */ + bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL; + break; + + case 'L': /* Traverse all symlinks-to-directories. */ + bit_flags = FTS_LOGICAL; + break; + + case 'P': /* Traverse no symlinks-to-directories. */ + bit_flags = FTS_PHYSICAL; + break; + + case 'h': /* --no-dereference: affect symlinks */ + dereference = 0; + break; + + case DEREFERENCE_OPTION: /* --dereference: affect the referent + of each symlink */ + dereference = 1; + break; + + case NO_PRESERVE_ROOT: + preserve_root = false; + break; + + case PRESERVE_ROOT: + preserve_root = true; + break; + + case REFERENCE_FILE_OPTION: + reference_file = optarg; + break; + + case 'R': + chopt.recurse = true; + break; + + case 'c': + chopt.verbosity = V_changes_only; + break; + + case 'f': + chopt.force_silent = true; + break; + + case 'v': + chopt.verbosity = V_high; + break; + + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + if (chopt.recurse) + { + if (bit_flags == FTS_PHYSICAL) + { + if (dereference == 1) + error (EXIT_FAILURE, 0, + _("-R --dereference requires either -H or -L")); + dereference = 0; + } + } + else + { + bit_flags = FTS_PHYSICAL; + } + chopt.affect_symlink_referent = (dereference != 0); + + if (argc - optind < (reference_file ? 1 : 2)) + { + if (argc <= optind) + error (0, 0, _("missing operand")); + else + error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); + usage (EXIT_FAILURE); + } + + if (reference_file) + { + struct stat ref_stats; + if (stat (reference_file, &ref_stats)) + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quote (reference_file)); + + gid = ref_stats.st_gid; + chopt.group_name = gid_to_name (ref_stats.st_gid); + } + else + { + char *group_name = argv[optind++]; + chopt.group_name = (*group_name ? group_name : NULL); + gid = parse_group (group_name); + } + + if (chopt.recurse & preserve_root) + { + static struct dev_ino dev_ino_buf; + chopt.root_dev_ino = get_root_dev_ino (&dev_ino_buf); + if (chopt.root_dev_ino == NULL) + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quote ("/")); + } + + ok = chown_files (argv + optind, bit_flags, + (uid_t) -1, gid, + (uid_t) -1, (gid_t) -1, &chopt); + + chopt_free (&chopt); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/chmod.c b/src/chmod.c new file mode 100644 index 0000000..028c882 --- /dev/null +++ b/src/chmod.c @@ -0,0 +1,532 @@ +/* chmod -- change permission modes of files + Copyright (C) 89, 90, 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by David MacKenzie */ + +#include +#include +#include +#include + +#include "system.h" +#include "dev-ino.h" +#include "error.h" +#include "filemode.h" +#include "modechange.h" +#include "openat.h" +#include "quote.h" +#include "quotearg.h" +#include "root-dev-ino.h" +#include "xfts.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "chmod" + +#define AUTHORS "David MacKenzie", "Jim Meyering" + +enum Change_status +{ + CH_NOT_APPLIED, + CH_SUCCEEDED, + CH_FAILED, + CH_NO_CHANGE_REQUESTED +}; + +enum Verbosity +{ + /* Print a message for each file that is processed. */ + V_high, + + /* Print a message for each file whose attributes we change. */ + V_changes_only, + + /* Do not be verbose. This is the default. */ + V_off +}; + +/* The name the program was run with. */ +char *program_name; + +/* The desired change to the mode. */ +static struct mode_change *change; + +/* The initial umask value, if it might be needed. */ +static mode_t umask_value; + +/* If true, change the modes of directories recursively. */ +static bool recurse; + +/* If true, force silence (no error messages). */ +static bool force_silent; + +/* If true, diagnose surprises from naive misuses like "chmod -r file". + POSIX allows diagnostics here, as portable code is supposed to use + "chmod -- -r file". */ +static bool diagnose_surprises; + +/* Level of verbosity. */ +static enum Verbosity verbosity = V_off; + +/* Pointer to the device and inode numbers of `/', when --recursive. + Otherwise NULL. */ +static struct dev_ino *root_dev_ino; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + NO_PRESERVE_ROOT = CHAR_MAX + 1, + PRESERVE_ROOT, + REFERENCE_FILE_OPTION +}; + +static struct option const long_options[] = +{ + {"changes", no_argument, NULL, 'c'}, + {"recursive", no_argument, NULL, 'R'}, + {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT}, + {"preserve-root", no_argument, NULL, PRESERVE_ROOT}, + {"quiet", no_argument, NULL, 'f'}, + {"reference", required_argument, NULL, REFERENCE_FILE_OPTION}, + {"silent", no_argument, NULL, 'f'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Return true if the chmodable permission bits of FILE changed. + The old mode was OLD_MODE, but it was changed to NEW_MODE. */ + +static bool +mode_changed (char const *file, mode_t old_mode, mode_t new_mode) +{ + if (new_mode & (S_ISUID | S_ISGID | S_ISVTX)) + { + /* The new mode contains unusual bits that the call to chmod may + have silently cleared. Check whether they actually changed. */ + + struct stat new_stats; + + if (stat (file, &new_stats) != 0) + { + if (!force_silent) + error (0, errno, _("getting new attributes of %s"), quote (file)); + return false; + } + + new_mode = new_stats.st_mode; + } + + return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0; +} + +/* Tell the user how/if the MODE of FILE has been changed. + CHANGED describes what (if anything) has happened. */ + +static void +describe_change (const char *file, mode_t mode, + enum Change_status changed) +{ + char perms[12]; /* "-rwxrwxrwx" ls-style modes. */ + const char *fmt; + + if (changed == CH_NOT_APPLIED) + { + printf (_("neither symbolic link %s nor referent has been changed\n"), + quote (file)); + return; + } + + strmode (mode, perms); + perms[10] = '\0'; /* Remove trailing space. */ + switch (changed) + { + case CH_SUCCEEDED: + fmt = _("mode of %s changed to %04lo (%s)\n"); + break; + case CH_FAILED: + fmt = _("failed to change mode of %s to %04lo (%s)\n"); + break; + case CH_NO_CHANGE_REQUESTED: + fmt = _("mode of %s retained as %04lo (%s)\n"); + break; + default: + abort (); + } + printf (fmt, quote (file), + (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]); +} + +/* Change the mode of FILE. + Return true if successful. This function is called + once for every file system object that fts encounters. */ + +static bool +process_file (FTS *fts, FTSENT *ent) +{ + char const *file_full_name = ent->fts_path; + char const *file = ent->fts_accpath; + const struct stat *file_stats = ent->fts_statp; + mode_t old_mode IF_LINT (= 0); + mode_t new_mode IF_LINT (= 0); + bool ok = true; + bool chmod_succeeded = false; + + switch (ent->fts_info) + { + case FTS_DP: + return true; + + case FTS_NS: + /* For a top-level file or directory, this FTS_NS (stat failed) + indicator is determined at the time of the initial fts_open call. + With programs like chmod, chown, and chgrp, that modify + permissions, it is possible that the file in question is + accessible when control reaches this point. So, if this is + the first time we've seen the FTS_NS for this file, tell + fts_read to stat it "again". */ + if (ent->fts_level == 0 && ent->fts_number == 0) + { + ent->fts_number = 1; + fts_set (fts, ent, FTS_AGAIN); + return true; + } + error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name)); + ok = false; + break; + + case FTS_ERR: + error (0, ent->fts_errno, _("%s"), quote (file_full_name)); + ok = false; + break; + + case FTS_DNR: + error (0, ent->fts_errno, _("cannot read directory %s"), + quote (file_full_name)); + ok = false; + break; + + default: + break; + } + + if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats)) + { + ROOT_DEV_INO_WARN (file_full_name); + /* Tell fts not to traverse into this hierarchy. */ + fts_set (fts, ent, FTS_SKIP); + /* Ensure that we do not process "/" on the second visit. */ + ent = fts_read (fts); + ok = false; + } + + if (ok) + { + old_mode = file_stats->st_mode; + new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value, + change, NULL); + + if (! S_ISLNK (old_mode)) + { + if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0) + chmod_succeeded = true; + else + { + if (! force_silent) + error (0, errno, _("changing permissions of %s"), + quote (file_full_name)); + ok = false; + } + } + } + + if (verbosity != V_off) + { + bool changed = (chmod_succeeded + && mode_changed (file, old_mode, new_mode)); + + if (changed || verbosity == V_high) + { + enum Change_status ch_status = + (!ok ? CH_FAILED + : !chmod_succeeded ? CH_NOT_APPLIED + : !changed ? CH_NO_CHANGE_REQUESTED + : CH_SUCCEEDED); + describe_change (file_full_name, new_mode, ch_status); + } + } + + if (chmod_succeeded & diagnose_surprises) + { + mode_t naively_expected_mode = + mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL); + if (new_mode & ~naively_expected_mode) + { + char new_perms[12]; + char naively_expected_perms[12]; + strmode (new_mode, new_perms); + strmode (naively_expected_mode, naively_expected_perms); + new_perms[10] = naively_expected_perms[10] = '\0'; + error (0, 0, + _("%s: new permissions are %s, not %s"), + quotearg_colon (file_full_name), + new_perms + 1, naively_expected_perms + 1); + ok = false; + } + } + + if ( ! recurse) + fts_set (fts, ent, FTS_SKIP); + + return ok; +} + +/* Recursively change the modes of the specified FILES (the last entry + of which is NULL). BIT_FLAGS controls how fts works. + Return true if successful. */ + +static bool +process_files (char **files, int bit_flags) +{ + bool ok = true; + + FTS *fts = xfts_open (files, bit_flags, NULL); + + while (1) + { + FTSENT *ent; + + ent = fts_read (fts); + if (ent == NULL) + { + if (errno != 0) + { + /* FIXME: try to give a better message */ + error (0, errno, _("fts_read failed")); + ok = false; + } + break; + } + + ok &= process_file (fts, ent); + } + + /* Ignore failure, since the only way it can do so is in failing to + return to the original directory, and since we're about to exit, + that doesn't matter. */ + fts_close (fts); + + return ok; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\ + or: %s [OPTION]... OCTAL-MODE FILE...\n\ + or: %s [OPTION]... --reference=RFILE FILE...\n\ +"), + program_name, program_name, program_name); + fputs (_("\ +Change the mode of each FILE to MODE.\n\ +\n\ + -c, --changes like verbose but report only when a change is made\n\ +"), stdout); + fputs (_("\ + --no-preserve-root do not treat `/' specially (the default)\n\ + --preserve-root fail to operate recursively on `/'\n\ +"), stdout); + fputs (_("\ + -f, --silent, --quiet suppress most error messages\n\ + -v, --verbose output a diagnostic for every file processed\n\ + --reference=RFILE use RFILE's mode instead of MODE values\n\ + -R, --recursive change files and directories recursively\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Parse the ASCII mode given on the command line into a linked list + of `struct mode_change' and apply that to each file argument. */ + +int +main (int argc, char **argv) +{ + char *mode = NULL; + size_t mode_len = 0; + size_t mode_alloc = 0; + bool ok; + bool preserve_root = false; + char const *reference_file = NULL; + int c; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + recurse = force_silent = diagnose_surprises = false; + + while ((c = getopt_long (argc, argv, + "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::", + long_options, NULL)) + != -1) + { + 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 '=': + /* Support nonportable uses like "chmod -w", but diagnose + surprises due to umask confusion. Even though "--", "--r", + etc., are valid modes, there is no "case '-'" here since + getopt_long reserves leading "--" for long options. */ + { + /* Allocate a mode string (e.g., "-rwx") by concatenating + the argument containing this option. If a previous mode + string was given, concatenate the previous string, a + comma, and the new string (e.g., "-s,-rwx"). */ + + char const *arg = argv[optind - 1]; + size_t arg_len = strlen (arg); + size_t mode_comma_len = mode_len + !!mode_len; + size_t new_mode_len = mode_comma_len + arg_len; + if (mode_alloc <= new_mode_len) + { + mode_alloc = new_mode_len + 1; + mode = X2REALLOC (mode, &mode_alloc); + } + mode[mode_len] = ','; + strcpy (mode + mode_comma_len, arg); + mode_len = new_mode_len; + + diagnose_surprises = true; + } + break; + case NO_PRESERVE_ROOT: + preserve_root = false; + break; + case PRESERVE_ROOT: + preserve_root = true; + break; + case REFERENCE_FILE_OPTION: + reference_file = optarg; + break; + case 'R': + recurse = true; + break; + case 'c': + verbosity = V_changes_only; + break; + case 'f': + force_silent = true; + break; + case 'v': + verbosity = V_high; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + if (reference_file) + { + if (mode) + { + error (0, 0, _("cannot combine mode and --reference options")); + usage (EXIT_FAILURE); + } + } + else + { + if (!mode) + mode = argv[optind++]; + } + + if (optind >= argc) + { + if (!mode || mode != argv[optind - 1]) + error (0, 0, _("missing operand")); + else + error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); + usage (EXIT_FAILURE); + } + + if (reference_file) + { + change = mode_create_from_ref (reference_file); + if (!change) + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quote (reference_file)); + } + else + { + change = mode_compile (mode); + if (!change) + { + error (0, 0, _("invalid mode: %s"), quote (mode)); + usage (EXIT_FAILURE); + } + umask_value = umask (0); + } + + if (recurse & preserve_root) + { + static struct dev_ino dev_ino_buf; + root_dev_ino = get_root_dev_ino (&dev_ino_buf); + if (root_dev_ino == NULL) + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quote ("/")); + } + else + { + root_dev_ino = NULL; + } + + ok = process_files (argv + optind, FTS_COMFOLLOW | FTS_PHYSICAL); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/chown-core.c b/src/chown-core.c new file mode 100644 index 0000000..bd987a8 --- /dev/null +++ b/src/chown-core.c @@ -0,0 +1,514 @@ +/* chown-core.c -- core functions for changing ownership. + Copyright (C) 2000, 2002, 2003, 2004, 2005, 2006, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Extracted from chown.c/chgrp.c and librarified by Jim Meyering. */ + +#include +#include +#include +#include +#include + +#include "system.h" +#include "chown-core.h" +#include "error.h" +#include "inttostr.h" +#include "openat.h" +#include "quote.h" +#include "root-dev-ino.h" +#include "xfts.h" + +#define FTSENT_IS_DIRECTORY(E) \ + ((E)->fts_info == FTS_D \ + || (E)->fts_info == FTS_DC \ + || (E)->fts_info == FTS_DP \ + || (E)->fts_info == FTS_DNR) + +enum RCH_status + { + /* we called fchown and close, and both succeeded */ + RC_ok = 2, + + /* required_uid and/or required_gid are specified, but don't match */ + RC_excluded, + + /* SAME_INODE check failed */ + RC_inode_changed, + + /* open/fchown isn't needed, isn't safe, or doesn't work due to + permissions problems; fall back on chown */ + RC_do_ordinary_chown, + + /* open, fstat, fchown, or close failed */ + RC_error + }; + +extern void +chopt_init (struct Chown_option *chopt) +{ + chopt->verbosity = V_off; + chopt->root_dev_ino = NULL; + chopt->affect_symlink_referent = true; + chopt->recurse = false; + chopt->force_silent = false; + chopt->user_name = NULL; + chopt->group_name = NULL; +} + +extern void +chopt_free (struct Chown_option *chopt ATTRIBUTE_UNUSED) +{ + /* Deliberately do not free chopt->user_name or ->group_name. + They're not always allocated. */ +} + +/* Convert the numeric group-id, GID, to a string stored in xmalloc'd memory, + and return it. If there's no corresponding group name, use the decimal + representation of the ID. */ + +extern char * +gid_to_name (gid_t gid) +{ + char buf[INT_BUFSIZE_BOUND (intmax_t)]; + struct group *grp = getgrgid (gid); + return xstrdup (grp ? grp->gr_name + : TYPE_SIGNED (gid_t) ? imaxtostr (gid, buf) + : umaxtostr (gid, buf)); +} + +/* Convert the numeric user-id, UID, to a string stored in xmalloc'd memory, + and return it. If there's no corresponding user name, use the decimal + representation of the ID. */ + +extern char * +uid_to_name (uid_t uid) +{ + char buf[INT_BUFSIZE_BOUND (intmax_t)]; + struct passwd *pwd = getpwuid (uid); + return xstrdup (pwd ? pwd->pw_name + : TYPE_SIGNED (uid_t) ? imaxtostr (uid, buf) + : umaxtostr (uid, buf)); +} + +/* Tell the user how/if the user and group of FILE have been changed. + If USER is NULL, give the group-oriented messages. + CHANGED describes what (if anything) has happened. */ + +static void +describe_change (const char *file, enum Change_status changed, + char const *user, char const *group) +{ + const char *fmt; + char const *spec; + char *spec_allocated = NULL; + + if (changed == CH_NOT_APPLIED) + { + printf (_("neither symbolic link %s nor referent has been changed\n"), + quote (file)); + return; + } + + if (user) + { + if (group) + { + spec_allocated = xmalloc (strlen (user) + 1 + strlen (group) + 1); + stpcpy (stpcpy (stpcpy (spec_allocated, user), ":"), group); + spec = spec_allocated; + } + else + { + spec = user; + } + } + else + { + spec = group; + } + + switch (changed) + { + case CH_SUCCEEDED: + fmt = (user ? _("changed ownership of %s to %s\n") + : group ? _("changed group of %s to %s\n") + : _("no change to ownership of %s\n")); + break; + case CH_FAILED: + fmt = (user ? _("failed to change ownership of %s to %s\n") + : group ? _("failed to change group of %s to %s\n") + : _("failed to change ownership of %s\n")); + break; + case CH_NO_CHANGE_REQUESTED: + fmt = (user ? _("ownership of %s retained as %s\n") + : group ? _("group of %s retained as %s\n") + : _("ownership of %s retained\n")); + break; + default: + abort (); + } + + printf (fmt, quote (file), spec); + + free (spec_allocated); +} + +/* Change the owner and/or group of the FILE to UID and/or GID (safely) + only if REQUIRED_UID and REQUIRED_GID match the owner and group IDs + of FILE. ORIG_ST must be the result of `stat'ing FILE. + + The `safely' part above means that we can't simply use chown(2), + since FILE might be replaced with some other file between the time + of the preceding stat/lstat and this chown call. So here we open + FILE and do everything else via the resulting file descriptor. + We first call fstat and verify that the dev/inode match those from + the preceding stat call, and only then, if appropriate (given the + required_uid and required_gid constraints) do we call fchown. + + Return RC_do_ordinary_chown if we can't open FILE, or if FILE is a + special file that might have undesirable side effects when opening. + In this case the caller can use the less-safe ordinary chown. + + Return one of the RCH_status values. */ + +static enum RCH_status +restricted_chown (int cwd_fd, char const *file, + struct stat const *orig_st, + uid_t uid, gid_t gid, + uid_t required_uid, gid_t required_gid) +{ + enum RCH_status status = RC_ok; + struct stat st; + int open_flags = O_NONBLOCK | O_NOCTTY; + int fd; + + if (required_uid == (uid_t) -1 && required_gid == (gid_t) -1) + return RC_do_ordinary_chown; + + if (! S_ISREG (orig_st->st_mode)) + { + if (S_ISDIR (orig_st->st_mode)) + open_flags |= O_DIRECTORY; + else + return RC_do_ordinary_chown; + } + + fd = openat (cwd_fd, file, O_RDONLY | open_flags); + if (! (0 <= fd + || (errno == EACCES && S_ISREG (orig_st->st_mode) + && 0 <= (fd = openat (cwd_fd, file, O_WRONLY | open_flags))))) + return (errno == EACCES ? RC_do_ordinary_chown : RC_error); + + if (fstat (fd, &st) != 0) + status = RC_error; + else if (! SAME_INODE (*orig_st, st)) + status = RC_inode_changed; + else if ((required_uid == (uid_t) -1 || required_uid == st.st_uid) + && (required_gid == (gid_t) -1 || required_gid == st.st_gid)) + { + if (fchown (fd, uid, gid) == 0) + { + status = (close (fd) == 0 + ? RC_ok : RC_error); + return status; + } + else + { + status = RC_error; + } + } + + { /* FIXME: remove these curly braces when we assume C99. */ + int saved_errno = errno; + close (fd); + errno = saved_errno; + return status; + } +} + +/* Change the owner and/or group of the file specified by FTS and ENT + to UID and/or GID as appropriate. + If REQUIRED_UID is not -1, then skip files with any other user ID. + If REQUIRED_GID is not -1, then skip files with any other group ID. + CHOPT specifies additional options. + Return true if successful. */ +static bool +change_file_owner (FTS *fts, FTSENT *ent, + uid_t uid, gid_t gid, + uid_t required_uid, gid_t required_gid, + struct Chown_option const *chopt) +{ + char const *file_full_name = ent->fts_path; + char const *file = ent->fts_accpath; + struct stat const *file_stats; + struct stat stat_buf; + bool ok = true; + bool do_chown; + bool symlink_changed = true; + + switch (ent->fts_info) + { + case FTS_D: + if (chopt->recurse) + { + if (ROOT_DEV_INO_CHECK (chopt->root_dev_ino, ent->fts_statp)) + { + /* This happens e.g., with "chown -R --preserve-root 0 /" + and with "chown -RH --preserve-root 0 symlink-to-root". */ + ROOT_DEV_INO_WARN (file_full_name); + /* Tell fts not to traverse into this hierarchy. */ + fts_set (fts, ent, FTS_SKIP); + /* Ensure that we do not process "/" on the second visit. */ + ent = fts_read (fts); + return false; + } + return true; + } + break; + + case FTS_DP: + if (! chopt->recurse) + return true; + break; + + case FTS_NS: + /* For a top-level file or directory, this FTS_NS (stat failed) + indicator is determined at the time of the initial fts_open call. + With programs like chmod, chown, and chgrp, that modify + permissions, it is possible that the file in question is + accessible when control reaches this point. So, if this is + the first time we've seen the FTS_NS for this file, tell + fts_read to stat it "again". */ + if (ent->fts_level == 0 && ent->fts_number == 0) + { + ent->fts_number = 1; + fts_set (fts, ent, FTS_AGAIN); + return true; + } + error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name)); + ok = false; + break; + + case FTS_ERR: + error (0, ent->fts_errno, _("%s"), quote (file_full_name)); + ok = false; + break; + + case FTS_DNR: + error (0, ent->fts_errno, _("cannot read directory %s"), + quote (file_full_name)); + ok = false; + break; + + default: + break; + } + + if (!ok) + { + do_chown = false; + file_stats = NULL; + } + else if (required_uid == (uid_t) -1 && required_gid == (gid_t) -1 + && chopt->verbosity == V_off + && ! chopt->root_dev_ino + && ! chopt->affect_symlink_referent) + { + do_chown = true; + file_stats = ent->fts_statp; + } + else + { + file_stats = ent->fts_statp; + + /* If this is a symlink and we're dereferencing them, + stat it to get info on the referent. */ + if (chopt->affect_symlink_referent && S_ISLNK (file_stats->st_mode)) + { + if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0) + { + error (0, errno, _("cannot dereference %s"), + quote (file_full_name)); + ok = false; + } + + file_stats = &stat_buf; + } + + do_chown = (ok + && (required_uid == (uid_t) -1 + || required_uid == file_stats->st_uid) + && (required_gid == (gid_t) -1 + || required_gid == file_stats->st_gid)); + } + + /* This happens when chown -LR --preserve-root encounters a symlink-to-/. */ + if (ok + && FTSENT_IS_DIRECTORY (ent) + && ROOT_DEV_INO_CHECK (chopt->root_dev_ino, file_stats)) + { + ROOT_DEV_INO_WARN (file_full_name); + return false; + } + + if (do_chown) + { + if ( ! chopt->affect_symlink_referent) + { + ok = (lchownat (fts->fts_cwd_fd, file, uid, gid) == 0); + + /* Ignore any error due to lack of support; POSIX requires + this behavior for top-level symbolic links with -h, and + implies that it's required for all symbolic links. */ + if (!ok && errno == EOPNOTSUPP) + { + ok = true; + symlink_changed = false; + } + } + else + { + /* If possible, avoid a race condition with --from=O:G and without the + (-h) --no-dereference option. If fts's stat call determined + that the uid/gid of FILE matched the --from=O:G-selected + owner and group IDs, blindly using chown(2) here could lead + chown(1) or chgrp(1) mistakenly to dereference a *symlink* + to an arbitrary file that an attacker had moved into the + place of FILE during the window between the stat and + chown(2) calls. If FILE is a regular file or a directory + that can be opened, this race condition can be avoided safely. */ + + enum RCH_status err + = restricted_chown (fts->fts_cwd_fd, file, file_stats, uid, gid, + required_uid, required_gid); + switch (err) + { + case RC_ok: + break; + + case RC_do_ordinary_chown: + ok = (chownat (fts->fts_cwd_fd, file, uid, gid) == 0); + break; + + case RC_error: + ok = false; + break; + + case RC_inode_changed: + /* FIXME: give a diagnostic in this case? */ + case RC_excluded: + do_chown = false; + ok = false; + break; + + default: + abort (); + } + } + + /* On some systems (e.g., Linux-2.4.x), + the chown function resets the `special' permission bits. + Do *not* restore those bits; doing so would open a window in + which a malicious user, M, could subvert a chown command run + by some other user and operating on files in a directory + where M has write access. */ + + if (do_chown && !ok && ! chopt->force_silent) + error (0, errno, (uid != (uid_t) -1 + ? _("changing ownership of %s") + : _("changing group of %s")), + quote (file_full_name)); + } + + if (chopt->verbosity != V_off) + { + bool changed = + ((do_chown & ok & symlink_changed) + && ! ((uid == (uid_t) -1 || uid == file_stats->st_uid) + && (gid == (gid_t) -1 || gid == file_stats->st_gid))); + + if (changed || chopt->verbosity == V_high) + { + enum Change_status ch_status = + (!ok ? CH_FAILED + : !symlink_changed ? CH_NOT_APPLIED + : !changed ? CH_NO_CHANGE_REQUESTED + : CH_SUCCEEDED); + describe_change (file_full_name, ch_status, + chopt->user_name, chopt->group_name); + } + } + + if ( ! chopt->recurse) + fts_set (fts, ent, FTS_SKIP); + + return ok; +} + +/* Change the owner and/or group of the specified FILES. + BIT_FLAGS specifies how to treat each symlink-to-directory + that is encountered during a recursive traversal. + CHOPT specifies additional options. + If UID is not -1, then change the owner id of each file to UID. + If GID is not -1, then change the group id of each file to GID. + If REQUIRED_UID and/or REQUIRED_GID is not -1, then change only + files with user ID and group ID that match the non-(-1) value(s). + Return true if successful. */ +extern bool +chown_files (char **files, int bit_flags, + uid_t uid, gid_t gid, + uid_t required_uid, gid_t required_gid, + struct Chown_option const *chopt) +{ + bool ok = true; + + /* Use lstat and stat only if they're needed. */ + int stat_flags = ((required_uid != (uid_t) -1 || required_gid != (gid_t) -1 + || chopt->affect_symlink_referent + || chopt->verbosity != V_off) + ? 0 + : FTS_NOSTAT); + + FTS *fts = xfts_open (files, bit_flags | stat_flags, NULL); + + while (1) + { + FTSENT *ent; + + ent = fts_read (fts); + if (ent == NULL) + { + if (errno != 0) + { + /* FIXME: try to give a better message */ + error (0, errno, _("fts_read failed")); + ok = false; + } + break; + } + + ok &= change_file_owner (fts, ent, uid, gid, + required_uid, required_gid, chopt); + } + + /* Ignore failure, since the only way it can do so is in failing to + return to the original directory, and since we're about to exit, + that doesn't matter. */ + fts_close (fts); + + return ok; +} diff --git a/src/chown-core.h b/src/chown-core.h new file mode 100644 index 0000000..1c83cfc --- /dev/null +++ b/src/chown-core.h @@ -0,0 +1,87 @@ +/* chown-core.h -- types and prototypes shared by chown and chgrp. + + Copyright (C) 2000, 2003, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef CHOWN_CORE_H +# define CHOWN_CORE_H + +# include "dev-ino.h" + +enum Change_status +{ + CH_NOT_APPLIED = 1, + CH_SUCCEEDED, + CH_FAILED, + CH_NO_CHANGE_REQUESTED +}; + +enum Verbosity +{ + /* Print a message for each file that is processed. */ + V_high, + + /* Print a message for each file whose attributes we change. */ + V_changes_only, + + /* Do not be verbose. This is the default. */ + V_off +}; + +struct Chown_option +{ + /* Level of verbosity. */ + enum Verbosity verbosity; + + /* If nonzero, change the ownership of directories recursively. */ + bool recurse; + + /* Pointer to the device and inode numbers of `/', when --recursive. + Need not be freed. Otherwise NULL. */ + struct dev_ino *root_dev_ino; + + /* This corresponds to the --dereference (opposite of -h) option. */ + bool affect_symlink_referent; + + /* If nonzero, force silence (no error messages). */ + bool force_silent; + + /* The name of the user to which ownership of the files is being given. */ + char *user_name; + + /* The name of the group to which ownership of the files is being given. */ + char *group_name; +}; + +void +chopt_init (struct Chown_option *); + +void +chopt_free (struct Chown_option *); + +char * +gid_to_name (gid_t); + +char * +uid_to_name (uid_t); + +bool +chown_files (char **files, int bit_flags, + uid_t uid, gid_t gid, + uid_t required_uid, gid_t required_gid, + struct Chown_option const *chopt); + +#endif /* CHOWN_CORE_H */ diff --git a/src/chown.c b/src/chown.c new file mode 100644 index 0000000..63a32f5 --- /dev/null +++ b/src/chown.c @@ -0,0 +1,338 @@ +/* chown -- change user and group ownership of files + Copyright (C) 89, 90, 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 "system.h" +#include "chown-core.h" +#include "error.h" +#include "fts_.h" +#include "quote.h" +#include "root-dev-ino.h" +#include "userspec.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "chown" + +#define AUTHORS "David MacKenzie", "Jim Meyering" + +/* The name the program was run with. */ +char *program_name; + +/* The argument to the --reference option. Use the owner and group IDs + of this file. This file must exist. */ +static char *reference_file; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + DEREFERENCE_OPTION = CHAR_MAX + 1, + FROM_OPTION, + NO_PRESERVE_ROOT, + PRESERVE_ROOT, + REFERENCE_FILE_OPTION +}; + +static struct option const long_options[] = +{ + {"recursive", no_argument, NULL, 'R'}, + {"changes", no_argument, NULL, 'c'}, + {"dereference", no_argument, NULL, DEREFERENCE_OPTION}, + {"from", required_argument, NULL, FROM_OPTION}, + {"no-dereference", no_argument, NULL, 'h'}, + {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT}, + {"preserve-root", no_argument, NULL, PRESERVE_ROOT}, + {"quiet", no_argument, NULL, 'f'}, + {"silent", no_argument, NULL, 'f'}, + {"reference", required_argument, NULL, REFERENCE_FILE_OPTION}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [OWNER][:[GROUP]] FILE...\n\ + or: %s [OPTION]... --reference=RFILE FILE...\n\ +"), + program_name, program_name); + fputs (_("\ +Change the owner and/or group of each FILE to OWNER and/or GROUP.\n\ +With --reference, change the owner and group of each FILE to those of RFILE.\n\ +\n\ + -c, --changes like verbose but report only when a change is made\n\ + --dereference affect the referent of each symbolic link (this is\n\ + the default), rather than the symbolic link itself\n\ +"), stdout); + fputs (_("\ + -h, --no-dereference affect each symbolic link instead of any referenced\n\ + file (useful only on systems that can change the\n\ + ownership of a symlink)\n\ +"), stdout); + fputs (_("\ + --from=CURRENT_OWNER:CURRENT_GROUP\n\ + change the owner and/or group of each file only if\n\ + its current owner and/or group match those specified\n\ + here. Either may be omitted, in which case a match\n\ + is not required for the omitted attribute.\n\ +"), stdout); + fputs (_("\ + --no-preserve-root do not treat `/' specially (the default)\n\ + --preserve-root fail to operate recursively on `/'\n\ +"), stdout); + fputs (_("\ + -f, --silent, --quiet suppress most error messages\n\ + --reference=RFILE use RFILE's owner and group rather than\n\ + specifying OWNER:GROUP values\n\ + -R, --recursive operate on files and directories recursively\n\ + -v, --verbose output a diagnostic for every file processed\n\ +\n\ +"), stdout); + fputs (_("\ +The following options modify how a hierarchy is traversed when the -R\n\ +option is also specified. If more than one is specified, only the final\n\ +one takes effect.\n\ +\n\ + -H if a command line argument is a symbolic link\n\ + to a directory, traverse it\n\ + -L traverse every symbolic link to a directory\n\ + encountered\n\ + -P do not traverse any symbolic links (default)\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Owner is unchanged if missing. Group is unchanged if missing, but changed\n\ +to login group if implied by a `:' following a symbolic OWNER.\n\ +OWNER and GROUP may be numeric as well as symbolic.\n\ +"), stdout); + printf (_("\ +\n\ +Examples:\n\ + %s root /u Change the owner of /u to \"root\".\n\ + %s root:staff /u Likewise, but also change its group to \"staff\".\n\ + %s -hR root /u Change the owner of /u and subfiles to \"root\".\n\ +"), + program_name, program_name, program_name); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + bool preserve_root = false; + + uid_t uid = -1; /* Specified uid; -1 if not to be changed. */ + gid_t gid = -1; /* Specified gid; -1 if not to be changed. */ + + /* Change the owner (group) of a file only if it has this uid (gid). + -1 means there's no restriction. */ + uid_t required_uid = -1; + gid_t required_gid = -1; + + /* Bit flags that control how fts works. */ + int bit_flags = FTS_PHYSICAL; + + /* 1 if --dereference, 0 if --no-dereference, -1 if neither has been + specified. */ + int dereference = -1; + + struct Chown_option chopt; + bool ok; + int optc; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + chopt_init (&chopt); + + while ((optc = getopt_long (argc, argv, "HLPRcfhv", long_options, NULL)) + != -1) + { + switch (optc) + { + case 'H': /* Traverse command-line symlinks-to-directories. */ + bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL; + break; + + case 'L': /* Traverse all symlinks-to-directories. */ + bit_flags = FTS_LOGICAL; + break; + + case 'P': /* Traverse no symlinks-to-directories. */ + bit_flags = FTS_PHYSICAL; + break; + + case 'h': /* --no-dereference: affect symlinks */ + dereference = 0; + break; + + case DEREFERENCE_OPTION: /* --dereference: affect the referent + of each symlink */ + dereference = 1; + break; + + case NO_PRESERVE_ROOT: + preserve_root = false; + break; + + case PRESERVE_ROOT: + preserve_root = true; + break; + + case REFERENCE_FILE_OPTION: + reference_file = optarg; + break; + + case FROM_OPTION: + { + char *u_dummy, *g_dummy; + const char *e = parse_user_spec (optarg, + &required_uid, &required_gid, + &u_dummy, &g_dummy); + if (e) + error (EXIT_FAILURE, 0, "%s: %s", quote (optarg), e); + break; + } + + case 'R': + chopt.recurse = true; + break; + + case 'c': + chopt.verbosity = V_changes_only; + break; + + case 'f': + chopt.force_silent = true; + break; + + case 'v': + chopt.verbosity = V_high; + break; + + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + if (chopt.recurse) + { + if (bit_flags == FTS_PHYSICAL) + { + if (dereference == 1) + error (EXIT_FAILURE, 0, + _("-R --dereference requires either -H or -L")); + dereference = 0; + } + } + else + { + bit_flags = FTS_PHYSICAL; + } + chopt.affect_symlink_referent = (dereference != 0); + + if (argc - optind < (reference_file ? 1 : 2)) + { + if (argc <= optind) + error (0, 0, _("missing operand")); + else + error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); + usage (EXIT_FAILURE); + } + + if (reference_file) + { + struct stat ref_stats; + if (stat (reference_file, &ref_stats)) + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quote (reference_file)); + + uid = ref_stats.st_uid; + gid = ref_stats.st_gid; + chopt.user_name = uid_to_name (ref_stats.st_uid); + chopt.group_name = gid_to_name (ref_stats.st_gid); + } + else + { + const char *e = parse_user_spec (argv[optind], &uid, &gid, + &chopt.user_name, &chopt.group_name); + if (e) + error (EXIT_FAILURE, 0, "%s: %s", quote (argv[optind]), e); + + /* If a group is specified but no user, set the user name to the + empty string so that diagnostics say "ownership :GROUP" + rather than "group GROUP". */ + if (!chopt.user_name && chopt.group_name) + chopt.user_name = ""; + + optind++; + } + + if (chopt.recurse & preserve_root) + { + static struct dev_ino dev_ino_buf; + chopt.root_dev_ino = get_root_dev_ino (&dev_ino_buf); + if (chopt.root_dev_ino == NULL) + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quote ("/")); + } + + ok = chown_files (argv + optind, bit_flags, + uid, gid, + required_uid, required_gid, &chopt); + + chopt_free (&chopt); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/chroot.c b/src/chroot.c new file mode 100644 index 0000000..d2ae0e5 --- /dev/null +++ b/src/chroot.c @@ -0,0 +1,118 @@ +/* chroot -- run command or shell with special root directory + Copyright (C) 95, 96, 1997, 1999-2004 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Roland McGrath. */ + +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "long-options.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "chroot" + +#define AUTHORS "Roland McGrath" + +/* The name this program was run with, for error messages. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s NEWROOT [COMMAND...]\n\ + or: %s OPTION\n\ +"), program_name, program_name); + fputs (_("\ +Run COMMAND with root directory set to NEWROOT.\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +If no command is given, run ``${SHELL} -i'' (default: /bin/sh).\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (EXIT_FAIL); + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "+", NULL, NULL) != -1) + usage (EXIT_FAIL); + + if (argc <= optind) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAIL); + } + + if (chroot (argv[optind]) != 0) + error (EXIT_FAIL, errno, _("cannot change root directory to %s"), argv[1]); + + if (chdir ("/")) + error (EXIT_FAIL, errno, _("cannot chdir to root directory")); + + if (argc == optind + 1) + { + /* No command. Run an interactive shell. */ + char *shell = getenv ("SHELL"); + if (shell == NULL) + shell = "/bin/sh"; + argv[0] = shell; + argv[1] = "-i"; + argv[2] = NULL; + } + else + { + /* The following arguments give the command. */ + argv += optind + 1; + } + + /* Execute the given command. */ + execvp (argv[0], argv); + + { + int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE); + error (0, errno, _("cannot run command %s"), quote (argv[0])); + exit (exit_status); + } +} diff --git a/src/cksum.c b/src/cksum.c new file mode 100644 index 0000000..d93877f --- /dev/null +++ b/src/cksum.c @@ -0,0 +1,316 @@ +/* cksum -- calculate and print POSIX checksums and sizes of files + Copyright (C) 92, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Q. Frank Xia, qx@math.columbia.edu. + Cosmetic changes and reorganization by David MacKenzie, djm@gnu.ai.mit.edu. + + Usage: cksum [file...] + + The code segment between "#ifdef CRCTAB" and "#else" is the code + which calculates the "crctab". It is included for those who want + verify the correctness of the "crctab". To recreate the "crctab", + do something like the following: + + cc -DCRCTAB -o crctab cksum.c + crctab > crctab.h + + This software is compatible with neither the System V nor the BSD + `sum' program. It is supposed to conform to POSIX, except perhaps + for foreign language support. Any inconsistency with the standard + (other than foreign language support) is a bug. */ + +#include + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "cksum" + +#define AUTHORS "Q. Frank Xia" + +#include +#include +#include +#include "system.h" + +#ifdef CRCTAB + +# define BIT(x) ((uint_fast32_t) 1 << (x)) +# define SBIT BIT (31) + +/* The generating polynomial is + + 32 26 23 22 16 12 11 10 8 7 5 4 2 1 + G(X)=X + X + X + X + X + X + X + X + X + X + X + X + X + X + 1 + + The i bit in GEN is set if X^i is a summand of G(X) except X^32. */ + +# define GEN (BIT (26) | BIT (23) | BIT (22) | BIT (16) | BIT (12) \ + | BIT (11) | BIT (10) | BIT (8) | BIT (7) | BIT (5) \ + | BIT (4) | BIT (2) | BIT (1) | BIT (0)) + +static uint_fast32_t r[8]; + +static void +fill_r (void) +{ + int i; + + r[0] = GEN; + for (i = 1; i < 8; i++) + r[i] = (r[i - 1] << 1) ^ ((r[i - 1] & SBIT) ? GEN : 0); +} + +static uint_fast32_t +crc_remainder (int m) +{ + uint_fast32_t rem = 0; + int i; + + for (i = 0; i < 8; i++) + if (BIT (i) & m) + rem ^= r[i]; + + return rem & 0xFFFFFFFF; /* Make it run on 64-bit machine. */ +} + +int +main (void) +{ + int i; + + fill_r (); + printf ("static uint_fast32_t const crctab[256] =\n{\n 0x00000000"); + for (i = 0; i < 51; i++) + { + printf (",\n 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x", + crc_remainder (i * 5 + 1), crc_remainder (i * 5 + 2), + crc_remainder (i * 5 + 3), crc_remainder (i * 5 + 4), + crc_remainder (i * 5 + 5)); + } + printf ("\n};\n"); + exit (EXIT_SUCCESS); +} + +#else /* !CRCTAB */ + +# include +# include "long-options.h" +# include "error.h" +# include "inttostr.h" + +/* Number of bytes to read at once. */ +# define BUFLEN (1 << 16) + +/* The name this program was run with. */ +char *program_name; + +static uint_fast32_t const crctab[256] = +{ + 0x00000000, + 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, + 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, + 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, + 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, + 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, + 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, + 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, + 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, + 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, + 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, + 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, + 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, + 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, + 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, + 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, + 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, + 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, + 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, + 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, + 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, + 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, + 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, + 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, + 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, + 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, + 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, + 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, + 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, + 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, + 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, + 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, + 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, + 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, + 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +/* Nonzero if any of the files read were the standard input. */ +static bool have_read_stdin; + +/* Calculate and print the checksum and length in bytes + of file FILE, or of the standard input if FILE is "-". + If PRINT_NAME is true, print FILE next to the checksum and size. + Return true if successful. */ + +static bool +cksum (const char *file, bool print_name) +{ + unsigned char buf[BUFLEN]; + uint_fast32_t crc = 0; + uintmax_t length = 0; + size_t bytes_read; + FILE *fp; + char length_buf[INT_BUFSIZE_BOUND (uintmax_t)]; + char const *hp; + + if (STREQ (file, "-")) + { + fp = stdin; + have_read_stdin = true; + if (O_BINARY && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + } + else + { + fp = fopen (file, (O_BINARY ? "rb" : "r")); + if (fp == NULL) + { + error (0, errno, "%s", file); + return false; + } + } + + while ((bytes_read = fread (buf, 1, BUFLEN, fp)) > 0) + { + unsigned char *cp = buf; + + if (length + bytes_read < length) + error (EXIT_FAILURE, 0, _("%s: file too long"), file); + length += bytes_read; + while (bytes_read--) + crc = (crc << 8) ^ crctab[((crc >> 24) ^ *cp++) & 0xFF]; + if (feof (fp)) + break; + } + + if (ferror (fp)) + { + error (0, errno, "%s", file); + if (!STREQ (file, "-")) + fclose (fp); + return false; + } + + if (!STREQ (file, "-") && fclose (fp) == EOF) + { + error (0, errno, "%s", file); + return false; + } + + hp = umaxtostr (length, length_buf); + + for (; length; length >>= 8) + crc = (crc << 8) ^ crctab[((crc >> 24) ^ length) & 0xFF]; + + crc = ~crc & 0xFFFFFFFF; + + if (print_name) + printf ("%u %s %s\n", (unsigned int) crc, hp, file); + else + printf ("%u %s\n", (unsigned int) crc, hp); + + if (ferror (stdout)) + error (EXIT_FAILURE, errno, "-: %s", _("write error")); + + return true; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [FILE]...\n\ + or: %s [OPTION]\n\ +"), + program_name, program_name); + fputs (_("\ +Print CRC checksum and byte counts of each FILE.\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int i; + bool ok; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + have_read_stdin = false; + + if (optind == argc) + ok = cksum ("-", false); + else + { + ok = true; + for (i = optind; i < argc; i++) + ok &= cksum (argv[i], true); + } + + if (have_read_stdin && fclose (stdin) == EOF) + error (EXIT_FAILURE, errno, "-"); + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} + +#endif /* !CRCTAB */ diff --git a/src/comm.c b/src/comm.c new file mode 100644 index 0000000..9b7e03f --- /dev/null +++ b/src/comm.c @@ -0,0 +1,285 @@ +/* comm -- compare two sorted files line by line. + Copyright (C) 86, 90, 91, 1995-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Richard Stallman and David MacKenzie. */ + +#include + +#include +#include +#include "system.h" +#include "linebuffer.h" +#include "error.h" +#include "hard-locale.h" +#include "quote.h" +#include "stdio--.h" +#include "xmemcoll.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "comm" + +#define AUTHORS "Richard Stallman", "David MacKenzie" + +/* Undefine, to avoid warning about redefinition on some systems. */ +#undef min +#define min(x, y) ((x) < (y) ? (x) : (y)) + +/* The name this program was run with. */ +char *program_name; + +/* True if the LC_COLLATE locale is hard. */ +static bool hard_LC_COLLATE; + +/* If true, print lines that are found only in file 1. */ +static bool only_file_1; + +/* If true, print lines that are found only in file 2. */ +static bool only_file_2; + +/* If true, print lines that are found in both files. */ +static bool both; + +static struct option const long_options[] = +{ + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + + + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... FILE1 FILE2\n\ +"), + program_name); + fputs (_("\ +Compare sorted files FILE1 and FILE2 line by line.\n\ +"), stdout); + fputs (_("\ +\n\ +With no options, produce three-column output. Column one contains\n\ +lines unique to FILE1, column two contains lines unique to FILE2,\n\ +and column three contains lines common to both files.\n\ +"), stdout); + fputs (_("\ +\n\ + -1 suppress lines unique to FILE1\n\ + -2 suppress lines unique to FILE2\n\ + -3 suppress lines that appear in both files\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Output the line in linebuffer LINE to stream STREAM + provided the switches say it should be output. + CLASS is 1 for a line found only in file 1, + 2 for a line only in file 2, 3 for a line in both. */ + +static void +writeline (const struct linebuffer *line, FILE *stream, int class) +{ + switch (class) + { + case 1: + if (!only_file_1) + return; + break; + + case 2: + if (!only_file_2) + return; + /* Print a TAB if we are printing lines from file 1. */ + if (only_file_1) + putc ('\t', stream); + break; + + case 3: + if (!both) + return; + /* Print a TAB if we are printing lines from file 1. */ + if (only_file_1) + putc ('\t', stream); + /* Print a TAB if we are printing lines from file 2. */ + if (only_file_2) + putc ('\t', stream); + break; + } + + fwrite (line->buffer, sizeof (char), line->length, stream); +} + +/* Compare INFILES[0] and INFILES[1]. + If either is "-", use the standard input for that file. + Assume that each input file is sorted; + merge them and output the result. */ + +static void +compare_files (char **infiles) +{ + /* For each file, we have one linebuffer in lb1. */ + struct linebuffer lb1[2]; + + /* thisline[i] points to the linebuffer holding the next available line + in file i, or is NULL if there are no lines left in that file. */ + struct linebuffer *thisline[2]; + + /* streams[i] holds the input stream for file i. */ + FILE *streams[2]; + + int i; + + /* Initialize the storage. */ + for (i = 0; i < 2; i++) + { + initbuffer (&lb1[i]); + thisline[i] = &lb1[i]; + streams[i] = (STREQ (infiles[i], "-") ? stdin : fopen (infiles[i], "r")); + if (!streams[i]) + error (EXIT_FAILURE, errno, "%s", infiles[i]); + + thisline[i] = readlinebuffer (thisline[i], streams[i]); + if (ferror (streams[i])) + error (EXIT_FAILURE, errno, "%s", infiles[i]); + } + + while (thisline[0] || thisline[1]) + { + int order; + + /* Compare the next available lines of the two files. */ + + if (!thisline[0]) + order = 1; + else if (!thisline[1]) + order = -1; + else + { + if (hard_LC_COLLATE) + order = xmemcoll (thisline[0]->buffer, thisline[0]->length - 1, + thisline[1]->buffer, thisline[1]->length - 1); + else + { + size_t len = min (thisline[0]->length, thisline[1]->length) - 1; + order = memcmp (thisline[0]->buffer, thisline[1]->buffer, len); + if (order == 0) + order = (thisline[0]->length < thisline[1]->length + ? -1 + : thisline[0]->length != thisline[1]->length); + } + } + + /* Output the line that is lesser. */ + if (order == 0) + writeline (thisline[1], stdout, 3); + else if (order > 0) + writeline (thisline[1], stdout, 2); + else + writeline (thisline[0], stdout, 1); + + /* Step the file the line came from. + If the files match, step both files. */ + if (order >= 0) + { + thisline[1] = readlinebuffer (thisline[1], streams[1]); + if (ferror (streams[1])) + error (EXIT_FAILURE, errno, "%s", infiles[1]); + } + if (order <= 0) + { + thisline[0] = readlinebuffer (thisline[0], streams[0]); + if (ferror (streams[0])) + error (EXIT_FAILURE, errno, "%s", infiles[0]); + } + } + + for (i = 0; i < 2; i++) + if (fclose (streams[i]) != 0) + error (EXIT_FAILURE, errno, "%s", infiles[i]); +} + +int +main (int argc, char **argv) +{ + int c; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + hard_LC_COLLATE = hard_locale (LC_COLLATE); + + atexit (close_stdout); + + only_file_1 = true; + only_file_2 = true; + both = true; + + while ((c = getopt_long (argc, argv, "123", long_options, NULL)) != -1) + switch (c) + { + case '1': + only_file_1 = false; + break; + + case '2': + only_file_2 = false; + break; + + case '3': + both = false; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + + if (argc - optind < 2) + { + if (argc <= optind) + error (0, 0, _("missing operand")); + else + error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); + usage (EXIT_FAILURE); + } + + if (2 < argc - optind) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 2])); + usage (EXIT_FAILURE); + } + + compare_files (argv + optind); + + exit (EXIT_SUCCESS); +} diff --git a/src/copy.c b/src/copy.c new file mode 100644 index 0000000..4bdb75c --- /dev/null +++ b/src/copy.c @@ -0,0 +1,2007 @@ +/* copy.c -- core functions for copying files and directories + Copyright (C) 89, 90, 91, 1995-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Extracted from cp.c and librarified by Jim Meyering. */ + +#include +#include +#include +#include + +#if HAVE_HURD_H +# include +#endif +#if HAVE_PRIV_H +# include +#endif + +#include "system.h" +#include "acl.h" +#include "backupfile.h" +#include "buffer-lcm.h" +#include "copy.h" +#include "cp-hash.h" +#include "euidaccess.h" +#include "error.h" +#include "fcntl--.h" +#include "filemode.h" +#include "filenamecat.h" +#include "full-write.h" +#include "getpagesize.h" +#include "hash.h" +#include "hash-pjw.h" +#include "lchmod.h" +#include "quote.h" +#include "same.h" +#include "savedir.h" +#include "stat-time.h" +#include "utimecmp.h" +#include "utimens.h" +#include "xreadlink.h" +#include "yesno.h" + +#ifndef HAVE_FCHOWN +# define HAVE_FCHOWN false +# define fchown(fd, uid, gid) (-1) +#endif + +#define SAME_OWNER(A, B) ((A).st_uid == (B).st_uid) +#define SAME_GROUP(A, B) ((A).st_gid == (B).st_gid) +#define SAME_OWNER_AND_GROUP(A, B) (SAME_OWNER (A, B) && SAME_GROUP (A, B)) + +#define UNWRITABLE(File_name, File_mode) \ + ( /* euidaccess is not meaningful for symlinks */ \ + ! S_ISLNK (File_mode) \ + && euidaccess (File_name, W_OK) != 0) + +struct dir_list +{ + struct dir_list *parent; + ino_t ino; + dev_t dev; +}; + +/* Describe a just-created or just-renamed destination file. */ +struct F_triple +{ + char *name; + ino_t st_ino; + dev_t st_dev; +}; + +/* Initial size of the above hash table. */ +#define DEST_INFO_INITIAL_CAPACITY 61 + +static bool copy_internal (char const *src_name, char const *dst_name, + bool new_dst, dev_t device, + struct dir_list *ancestors, + const struct cp_options *x, + bool command_line_arg, + bool *copy_into_self, + bool *rename_succeeded); + +/* Pointers to the file names: they're used in the diagnostic that is issued + when we detect the user is trying to copy a directory into itself. */ +static char const *top_level_src_name; +static char const *top_level_dst_name; + +/* The invocation name of this program. */ +extern char *program_name; + +/* FIXME: describe */ +/* FIXME: rewrite this to use a hash table so we avoid the quadratic + performance hit that's probably noticeable only on trees deeper + than a few hundred levels. See use of active_dir_map in remove.c */ + +static bool +is_ancestor (const struct stat *sb, const struct dir_list *ancestors) +{ + while (ancestors != 0) + { + if (ancestors->ino == sb->st_ino && ancestors->dev == sb->st_dev) + return true; + ancestors = ancestors->parent; + } + return false; +} + +/* Read the contents of the directory SRC_NAME_IN, and recursively + copy the contents to DST_NAME_IN. NEW_DST is true if + DST_NAME_IN is a directory that was created previously in the + recursion. SRC_SB and ANCESTORS describe SRC_NAME_IN. + Set *COPY_INTO_SELF if SRC_NAME_IN is a parent of + (or the same as) DST_NAME_IN; otherwise, clear it. + Return true if successful. */ + +static bool +copy_dir (char const *src_name_in, char const *dst_name_in, bool new_dst, + const struct stat *src_sb, struct dir_list *ancestors, + const struct cp_options *x, bool *copy_into_self) +{ + char *name_space; + char *namep; + struct cp_options non_command_line_options = *x; + bool ok = true; + + name_space = savedir (src_name_in); + if (name_space == NULL) + { + /* This diagnostic is a bit vague because savedir can fail in + several different ways. */ + error (0, errno, _("cannot access %s"), quote (src_name_in)); + return false; + } + + /* For cp's -H option, dereference command line arguments, but do not + dereference symlinks that are found via recursive traversal. */ + if (x->dereference == DEREF_COMMAND_LINE_ARGUMENTS) + non_command_line_options.dereference = DEREF_NEVER; + + namep = name_space; + while (*namep != '\0') + { + bool local_copy_into_self; + char *src_name = file_name_concat (src_name_in, namep, NULL); + char *dst_name = file_name_concat (dst_name_in, namep, NULL); + + ok &= copy_internal (src_name, dst_name, new_dst, src_sb->st_dev, + ancestors, &non_command_line_options, false, + &local_copy_into_self, NULL); + *copy_into_self |= local_copy_into_self; + + free (dst_name); + free (src_name); + + namep += strlen (namep) + 1; + } + free (name_space); + return ok; +} + +/* Set the owner and owning group of DEST_DESC to the st_uid and + st_gid fields of SRC_SB. If DEST_DESC is undefined (-1), set + the owner and owning group of DST_NAME instead. DEST_DESC must + refer to the same file as DEST_NAME if defined. + Return 1 if the syscall succeeds, 0 if it fails but it's OK + not to preserve ownership, -1 otherwise. */ + +static int +set_owner (const struct cp_options *x, char const *dst_name, int dest_desc, + uid_t uid, gid_t gid) +{ + if (HAVE_FCHOWN && dest_desc != -1) + { + if (fchown (dest_desc, uid, gid) == 0) + return 1; + } + else + { + if (chown (dst_name, uid, gid) == 0) + return 1; + } + + if (! chown_failure_ok (x)) + { + error (0, errno, _("failed to preserve ownership for %s"), + quote (dst_name)); + if (x->require_preserve) + return -1; + } + + return 0; +} + +/* Set the st_author field of DEST_DESC to the st_author field of + SRC_SB. If DEST_DESC is undefined (-1), set the st_author field + of DST_NAME instead. DEST_DESC must refer to the same file as + DEST_NAME if defined. */ + +static void +set_author (const char *dst_name, int dest_desc, const struct stat *src_sb) +{ +#if HAVE_STRUCT_STAT_ST_AUTHOR + /* Preserve the st_author field. */ + file_t file = (dest_desc < 0 + ? file_name_lookup (dst_name, 0, 0) + : getdport (dest_desc)); + if (file == MACH_PORT_NULL) + error (0, errno, _("failed to lookup file %s"), quote (dst_name)); + else + { + error_t err = file_chauthor (file, src_sb->st_author); + if (err) + error (0, err, _("failed to preserve authorship for %s"), + quote (dst_name)); + mach_port_deallocate (mach_task_self (), file); + } +#endif +} + +/* Change the file mode bits of the file identified by DESC or NAME to MODE. + Use DESC if DESC is valid and fchmod is available, NAME otherwise. */ + +static int +fchmod_or_lchmod (int desc, char const *name, mode_t mode) +{ +#if HAVE_FCHMOD + if (0 <= desc) + return fchmod (desc, mode); +#endif + return lchmod (name, mode); +} + +/* Copy a regular file from SRC_NAME to DST_NAME. + 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.) + When creating the destination, use DST_MODE & ~OMITTED_PERMISSIONS + as the third argument in the call to open, adding + OMITTED_PERMISSIONS after copying as needed. + X provides many option settings. + Return true if successful. + *NEW_DST is as in copy_internal. + SRC_SB is the result of calling XSTAT (aka stat) on SRC_NAME. */ + +static bool +copy_reg (char const *src_name, char const *dst_name, + const struct cp_options *x, + mode_t dst_mode, mode_t omitted_permissions, bool *new_dst, + struct stat const *src_sb) +{ + char *buf; + char *buf_alloc = NULL; + int dest_desc; + int source_desc; + mode_t src_mode = src_sb->st_mode; + struct stat sb; + struct stat src_open_sb; + bool return_val = true; + + source_desc = open (src_name, O_RDONLY | O_BINARY); + if (source_desc < 0) + { + error (0, errno, _("cannot open %s for reading"), quote (src_name)); + return false; + } + + if (fstat (source_desc, &src_open_sb) != 0) + { + error (0, errno, _("cannot fstat %s"), quote (src_name)); + return_val = false; + goto close_src_desc; + } + + /* Compare the source dev/ino from the open file to the incoming, + saved ones obtained via a previous call to stat. */ + if (! SAME_INODE (*src_sb, src_open_sb)) + { + error (0, 0, + _("skipping file %s, as it was replaced while being copied"), + quote (src_name)); + return_val = false; + goto close_src_desc; + } + + /* The semantics of the following open calls are mandated + by the specs for both cp and mv. */ + if (! *new_dst) + { + dest_desc = open (dst_name, O_WRONLY | O_TRUNC | O_BINARY); + + if (dest_desc < 0 && x->unlink_dest_after_failed_open) + { + if (unlink (dst_name) != 0) + { + error (0, errno, _("cannot remove %s"), quote (dst_name)); + return_val = false; + goto close_src_desc; + } + if (x->verbose) + printf (_("removed %s\n"), quote (dst_name)); + + /* Tell caller that the destination file was unlinked. */ + *new_dst = true; + } + } + + if (*new_dst) + dest_desc = open (dst_name, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, + dst_mode & ~omitted_permissions); + else + omitted_permissions = 0; + + if (dest_desc < 0) + { + error (0, errno, _("cannot create regular file %s"), quote (dst_name)); + return_val = false; + goto close_src_desc; + } + + if (fstat (dest_desc, &sb) != 0) + { + error (0, errno, _("cannot fstat %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + + if (! (S_ISREG (src_open_sb.st_mode) && src_open_sb.st_size == 0)) + { + typedef uintptr_t word; + off_t n_read_total = 0; + + /* Choose a suitable buffer size; it may be adjusted later. */ + size_t buf_alignment = lcm (getpagesize (), sizeof (word)); + size_t buf_alignment_slop = sizeof (word) + buf_alignment - 1; + size_t buf_size = ST_BLKSIZE (sb); + + /* Deal with sparse files. */ + bool last_write_made_hole = false; + bool make_holes = false; + + if (S_ISREG (sb.st_mode)) + { + /* Even with --sparse=always, try to create holes only + if the destination is a regular file. */ + if (x->sparse_mode == SPARSE_ALWAYS) + make_holes = true; + +#if HAVE_STRUCT_STAT_ST_BLOCKS + /* Use a heuristic to determine whether SRC_NAME contains any sparse + blocks. 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 (x->sparse_mode == SPARSE_AUTO && S_ISREG (src_open_sb.st_mode) + && ST_NBLOCKS (src_open_sb) < src_open_sb.st_size / ST_NBLOCKSIZE) + make_holes = true; +#endif + } + + /* If not making a sparse file, try to use a more-efficient + buffer size. */ + if (! make_holes) + { + /* These days there's no point ever messing with buffers smaller + than 8 KiB. It would be nice to configure SMALL_BUF_SIZE + dynamically for this host and pair of files, but there doesn't + seem to be a good way to get readahead info portably. */ + enum { SMALL_BUF_SIZE = 8 * 1024 }; + + /* Compute the least common multiple of the input and output + buffer sizes, adjusting for outlandish values. */ + size_t blcm_max = MIN (SIZE_MAX, SSIZE_MAX) - buf_alignment_slop; + size_t blcm = buffer_lcm (ST_BLKSIZE (src_open_sb), buf_size, + blcm_max); + + /* Do not use a block size that is too small. */ + buf_size = MAX (SMALL_BUF_SIZE, blcm); + + /* Do not bother with a buffer larger than the input file, plus one + byte to make sure the file has not grown while reading it. */ + if (S_ISREG (src_open_sb.st_mode) && src_open_sb.st_size < buf_size) + buf_size = src_open_sb.st_size + 1; + + /* However, stick with a block size that is a positive multiple of + blcm, overriding the above adjustments. Watch out for + overflow. */ + buf_size += blcm - 1; + buf_size -= buf_size % blcm; + if (buf_size == 0 || blcm_max < buf_size) + buf_size = blcm; + } + + /* Make a buffer with space for a sentinel at the end. */ + buf_alloc = xmalloc (buf_size + buf_alignment_slop); + buf = ptr_align (buf_alloc, buf_alignment); + + for (;;) + { + word *wp = NULL; + + ssize_t n_read = read (source_desc, buf, buf_size); + if (n_read < 0) + { +#ifdef EINTR + if (errno == EINTR) + continue; +#endif + error (0, errno, _("reading %s"), quote (src_name)); + return_val = false; + goto close_src_and_dst_desc; + } + if (n_read == 0) + break; + + n_read_total += n_read; + + if (make_holes) + { + char *cp; + + /* Sentinel to stop loop. */ + buf[n_read] = '\1'; +#ifdef lint + /* Usually, buf[n_read] is not the byte just before a "word" + (aka uintptr_t) boundary. In that case, the word-oriented + test below (*wp++ == 0) would read some uninitialized bytes + after the sentinel. To avoid false-positive reports about + this condition (e.g., from a tool like valgrind), set the + remaining bytes -- to any value. */ + memset (buf + n_read + 1, 0, sizeof (word) - 1); +#endif + + /* Find first nonzero *word*, or the word with the sentinel. */ + + wp = (word *) buf; + while (*wp++ == 0) + continue; + + /* Find the first nonzero *byte*, or the sentinel. */ + + cp = (char *) (wp - 1); + while (*cp++ == 0) + continue; + + if (cp <= buf + n_read) + /* Clear to indicate that a normal write is needed. */ + wp = NULL; + else + { + /* We found the sentinel, so the whole input block was zero. + Make a hole. */ + if (lseek (dest_desc, n_read, SEEK_CUR) < 0) + { + error (0, errno, _("cannot lseek %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + last_write_made_hole = true; + } + } + + if (!wp) + { + size_t n = n_read; + if (full_write (dest_desc, buf, n) != n) + { + error (0, errno, _("writing %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + last_write_made_hole = false; + + /* A short read on a regular file means EOF. */ + if (n_read != buf_size && S_ISREG (src_open_sb.st_mode)) + break; + } + } + + /* If the file ends with a `hole', we need to do something to record + the length of the file. On modern systems, calling ftruncate does + the job. On systems without native ftruncate support, we have to + write a byte at the ending position. Otherwise the kernel would + truncate the file at the end of the last write operation. */ + + if (last_write_made_hole) + { + if (HAVE_FTRUNCATE + ? /* ftruncate sets the file size, + so there is no need for a write. */ + ftruncate (dest_desc, n_read_total) < 0 + : /* Seek backwards one character and write a null. */ + (lseek (dest_desc, (off_t) -1, SEEK_CUR) < 0L + || full_write (dest_desc, "", 1) != 1)) + { + error (0, errno, _("writing %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + } + } + + if (x->preserve_timestamps) + { + struct timespec timespec[2]; + timespec[0] = get_stat_atime (src_sb); + timespec[1] = get_stat_mtime (src_sb); + + if (futimens (dest_desc, dst_name, timespec) != 0) + { + error (0, errno, _("preserving times for %s"), quote (dst_name)); + if (x->require_preserve) + { + return_val = false; + goto close_src_and_dst_desc; + } + } + } + + if (x->preserve_ownership && ! SAME_OWNER_AND_GROUP (*src_sb, sb)) + { + switch (set_owner (x, dst_name, dest_desc, + src_sb->st_uid, src_sb->st_gid)) + { + case -1: + return_val = false; + goto close_src_and_dst_desc; + + case 0: + src_mode &= ~ (S_ISUID | S_ISGID | S_ISVTX); + break; + } + } + + set_author (dst_name, dest_desc, src_sb); + + if (x->preserve_mode || x->move_mode) + { + if (copy_acl (src_name, source_desc, dst_name, dest_desc, src_mode) != 0 + && x->require_preserve) + return_val = false; + } + else if (x->set_mode) + { + if (set_acl (dst_name, dest_desc, x->mode) != 0) + return_val = false; + } + else if (omitted_permissions) + { + omitted_permissions &= ~ cached_umask (); + if (omitted_permissions + && fchmod_or_lchmod (dest_desc, dst_name, dst_mode) != 0) + { + error (0, errno, _("preserving permissions for %s"), + quote (dst_name)); + if (x->require_preserve) + return_val = false; + } + } + +close_src_and_dst_desc: + if (close (dest_desc) < 0) + { + error (0, errno, _("closing %s"), quote (dst_name)); + return_val = false; + } +close_src_desc: + if (close (source_desc) < 0) + { + error (0, errno, _("closing %s"), quote (src_name)); + return_val = false; + } + + free (buf_alloc); + return return_val; +} + +/* Return true if it's ok that the source and destination + files are the `same' by some measure. The goal is to avoid + making the `copy' operation remove both copies of the file + in that case, while still allowing the user to e.g., move or + copy a regular file onto a symlink that points to it. + Try to minimize the cost of this function in the common case. + Set *RETURN_NOW if we've determined that the caller has no more + work to do and should return successfully, right away. + + Set *UNLINK_SRC if we've determined that the caller wants to do + `rename (a, b)' where `a' and `b' are distinct hard links to the same + file. In that case, the caller should try to unlink `a' and then return + successfully. Ideally, we wouldn't have to do that, and we'd be + able to rely on rename to remove the source file. However, POSIX + mistakenly requires that such a rename call do *nothing* and return + successfully. */ + +static bool +same_file_ok (char const *src_name, struct stat const *src_sb, + char const *dst_name, struct stat const *dst_sb, + const struct cp_options *x, bool *return_now, bool *unlink_src) +{ + const struct stat *src_sb_link; + const struct stat *dst_sb_link; + struct stat tmp_dst_sb; + struct stat tmp_src_sb; + + bool same_link; + bool same = SAME_INODE (*src_sb, *dst_sb); + + *return_now = false; + *unlink_src = false; + + /* FIXME: this should (at the very least) be moved into the following + if-block. More likely, it should be removed, because it inhibits + making backups. But removing it will result in a change in behavior + that will probably have to be documented -- and tests will have to + be updated. */ + if (same && x->hard_link) + { + *return_now = true; + return true; + } + + if (x->dereference == DEREF_NEVER) + { + same_link = same; + + /* If both the source and destination files are symlinks (and we'll + know this here IFF preserving symlinks), then it's ok -- as long + as they are distinct. */ + if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode)) + return ! same_name (src_name, dst_name); + + src_sb_link = src_sb; + dst_sb_link = dst_sb; + } + else + { + if (!same) + return true; + + if (lstat (dst_name, &tmp_dst_sb) != 0 + || lstat (src_name, &tmp_src_sb) != 0) + return true; + + src_sb_link = &tmp_src_sb; + dst_sb_link = &tmp_dst_sb; + + same_link = SAME_INODE (*src_sb_link, *dst_sb_link); + + /* If both are symlinks, then it's ok, but only if the destination + will be unlinked before being opened. This is like the test + above, but with the addition of the unlink_dest_before_opening + conjunct because otherwise, with two symlinks to the same target, + we'd end up truncating the source file. */ + if (S_ISLNK (src_sb_link->st_mode) && S_ISLNK (dst_sb_link->st_mode) + && x->unlink_dest_before_opening) + return true; + } + + /* The backup code ensures there's a copy, so it's usually ok to + remove any destination file. One exception is when both + source and destination are the same directory entry. In that + case, moving the destination file aside (in making the backup) + would also rename the source file and result in an error. */ + if (x->backup_type != no_backups) + { + if (!same_link) + { + /* In copy mode when dereferencing symlinks, if the source is a + symlink and the dest is not, then backing up the destination + (moving it aside) would make it a dangling symlink, and the + subsequent attempt to open it in copy_reg would fail with + a misleading diagnostic. Avoid that by returning zero in + that case so the caller can make cp (or mv when it has to + resort to reading the source file) fail now. */ + + /* FIXME-note: even with the following kludge, we can still provoke + the offending diagnostic. It's just a little harder to do :-) + $ rm -f a b c; touch c; ln -s c b; ln -s b a; cp -b a b + cp: cannot open `a' for reading: No such file or directory + That's misleading, since a subsequent `ls' shows that `a' + is still there. + One solution would be to open the source file *before* moving + aside the destination, but that'd involve a big rewrite. */ + if ( ! x->move_mode + && x->dereference != DEREF_NEVER + && S_ISLNK (src_sb_link->st_mode) + && ! S_ISLNK (dst_sb_link->st_mode)) + return false; + + return true; + } + + return ! same_name (src_name, dst_name); + } + +#if 0 + /* FIXME: use or remove */ + + /* If we're making a backup, we'll detect the problem case in + copy_reg because SRC_NAME will no longer exist. Allowing + the test to be deferred lets cp do some useful things. + But when creating hardlinks and SRC_NAME is a symlink + but DST_NAME is not we must test anyway. */ + if (x->hard_link + || !S_ISLNK (src_sb_link->st_mode) + || S_ISLNK (dst_sb_link->st_mode)) + return true; + + if (x->dereference != DEREF_NEVER) + return true; +#endif + + /* They may refer to the same file if we're in move mode and the + target is a symlink. That is ok, since we remove any existing + destination file before opening it -- via `rename' if they're on + the same file system, via `unlink (DST_NAME)' otherwise. + It's also ok if they're distinct hard links to the same file. */ + if (x->move_mode || x->unlink_dest_before_opening) + { + if (S_ISLNK (dst_sb_link->st_mode)) + return true; + + if (same_link + && 1 < dst_sb_link->st_nlink + && ! same_name (src_name, dst_name)) + { + if (x->move_mode) + { + *unlink_src = true; + *return_now = true; + } + return true; + } + } + + /* If neither is a symlink, then it's ok as long as they aren't + hard links to the same file. */ + if (!S_ISLNK (src_sb_link->st_mode) && !S_ISLNK (dst_sb_link->st_mode)) + { + if (!SAME_INODE (*src_sb_link, *dst_sb_link)) + return true; + + /* If they are the same file, it's ok if we're making hard links. */ + if (x->hard_link) + { + *return_now = true; + return true; + } + } + + /* It's ok to remove a destination symlink. But that works only when we + unlink before opening the destination and when the source and destination + files are on the same partition. */ + if (x->unlink_dest_before_opening + && S_ISLNK (dst_sb_link->st_mode)) + return dst_sb_link->st_dev == src_sb_link->st_dev; + + if (x->dereference == DEREF_NEVER) + { + if ( ! S_ISLNK (src_sb_link->st_mode)) + tmp_src_sb = *src_sb_link; + else if (stat (src_name, &tmp_src_sb) != 0) + return true; + + if ( ! S_ISLNK (dst_sb_link->st_mode)) + tmp_dst_sb = *dst_sb_link; + else if (stat (dst_name, &tmp_dst_sb) != 0) + return true; + + if ( ! SAME_INODE (tmp_src_sb, tmp_dst_sb)) + return true; + + /* FIXME: shouldn't this be testing whether we're making symlinks? */ + if (x->hard_link) + { + *return_now = true; + return true; + } + } + + return false; +} + +static void +overwrite_prompt (char const *dst_name, struct stat const *dst_sb) +{ + if (euidaccess (dst_name, W_OK) != 0) + { + char perms[12]; /* "-rwxrwxrwx " ls-style modes. */ + strmode (dst_sb->st_mode, perms); + perms[10] = '\0'; + fprintf (stderr, + _("%s: try to overwrite %s, overriding mode %04lo (%s)? "), + program_name, quote (dst_name), + (unsigned long int) (dst_sb->st_mode & CHMOD_MODE_BITS), + &perms[1]); + } + else + { + fprintf (stderr, _("%s: overwrite %s? "), + program_name, quote (dst_name)); + } +} + +/* Hash an F_triple. */ +static size_t +triple_hash (void const *x, size_t table_size) +{ + struct F_triple const *p = x; + + /* Also take the name into account, so that when moving N hard links to the + same file (all listed on the command line) all into the same directory, + we don't experience any N^2 behavior. */ + /* FIXME-maybe: is it worth the overhead of doing this + just to avoid N^2 in such an unusual case? N would have + to be very large to make the N^2 factor noticable, and + one would probably encounter a limit on the length of + a command line before it became a problem. */ + size_t tmp = hash_pjw (p->name, table_size); + + /* Ignoring the device number here should be fine. */ + return (tmp | p->st_ino) % table_size; +} + +/* Hash an F_triple. */ +static size_t +triple_hash_no_name (void const *x, size_t table_size) +{ + struct F_triple const *p = x; + + /* Ignoring the device number here should be fine. */ + return p->st_ino % table_size; +} + +/* Compare two F_triple structs. */ +static bool +triple_compare (void const *x, void const *y) +{ + struct F_triple const *a = x; + struct F_triple const *b = y; + return (SAME_INODE (*a, *b) && same_name (a->name, b->name)) ? true : false; +} + +/* Free an F_triple. */ +static void +triple_free (void *x) +{ + struct F_triple *a = x; + free (a->name); + free (a); +} + +/* Initialize the hash table implementing a set of F_triple entries + corresponding to destination files. */ +extern void +dest_info_init (struct cp_options *x) +{ + x->dest_info + = hash_initialize (DEST_INFO_INITIAL_CAPACITY, + NULL, + triple_hash, + triple_compare, + triple_free); +} + +/* Initialize the hash table implementing a set of F_triple entries + corresponding to source files listed on the command line. */ +extern void +src_info_init (struct cp_options *x) +{ + + /* Note that we use triple_hash_no_name here. + Contrast with the use of triple_hash above. + That is necessary because a source file may be specified + in many different ways. We want to warn about this + cp a a d/ + as well as this: + cp a ./a d/ + */ + x->src_info + = hash_initialize (DEST_INFO_INITIAL_CAPACITY, + NULL, + triple_hash_no_name, + triple_compare, + triple_free); +} + +/* Return true if there is an entry in hash table, HT, + for the file described by FILE and STATS. */ +static bool +seen_file (Hash_table const *ht, char const *file, + struct stat const *stats) +{ + struct F_triple new_ent; + + if (ht == NULL) + return false; + + new_ent.name = (char *) file; + new_ent.st_ino = stats->st_ino; + new_ent.st_dev = stats->st_dev; + + return !!hash_lookup (ht, &new_ent); +} + +/* Record destination file, FILE, and dev/ino from *STATS, + in the hash table, HT. If HT is NULL, return immediately. + If STATS is NULL, call lstat on FILE to get the device + and inode numbers. If that lstat fails, simply return. + If memory allocation fails, exit immediately. */ +static void +record_file (Hash_table *ht, char const *file, + struct stat const *stats) +{ + struct F_triple *ent; + + if (ht == NULL) + return; + + ent = xmalloc (sizeof *ent); + ent->name = xstrdup (file); + if (stats) + { + ent->st_ino = stats->st_ino; + ent->st_dev = stats->st_dev; + } + else + { + struct stat sb; + if (lstat (file, &sb) != 0) + return; + ent->st_ino = sb.st_ino; + ent->st_dev = sb.st_dev; + } + + { + struct F_triple *ent_from_table = hash_insert (ht, ent); + if (ent_from_table == NULL) + { + /* Insertion failed due to lack of memory. */ + xalloc_die (); + } + + if (ent_from_table != ent) + { + /* There was alread a matching entry in the table, so ENT was + not inserted. Free it. */ + triple_free (ent); + } + } +} + +/* When effecting a move (e.g., for mv(1)), and given the name DST_NAME + of the destination and a corresponding stat buffer, DST_SB, return + true if the logical `move' operation should _not_ proceed. + Otherwise, return false. + Depending on options specified in X, this code may issue an + interactive prompt asking whether it's ok to overwrite DST_NAME. */ +static bool +abandon_move (const struct cp_options *x, + char const *dst_name, + struct stat const *dst_sb) +{ + assert (x->move_mode); + return (x->interactive == I_ALWAYS_NO + || ((x->interactive == I_ASK_USER + || (x->interactive == I_UNSPECIFIED + && x->stdin_tty + && UNWRITABLE (dst_name, dst_sb->st_mode))) + && (overwrite_prompt (dst_name, dst_sb), 1) + && ! yesno ())); +} + +/* Print --verbose output on standard output, e.g. `new' -> `old'. + If BACKUP_DST_NAME is non-NULL, then also indicate that it is + the name of a backup file. */ +static void +emit_verbose (char const *src, char const *dst, char const *backup_dst_name) +{ + printf ("%s -> %s", quote_n (0, src), quote_n (1, dst)); + if (backup_dst_name) + printf (_(" (backup: %s)"), quote (backup_dst_name)); + putchar ('\n'); +} + +/* Copy the file SRC_NAME to the file DST_NAME. The files may be of + any type. NEW_DST should be true if the file DST_NAME cannot + exist because its parent directory was just created; NEW_DST should + be false if DST_NAME 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_NAME. COMMAND_LINE_ARG + is true iff SRC_NAME was specified on the command line. + Set *COPY_INTO_SELF if SRC_NAME is a parent of (or the + same as) DST_NAME; otherwise, clear it. + Return true if successful. */ +static bool +copy_internal (char const *src_name, char const *dst_name, + bool new_dst, + dev_t device, + struct dir_list *ancestors, + const struct cp_options *x, + bool command_line_arg, + bool *copy_into_self, + bool *rename_succeeded) +{ + struct stat src_sb; + struct stat dst_sb; + mode_t src_mode; + mode_t dst_mode IF_LINT (= 0); + mode_t dst_mode_bits; + mode_t omitted_permissions; + bool restore_dst_mode = false; + char *earlier_file = NULL; + char *dst_backup = NULL; + bool backup_succeeded = false; + bool delayed_ok; + bool copied_as_regular = false; + bool preserve_metadata; + + if (x->move_mode && rename_succeeded) + *rename_succeeded = false; + + *copy_into_self = false; + + if (XSTAT (x, src_name, &src_sb) != 0) + { + error (0, errno, _("cannot stat %s"), quote (src_name)); + return false; + } + + src_mode = src_sb.st_mode; + + if (S_ISDIR (src_mode) && !x->recursive) + { + error (0, 0, _("omitting directory %s"), quote (src_name)); + return false; + } + + /* Detect the case in which the same source file appears more than + once on the command line and no backup option has been selected. + If so, simply warn and don't copy it the second time. + This check is enabled only if x->src_info is non-NULL. */ + if (command_line_arg) + { + if ( ! S_ISDIR (src_sb.st_mode) + && x->backup_type == no_backups + && seen_file (x->src_info, src_name, &src_sb)) + { + error (0, 0, _("warning: source file %s specified more than once"), + quote (src_name)); + return true; + } + + record_file (x->src_info, src_name, &src_sb); + } + + if (!new_dst) + { + if (XSTAT (x, dst_name, &dst_sb) != 0) + { + if (errno != ENOENT) + { + error (0, errno, _("cannot stat %s"), quote (dst_name)); + return false; + } + else + { + new_dst = true; + } + } + else + { /* Here, we know that dst_name exists, at least to the point + that it is XSTAT'able. */ + bool return_now; + bool unlink_src; + + if (! same_file_ok (src_name, &src_sb, dst_name, &dst_sb, + x, &return_now, &unlink_src)) + { + error (0, 0, _("%s and %s are the same file"), + quote_n (0, src_name), quote_n (1, dst_name)); + return false; + } + + /* When there is an existing destination file, we may end up + returning early, and hence not copying/moving the file. + This may be due to an interactive `negative' reply to the + prompt about the existing file. It may also be due to the + use of the --reply=no option. + + cp and mv treat -i and -f differently. */ + if (x->move_mode) + { + if (abandon_move (x, dst_name, &dst_sb) + || (unlink_src && unlink (src_name) == 0)) + { + /* Pretend the rename succeeded, so the caller (mv) + doesn't end up removing the source file. */ + if (rename_succeeded) + *rename_succeeded = true; + if (unlink_src && x->verbose) + printf (_("removed %s\n"), quote (src_name)); + return true; + } + if (unlink_src) + { + error (0, errno, _("cannot remove %s"), quote (src_name)); + return false; + } + } + else + { + if (! S_ISDIR (src_mode) + && (x->interactive == I_ALWAYS_NO + || (x->interactive == I_ASK_USER + && (overwrite_prompt (dst_name, &dst_sb), 1) + && ! yesno ()))) + return true; + } + + if (return_now) + return true; + + if (!S_ISDIR (dst_sb.st_mode)) + { + if (S_ISDIR (src_mode)) + { + if (x->move_mode && x->backup_type != no_backups) + { + /* Moving a directory onto an existing + non-directory is ok only with --backup. */ + } + else + { + error (0, 0, + _("cannot overwrite non-directory %s with directory %s"), + quote_n (0, dst_name), quote_n (1, src_name)); + return false; + } + } + + /* Don't let the user destroy their data, even if they try hard: + This mv command must fail (likewise for cp): + rm -rf a b c; mkdir a b c; touch a/f b/f; mv a/f b/f c + Otherwise, the contents of b/f would be lost. + In the case of `cp', b/f would be lost if the user simulated + a move using cp and rm. + Note that it works fine if you use --backup=numbered. */ + if (command_line_arg + && x->backup_type != numbered_backups + && seen_file (x->dest_info, dst_name, &dst_sb)) + { + error (0, 0, + _("will not overwrite just-created %s with %s"), + quote_n (0, dst_name), quote_n (1, src_name)); + return false; + } + } + + if (!S_ISDIR (src_mode)) + { + if (S_ISDIR (dst_sb.st_mode)) + { + if (x->move_mode && x->backup_type != no_backups) + { + /* Moving a non-directory onto an existing + directory is ok only with --backup. */ + } + else + { + error (0, 0, + _("cannot overwrite directory %s with non-directory"), + quote (dst_name)); + return false; + } + } + + if (x->update) + { + /* When preserving time stamps (but not moving within a file + system), don't worry if the destination time stamp is + less than the source merely because of time stamp + truncation. */ + int options = ((x->preserve_timestamps + && ! (x->move_mode + && dst_sb.st_dev == src_sb.st_dev)) + ? UTIMECMP_TRUNCATE_SOURCE + : 0); + + if (0 <= utimecmp (dst_name, &dst_sb, &src_sb, options)) + { + /* We're using --update and the destination is not older + than the source, so do not copy or move. Pretend the + rename succeeded, so the caller (if it's mv) doesn't + end up removing the source file. */ + if (rename_succeeded) + *rename_succeeded = true; + return true; + } + } + } + + if (x->move_mode) + { + /* Don't allow user to move a directory onto a non-directory. */ + if (S_ISDIR (src_sb.st_mode) && !S_ISDIR (dst_sb.st_mode) + && x->backup_type == no_backups) + { + error (0, 0, + _("cannot move directory onto non-directory: %s -> %s"), + quote_n (0, src_name), quote_n (0, dst_name)); + return false; + } + } + + if (x->backup_type != no_backups + /* Don't try to back up a destination if the last + component of src_name is "." or "..". */ + && ! dot_or_dotdot (last_component (src_name)) + /* Create a backup of each destination directory in move mode, + but not in copy mode. FIXME: it might make sense to add an + option to suppress backup creation also for move mode. + That would let one use mv to merge new content into an + existing hierarchy. */ + && (x->move_mode || ! S_ISDIR (dst_sb.st_mode))) + { + char *tmp_backup = find_backup_file_name (dst_name, + x->backup_type); + + /* Detect (and fail) when creating the backup file would + destroy the source file. Before, running the commands + cd /tmp; rm -f a a~; : > a; echo A > a~; cp --b=simple a~ a + would leave two zero-length files: a and a~. */ + /* FIXME: but simply change e.g., the final a~ to `./a~' + and the source will still be destroyed. */ + if (STREQ (tmp_backup, src_name)) + { + const char *fmt; + fmt = (x->move_mode + ? _("backing up %s would destroy source; %s not moved") + : _("backing up %s would destroy source; %s not copied")); + error (0, 0, fmt, + quote_n (0, dst_name), + quote_n (1, src_name)); + free (tmp_backup); + return false; + } + + /* FIXME: use fts: + Using alloca for a file name that may be arbitrarily + long is not recommended. In fact, even forming such a name + should be discouraged. Eventually, this code will be rewritten + to use fts, so using alloca here will be less of a problem. */ + ASSIGN_STRDUPA (dst_backup, tmp_backup); + free (tmp_backup); + if (rename (dst_name, dst_backup) != 0) + { + if (errno != ENOENT) + { + error (0, errno, _("cannot backup %s"), quote (dst_name)); + return false; + } + else + { + dst_backup = NULL; + } + } + else + { + backup_succeeded = true; + } + new_dst = true; + } + else if (! S_ISDIR (dst_sb.st_mode) + && (x->unlink_dest_before_opening + || (x->preserve_links && 1 < dst_sb.st_nlink) + || (!x->move_mode + && x->dereference == DEREF_NEVER + && S_ISLNK (src_sb.st_mode)) + )) + { + if (unlink (dst_name) != 0 && errno != ENOENT) + { + error (0, errno, _("cannot remove %s"), quote (dst_name)); + return false; + } + new_dst = true; + if (x->verbose) + printf (_("removed %s\n"), quote (dst_name)); + } + } + } + + /* 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 (x->verbose && !S_ISDIR (src_mode)) + emit_verbose (src_name, dst_name, backup_succeeded ? dst_backup : NULL); + + /* Associate the destination file name with the source device and inode + so that if we encounter a matching dev/ino pair in the source tree + we can arrange to create a hard link between the corresponding names + in the destination tree. + + Sometimes, when preserving links, we have to record dev/ino even + though st_nlink == 1: + - when in move_mode, since we may be moving a group of N hard-linked + files (via two or more command line arguments) to a different + partition; the links may be distributed among the command line + arguments (possibly hierarchies) so that the link count of + the final, once-linked source file is reduced to 1 when it is + considered below. But in this case (for mv) we don't need to + incur the expense of recording the dev/ino => name mapping; all we + really need is a lookup, to see if the dev/ino pair has already + been copied. + - when using -H and processing a command line argument; + that command line argument could be a symlink pointing to another + command line argument. With `cp -H --preserve=link', we hard-link + those two destination files. + - likewise for -L except that it applies to all files, not just + command line arguments. + + Also record directory dev/ino when using --recursive. We'll use that + info to detect this problem: cp -R dir dir. FIXME-maybe: ideally, + directory info would be recorded in a separate hash table, since + such entries are useful only while a single command line hierarchy + is being copied -- so that separate table could be cleared between + command line args. Using the same hash table to preserve hard + links means that it may not be cleared. */ + + if (x->move_mode && src_sb.st_nlink == 1) + { + earlier_file = src_to_dest_lookup (src_sb.st_ino, src_sb.st_dev); + } + else if ((x->preserve_links + && (1 < src_sb.st_nlink + || (command_line_arg + && x->dereference == DEREF_COMMAND_LINE_ARGUMENTS) + || x->dereference == DEREF_ALWAYS)) + || (x->recursive && S_ISDIR (src_mode))) + { + earlier_file = remember_copied (dst_name, src_sb.st_ino, src_sb.st_dev); + } + + /* Did we copy this inode somewhere else (in this command line argument) + and therefore this is a second hard link to the inode? */ + + if (earlier_file) + { + /* Avoid damaging the destination file system by refusing to preserve + hard-linked directories (which are found at least in Netapp snapshot + directories). */ + if (S_ISDIR (src_mode)) + { + /* If src_name and earlier_file refer to the same directory entry, + then warn about copying a directory into itself. */ + if (same_name (src_name, earlier_file)) + { + error (0, 0, _("cannot copy a directory, %s, into itself, %s"), + quote_n (0, top_level_src_name), + quote_n (1, top_level_dst_name)); + *copy_into_self = true; + goto un_backup; + } + else if (x->dereference == DEREF_ALWAYS) + { + /* This happens when e.g., encountering a directory for the + second or subsequent time via symlinks when cp is invoked + with -R and -L. E.g., + rm -rf a b c d; mkdir a b c d; ln -s ../c a; ln -s ../c b; + cp -RL a b d + */ + } + else + { + error (0, 0, _("will not create hard link %s to directory %s"), + quote_n (0, dst_name), quote_n (1, earlier_file)); + goto un_backup; + } + } + else + { + bool link_failed = (link (earlier_file, dst_name) != 0); + + /* If the link failed because of an existing destination, + remove that file and then call link again. */ + if (link_failed && errno == EEXIST) + { + if (unlink (dst_name) != 0) + { + error (0, errno, _("cannot remove %s"), quote (dst_name)); + goto un_backup; + } + if (x->verbose) + printf (_("removed %s\n"), quote (dst_name)); + link_failed = (link (earlier_file, dst_name) != 0); + } + + if (link_failed) + { + error (0, errno, _("cannot create hard link %s to %s"), + quote_n (0, dst_name), quote_n (1, earlier_file)); + goto un_backup; + } + + return true; + } + } + + if (x->move_mode) + { + if (rename (src_name, dst_name) == 0) + { + if (x->verbose && S_ISDIR (src_mode)) + emit_verbose (src_name, dst_name, + backup_succeeded ? dst_backup : NULL); + + if (rename_succeeded) + *rename_succeeded = true; + + if (command_line_arg) + { + /* Record destination dev/ino/name, so that if we are asked + to overwrite that file again, we can detect it and fail. */ + /* It's fine to use the _source_ stat buffer (src_sb) to get the + _destination_ dev/ino, since the rename above can't have + changed those, and `mv' always uses lstat. + We could limit it further by operating + only on non-directories. */ + record_file (x->dest_info, dst_name, &src_sb); + } + + return true; + } + + /* FIXME: someday, consider what to do when moving a directory into + itself but when source and destination are on different devices. */ + + /* This happens when attempting to rename a directory to a + subdirectory of itself. */ + if (errno == EINVAL) + { + /* FIXME: this is a little fragile in that it relies on rename(2) + failing with a specific errno value. Expect problems on + non-POSIX systems. */ + error (0, 0, _("cannot move %s to a subdirectory of itself, %s"), + quote_n (0, top_level_src_name), + quote_n (1, top_level_dst_name)); + + /* Note that there is no need to call forget_created here, + (compare with the other calls in this file) since the + destination directory didn't exist before. */ + + *copy_into_self = true; + /* FIXME-cleanup: Don't return true here; adjust mv.c accordingly. + The only caller that uses this code (mv.c) ends up setting its + exit status to nonzero when copy_into_self is nonzero. */ + return true; + } + + /* WARNING: there probably exist systems for which an inter-device + rename fails with a value of errno not handled here. + If/as those are reported, add them to the condition below. + If this happens to you, please do the following and send the output + to the bug-reporting address (e.g., in the output of cp --help): + touch k; perl -e 'rename "k","/tmp/k" or print "$!(",$!+0,")\n"' + where your current directory is on one partion and /tmp is the other. + Also, please try to find the E* errno macro name corresponding to + the diagnostic and parenthesized integer, and include that in your + e-mail. One way to do that is to run a command like this + find /usr/include/. -type f \ + | xargs grep 'define.*\.*\<18\>' /dev/null + where you'd replace `18' with the integer in parentheses that + was output from the perl one-liner above. + If necessary, of course, change `/tmp' to some other directory. */ + if (errno != EXDEV) + { + /* There are many ways this can happen due to a race condition. + When something happens between the initial XSTAT and the + subsequent rename, we can get many different types of errors. + For example, if the destination is initially a non-directory + or non-existent, but it is created as a directory, the rename + fails. If two `mv' commands try to rename the same file at + about the same time, one will succeed and the other will fail. + If the permissions on the directory containing the source or + destination file are made too restrictive, the rename will + fail. Etc. */ + error (0, errno, + _("cannot move %s to %s"), + quote_n (0, src_name), quote_n (1, dst_name)); + forget_created (src_sb.st_ino, src_sb.st_dev); + return false; + } + + /* The rename attempt has failed. Remove any existing destination + file so that a cross-device `mv' acts as if it were really using + the rename syscall. */ + if (unlink (dst_name) != 0 && errno != ENOENT) + { + error (0, errno, + _("inter-device move failed: %s to %s; unable to remove target"), + quote_n (0, src_name), quote_n (1, dst_name)); + forget_created (src_sb.st_ino, src_sb.st_dev); + return false; + } + + new_dst = true; + } + + /* If the ownership might change, or if it is a directory (whose + special mode bits may change after the directory is created), + omit some permissions at first, so unauthorized users cannot nip + in before the file is ready. */ + dst_mode_bits = (x->set_mode ? x->mode : src_mode) & CHMOD_MODE_BITS; + omitted_permissions = + (dst_mode_bits + & (x->preserve_ownership ? S_IRWXG | S_IRWXO + : S_ISDIR (src_mode) ? S_IWGRP | S_IWOTH + : 0)); + + delayed_ok = true; + + /* In certain modes (cp's --symbolic-link), and for certain file types + (symlinks and hard links) it doesn't make sense to preserve metadata, + or it's possible to preserve only some of it. + In such cases, set this variable to zero. */ + preserve_metadata = true; + + if (S_ISDIR (src_mode)) + { + 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, _("cannot copy cyclic symbolic link %s"), + quote (src_name)); + goto un_backup; + } + + /* Insert the current directory in the list of parents. */ + + dir = alloca (sizeof *dir); + dir->parent = ancestors; + dir->ino = src_sb.st_ino; + dir->dev = src_sb.st_dev; + + if (new_dst || !S_ISDIR (dst_sb.st_mode)) + { + /* POSIX says mkdir's behavior is implementation-defined when + (src_mode & ~S_IRWXUGO) != 0. However, common practice is + to ask mkdir to copy all the CHMOD_MODE_BITS, letting mkdir + decide what to do with S_ISUID | S_ISGID | S_ISVTX. */ + if (mkdir (dst_name, dst_mode_bits & ~omitted_permissions) != 0) + { + error (0, errno, _("cannot create directory %s"), + quote (dst_name)); + goto un_backup; + } + + /* We need search and write permissions to the new directory + for writing the directory's contents. Check if these + permissions are there. */ + + if (lstat (dst_name, &dst_sb) != 0) + { + error (0, errno, _("cannot stat %s"), quote (dst_name)); + goto un_backup; + } + else if ((dst_sb.st_mode & S_IRWXU) != S_IRWXU) + { + /* Make the new directory searchable and writable. */ + + dst_mode = dst_sb.st_mode; + restore_dst_mode = true; + + if (lchmod (dst_name, dst_mode | S_IRWXU) != 0) + { + error (0, errno, _("setting permissions for %s"), + quote (dst_name)); + goto un_backup; + } + } + + /* Insert the created directory's inode and device + numbers into the search structure, so that we can + avoid copying it again. */ + + remember_copied (dst_name, dst_sb.st_ino, dst_sb.st_dev); + + if (x->verbose) + emit_verbose (src_name, dst_name, NULL); + } + + /* Decide whether to copy the contents of the directory. */ + if (x->one_file_system && device != 0 && device != src_sb.st_dev) + { + /* Here, we are crossing a file system boundary and cp's -x option + is in effect: so don't copy the contents of this directory. */ + } + else + { + /* Copy the contents of the directory. Don't just return if + this fails -- otherwise, the failure to read a single file + in a source directory would cause the containing destination + directory not to have owner/perms set properly. */ + delayed_ok = copy_dir (src_name, dst_name, new_dst, &src_sb, dir, x, + copy_into_self); + } + } + else if (x->symbolic_link) + { + preserve_metadata = false; + + if (*src_name != '/') + { + /* Check that DST_NAME denotes a file in the current directory. */ + struct stat dot_sb; + struct stat dst_parent_sb; + char *dst_parent; + bool in_current_dir; + + dst_parent = dir_name (dst_name); + + in_current_dir = (STREQ (".", dst_parent) + /* If either stat call fails, it's ok not to report + the failure and say dst_name is in the current + directory. Other things will fail later. */ + || stat (".", &dot_sb) != 0 + || stat (dst_parent, &dst_parent_sb) != 0 + || SAME_INODE (dot_sb, dst_parent_sb)); + free (dst_parent); + + if (! in_current_dir) + { + error (0, 0, + _("%s: can make relative symbolic links only in current directory"), + quote (dst_name)); + goto un_backup; + } + } + if (symlink (src_name, dst_name) != 0) + { + error (0, errno, _("cannot create symbolic link %s to %s"), + quote_n (0, dst_name), quote_n (1, src_name)); + goto un_backup; + } + } + + else if (x->hard_link +#ifdef LINK_FOLLOWS_SYMLINKS + /* A POSIX-conforming link syscall dereferences a symlink, yet cp, + invoked with `--link --no-dereference', should not. Thus, with + a POSIX-conforming link system call, we can't use link() here, + since that would create a hard link to the referent (effectively + dereferencing the symlink), rather than to the symlink itself. + We can approximate the desired behavior by skipping this hard-link + creating block and instead copying the symlink, via the `S_ISLNK'- + copying code below. + When link operates on the symlinks themselves, we use this block + and just call link(). */ + && !(S_ISLNK (src_mode) && x->dereference == DEREF_NEVER) +#endif + ) + { + preserve_metadata = false; + if (link (src_name, dst_name)) + { + error (0, errno, _("cannot create link %s"), quote (dst_name)); + goto un_backup; + } + } + else if (S_ISREG (src_mode) + || (x->copy_as_regular && !S_ISLNK (src_mode))) + { + copied_as_regular = true; + /* POSIX says the permission bits of the source file must be + used as the 3rd argument in the open call. Historical + practice passed all the source mode bits to 'open', but the extra + bits were ignored, so it should be the same either way. */ + if (! copy_reg (src_name, dst_name, x, src_mode & S_IRWXUGO, + omitted_permissions, &new_dst, &src_sb)) + goto un_backup; + } + else if (S_ISFIFO (src_mode)) + { + /* Use mknod, rather than mkfifo, because the former preserves + the special mode bits of a fifo on Solaris 10, while mkfifo + does not. But fall back on mkfifo, because on some BSD systems, + mknod always fails when asked to create a FIFO. */ + if (mknod (dst_name, src_mode & ~omitted_permissions, 0) != 0) +#if HAVE_MKFIFO + if (mkfifo (dst_name, src_mode & ~S_IFIFO & ~omitted_permissions) != 0) +#endif + { + error (0, errno, _("cannot create fifo %s"), quote (dst_name)); + goto un_backup; + } + } + else if (S_ISBLK (src_mode) || S_ISCHR (src_mode) || S_ISSOCK (src_mode)) + { + if (mknod (dst_name, src_mode & ~omitted_permissions, src_sb.st_rdev) + != 0) + { + error (0, errno, _("cannot create special file %s"), + quote (dst_name)); + goto un_backup; + } + } + else if (S_ISLNK (src_mode)) + { + char *src_link_val = xreadlink_with_size (src_name, src_sb.st_size); + if (src_link_val == NULL) + { + error (0, errno, _("cannot read symbolic link %s"), quote (src_name)); + goto un_backup; + } + + if (symlink (src_link_val, dst_name) == 0) + free (src_link_val); + else + { + int saved_errno = errno; + bool same_link = false; + if (x->update && !new_dst && S_ISLNK (dst_sb.st_mode) + && dst_sb.st_size == strlen (src_link_val)) + { + /* See if the destination is already the desired symlink. + FIXME: This behavior isn't documented, and seems wrong + in some cases, e.g., if the destination symlink has the + wrong ownership, permissions, or time stamps. */ + char *dest_link_val = + xreadlink_with_size (dst_name, dst_sb.st_size); + if (STREQ (dest_link_val, src_link_val)) + same_link = true; + free (dest_link_val); + } + free (src_link_val); + + if (! same_link) + { + error (0, saved_errno, _("cannot create symbolic link %s"), + quote (dst_name)); + goto un_backup; + } + } + + /* There's no need to preserve timestamps or permissions. */ + preserve_metadata = false; + + if (x->preserve_ownership) + { + /* Preserve the owner and group of the just-`copied' + symbolic link, if possible. */ +#if HAVE_LCHOWN + if (lchown (dst_name, src_sb.st_uid, src_sb.st_gid) != 0 + && ! chown_failure_ok (x)) + { + error (0, errno, _("failed to preserve ownership for %s"), + dst_name); + goto un_backup; + } +#else + /* Can't preserve ownership of symlinks. + FIXME: maybe give a warning or even error for symlinks + in directories with the sticky bit set -- there, not + preserving owner/group is a potential security problem. */ +#endif + } + } + else + { + error (0, 0, _("%s has unknown file type"), quote (src_name)); + goto un_backup; + } + + if (command_line_arg) + record_file (x->dest_info, dst_name, NULL); + + if ( ! preserve_metadata) + return true; + + if (copied_as_regular) + return delayed_ok; + + /* POSIX says that `cp -p' must restore the following: + - permission bits + - setuid, setgid bits + - owner and group + If it fails to restore any of those, we may give a warning but + the destination must not be removed. + FIXME: implement the above. */ + + /* 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 (x->preserve_timestamps) + { + struct timespec timespec[2]; + timespec[0] = get_stat_atime (&src_sb); + timespec[1] = get_stat_mtime (&src_sb); + + if (utimens (dst_name, timespec) != 0) + { + error (0, errno, _("preserving times for %s"), quote (dst_name)); + if (x->require_preserve) + return false; + } + } + + /* Avoid calling chown if we know it's not necessary. */ + if (x->preserve_ownership + && (new_dst || !SAME_OWNER_AND_GROUP (src_sb, dst_sb))) + { + switch (set_owner (x, dst_name, -1, src_sb.st_uid, src_sb.st_gid)) + { + case -1: + return false; + + case 0: + src_mode &= ~ (S_ISUID | S_ISGID | S_ISVTX); + break; + } + } + + set_author (dst_name, -1, &src_sb); + + if (x->preserve_mode || x->move_mode) + { + if (copy_acl (src_name, -1, dst_name, -1, src_mode) != 0 + && x->require_preserve) + return false; + } + else if (x->set_mode) + { + if (set_acl (dst_name, -1, x->mode) != 0) + return false; + } + else + { + if (omitted_permissions) + { + omitted_permissions &= ~ cached_umask (); + + if (omitted_permissions && !restore_dst_mode) + { + /* Permissions were deliberately omitted when the file + was created due to security concerns. See whether + they need to be re-added now. It'd be faster to omit + the lstat, but deducing the current destination mode + is tricky in the presence of implementation-defined + rules for special mode bits. */ + if (new_dst && lstat (dst_name, &dst_sb) != 0) + { + error (0, errno, _("cannot stat %s"), quote (dst_name)); + return false; + } + dst_mode = dst_sb.st_mode; + if (omitted_permissions & ~dst_mode) + restore_dst_mode = true; + } + } + + if (restore_dst_mode) + { + if (lchmod (dst_name, dst_mode | omitted_permissions) != 0) + { + error (0, errno, _("preserving permissions for %s"), + quote (dst_name)); + if (x->require_preserve) + return false; + } + } + } + + return delayed_ok; + +un_backup: + + /* We have failed to create the destination file. + If we've just added a dev/ino entry via the remember_copied + call above (i.e., unless we've just failed to create a hard link), + remove the entry associating the source dev/ino with the + destination file name, so we don't try to `preserve' a link + to a file we didn't create. */ + if (earlier_file == NULL) + forget_created (src_sb.st_ino, src_sb.st_dev); + + if (dst_backup) + { + if (rename (dst_backup, dst_name) != 0) + error (0, errno, _("cannot un-backup %s"), quote (dst_name)); + else + { + if (x->verbose) + printf (_("%s -> %s (unbackup)\n"), + quote_n (0, dst_backup), quote_n (1, dst_name)); + } + } + return false; +} + +static bool +valid_options (const struct cp_options *co) +{ + assert (co != NULL); + assert (VALID_BACKUP_TYPE (co->backup_type)); + assert (VALID_SPARSE_MODE (co->sparse_mode)); + assert (!(co->hard_link && co->symbolic_link)); + return true; +} + +/* Copy the file SRC_NAME to the file DST_NAME. The files may be of + any type. NONEXISTENT_DST should be true if the file DST_NAME + is known not to exist (e.g., because its parent directory was just + created); NONEXISTENT_DST should be false if DST_NAME might already + exist. OPTIONS is ... FIXME-describe + Set *COPY_INTO_SELF if SRC_NAME is a parent of (or the + same as) DST_NAME; otherwise, set clear it. + Return true if successful. */ + +extern bool +copy (char const *src_name, char const *dst_name, + bool nonexistent_dst, const struct cp_options *options, + bool *copy_into_self, bool *rename_succeeded) +{ + assert (valid_options (options)); + + /* Record the file names: they're used in case of error, when copying + a directory into itself. I don't like to make these tools do *any* + extra work in the common case when that work is solely to handle + exceptional cases, but in this case, I don't see a way to derive the + top level source and destination directory names where they're used. + An alternative is to use COPY_INTO_SELF and print the diagnostic + from every caller -- but I don't want to do that. */ + top_level_src_name = src_name; + top_level_dst_name = dst_name; + + return copy_internal (src_name, dst_name, nonexistent_dst, 0, NULL, + options, true, copy_into_self, rename_succeeded); +} + +/* Return true if this process has appropriate privileges to chown a + file whose owner is not the effective user ID. */ + +extern bool +chown_privileges (void) +{ +#ifdef PRIV_FILE_CHOWN + bool result; + priv_set_t *pset = priv_allocset (); + if (!pset) + xalloc_die (); + result = (getppriv (PRIV_EFFECTIVE, pset) == 0 + && priv_ismember (pset, PRIV_FILE_CHOWN)); + priv_freeset (pset); + return result; +#else + return (geteuid () == 0); +#endif +} + +/* Return true if it's OK for chown to fail, where errno is + the error number that chown failed with and X is the copying + option set. */ + +extern bool +chown_failure_ok (struct cp_options const *x) +{ + /* 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, + or if the target system doesn't support file ownership. */ + + return ((errno == EPERM || errno == EINVAL) && !x->chown_privileges); +} + +/* Return the user's umask, caching the result. */ + +extern mode_t +cached_umask (void) +{ + static mode_t mask = (mode_t) -1; + if (mask == (mode_t) -1) + { + mask = umask (0); + umask (mask); + } + return mask; +} diff --git a/src/copy.h b/src/copy.h new file mode 100644 index 0000000..c815baf --- /dev/null +++ b/src/copy.h @@ -0,0 +1,218 @@ +/* core functions for copying files and directories + Copyright (C) 89, 90, 91, 1995-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Extracted from cp.c and librarified by Jim Meyering. */ + +#ifndef COPY_H +# define COPY_H + +# include +# include "hash.h" +# include "lstat.h" + +/* Control creation of sparse files (files with holes). */ +enum Sparse_type +{ + SPARSE_UNUSED, + + /* Never create holes in DEST. */ + SPARSE_NEVER, + + /* This is the default. Use a crude (and sometimes inaccurate) + heuristic to determine if SOURCE has holes. If so, try to create + holes in DEST. */ + SPARSE_AUTO, + + /* For every sufficiently long sequence of bytes in SOURCE, try to + create a corresponding hole in DEST. There is a performance penalty + here because CP has to search for holes in SRC. But if the holes are + big enough, that penalty can be offset by the decrease in the amount + of data written to disk. */ + SPARSE_ALWAYS +}; + +/* This type is used to help mv (via copy.c) distinguish these cases. */ +enum Interactive +{ + I_ALWAYS_YES = 1, + I_ALWAYS_NO, + I_ASK_USER, + I_UNSPECIFIED +}; + +/* How to handle symbolic links. */ +enum Dereference_symlink +{ + DEREF_UNDEFINED = 1, + + /* Copy the symbolic link itself. -P */ + DEREF_NEVER, + + /* If the symbolic is a command line argument, then copy + its referent. Otherwise, copy the symbolic link itself. -H */ + DEREF_COMMAND_LINE_ARGUMENTS, + + /* Copy the referent of the symbolic link. -L */ + DEREF_ALWAYS +}; + +# define VALID_SPARSE_MODE(Mode) \ + ((Mode) == SPARSE_NEVER \ + || (Mode) == SPARSE_AUTO \ + || (Mode) == SPARSE_ALWAYS) + +/* These options control how files are copied by at least the + following programs: mv (when rename doesn't work), cp, install. + So, if you add a new member, be sure to initialize it in + mv.c, cp.c, and install.c. */ +struct cp_options +{ + enum backup_type backup_type; + + /* If true, copy all files except (directories and, if not dereferencing + them, symbolic links,) as if they were regular files. */ + bool copy_as_regular; + + /* How to handle symlinks. */ + enum Dereference_symlink dereference; + + /* If true, remove each existing destination nondirectory before + trying to open it. */ + bool unlink_dest_before_opening; + + /* If true, first try to open each existing destination nondirectory, + then, if the open fails, unlink and try again. + This option must be set for `cp -f', in case the destination file + exists when the open is attempted. It is irrelevant to `mv' since + any destination is sure to be removed before the open. */ + bool unlink_dest_after_failed_open; + + /* If true, create hard links instead of copying files. + Create destination directories as usual. */ + bool hard_link; + + /* This value is used to determine whether to prompt before removing + each existing destination file. It works differently depending on + whether move_mode is set. See code/comments in copy.c. */ + enum Interactive interactive; + + /* If true, rather than copying, first attempt to use rename. + If that fails, then resort to copying. */ + bool move_mode; + + /* Whether this process has appropriate privileges to chown a file + whose owner is not the effective user ID. */ + bool chown_privileges; + + /* If true, when copying recursively, skip any subdirectories that are + on different file systems from the one we started on. */ + bool one_file_system; + + /* If true, attempt to give the copies the original files' permissions, + owner, group, and timestamps. */ + bool preserve_ownership; + bool preserve_mode; + bool preserve_timestamps; + + /* Enabled for mv, and for cp by the --preserve=links option. + If true, attempt to preserve in the destination files any + logical hard links between the source files. If used with cp's + --no-dereference option, and copying two hard-linked files, + the two corresponding destination files will also be hard linked. + + If used with cp's --dereference (-L) option, then, as that option implies, + hard links are *not* preserved. However, when copying a file F and + a symlink S to F, the resulting S and F in the destination directory + will be hard links to the same file (a copy of F). */ + bool preserve_links; + + /* If true and any of the above (for preserve) file attributes cannot + be applied to a destination file, treat it as a failure and return + nonzero immediately. E.g. cp -p requires this be nonzero, mv requires + it be zero. */ + bool require_preserve; + + /* If true, copy directories recursively and copy special files + as themselves rather than copying their contents. */ + bool recursive; + + /* If true, set file mode to value of MODE. Otherwise, + set it based on current umask modified by UMASK_KILL. */ + bool set_mode; + + /* Set the mode of the destination file to exactly this value + if SET_MODE is nonzero. */ + mode_t mode; + + /* Control creation of sparse files. */ + enum Sparse_type sparse_mode; + + /* If true, create symbolic links instead of copying files. + Create destination directories as usual. */ + bool symbolic_link; + + /* If true, do not copy a nondirectory that has an existing destination + with the same or newer modification time. */ + bool update; + + /* If true, display the names of the files before copying them. */ + bool verbose; + + /* If true, stdin is a tty. */ + bool stdin_tty; + + /* This is a set of destination name/inode/dev triples. Each such triple + represents a file we have created corresponding to a source file name + that was specified on the command line. Use it to avoid clobbering + source files in commands like this: + rm -rf a b c; mkdir a b c; touch a/f b/f; mv a/f b/f c + For now, it protects only regular files when copying (i.e. not renaming). + When renaming, it protects all non-directories. + Use dest_info_init to initialize it, or set it to NULL to disable + this feature. */ + Hash_table *dest_info; + + /* FIXME */ + Hash_table *src_info; +}; + +# define XSTAT(X, Src_name, Src_sb) \ + ((X)->dereference == DEREF_NEVER \ + ? lstat (Src_name, Src_sb) \ + : stat (Src_name, Src_sb)) + +/* Arrange to make rename calls go through the wrapper function + on systems with a rename function that fails for a source file name + specified with a trailing slash. */ +# if RENAME_TRAILING_SLASH_BUG +int rpl_rename (const char *, const char *); +# undef rename +# define rename rpl_rename +# endif + +bool copy (char const *src_name, char const *dst_name, + bool nonexistent_dst, const struct cp_options *options, + bool *copy_into_self, bool *rename_succeeded); + +void dest_info_init (struct cp_options *); +void src_info_init (struct cp_options *); + +bool chown_privileges (void); +bool chown_failure_ok (struct cp_options const *); +mode_t cached_umask (void); + +#endif diff --git a/src/cp-hash.c b/src/cp-hash.c new file mode 100644 index 0000000..a4b5157 --- /dev/null +++ b/src/cp-hash.c @@ -0,0 +1,187 @@ +/* cp-hash.c -- file copying (hash search routines) + Copyright (C) 89, 90, 91, 1995-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Written by Torbjorn Granlund, Sweden (tege@sics.se). + Rewritten to use lib/hash.c by Jim Meyering. */ + +#include + +#include +#include +#include "system.h" + +#include "same.h" +#include "quote.h" +#include "hash.h" +#include "error.h" +#include "cp-hash.h" + +/* Use ST_DEV and ST_INO as the key, FILENAME as the value. + These are used e.g., in copy.c to associate the destination name with + the source device/inode pair so that if we encounter a matching dev/ino + pair in the source tree we can arrange to create a hard link between + the corresponding names in the destination tree. */ +struct Src_to_dest +{ + ino_t st_ino; + dev_t st_dev; + /* Destination file name (of non-directory or pre-existing directory) + corresponding to the dev/ino of a copied file, or the destination file + name corresponding to a dev/ino pair for a newly-created directory. */ + char *name; +}; + +/* This table maps source dev/ino to destination file name. + We use it to preserve hard links when copying. */ +static Hash_table *src_to_dest; + +/* Initial size of the above hash table. */ +#define INITIAL_TABLE_SIZE 103 + +static size_t +src_to_dest_hash (void const *x, size_t table_size) +{ + struct Src_to_dest const *p = x; + + /* Ignoring the device number here should be fine. */ + /* The cast to uintmax_t prevents negative remainders + if st_ino is negative. */ + return (uintmax_t) p->st_ino % table_size; +} + +/* Compare two Src_to_dest entries. + Return true if their keys are judged `equal'. */ +static bool +src_to_dest_compare (void const *x, void const *y) +{ + struct Src_to_dest const *a = x; + struct Src_to_dest const *b = y; + return SAME_INODE (*a, *b) ? true : false; +} + +static void +src_to_dest_free (void *x) +{ + struct Src_to_dest *a = x; + free (a->name); + free (x); +} + +/* Remove the entry matching INO/DEV from the table + that maps source ino/dev to destination file name. */ +extern void +forget_created (ino_t ino, dev_t dev) +{ + struct Src_to_dest probe; + struct Src_to_dest *ent; + + probe.st_ino = ino; + probe.st_dev = dev; + probe.name = NULL; + + ent = hash_delete (src_to_dest, &probe); + if (ent) + src_to_dest_free (ent); +} + +/* Add FILE to the list of files that we have created. + Return true if successful. */ + +extern bool +remember_created (char const *file) +{ + struct stat sb; + + if (stat (file, &sb) < 0) + { + error (0, errno, "%s", quote (file)); + return false; + } + + remember_copied (file, sb.st_ino, sb.st_dev); + return true; +} + +/* If INO/DEV correspond to an already-copied source file, return the + name of the corresponding destination file. Otherwise, return NULL. */ + +extern char * +src_to_dest_lookup (ino_t ino, dev_t dev) +{ + struct Src_to_dest ent; + struct Src_to_dest const *e; + ent.st_ino = ino; + ent.st_dev = dev; + e = hash_lookup (src_to_dest, &ent); + return e ? e->name : NULL; +} + +/* Add file NAME, copied from inode number INO and device number DEV, + to the list of files we have copied. + Return NULL if inserted, otherwise non-NULL. */ + +extern char * +remember_copied (const char *name, ino_t ino, dev_t dev) +{ + struct Src_to_dest *ent; + struct Src_to_dest *ent_from_table; + + ent = xmalloc (sizeof *ent); + ent->name = xstrdup (name); + ent->st_ino = ino; + ent->st_dev = dev; + + ent_from_table = hash_insert (src_to_dest, ent); + if (ent_from_table == NULL) + { + /* Insertion failed due to lack of memory. */ + xalloc_die (); + } + + /* Determine whether there was already an entry in the table + with a matching key. If so, free ENT (it wasn't inserted) and + return the `name' from the table entry. */ + if (ent_from_table != ent) + { + src_to_dest_free (ent); + return (char *) ent_from_table->name; + } + + /* New key; insertion succeeded. */ + return NULL; +} + +/* Initialize the hash table. */ +extern void +hash_init (void) +{ + src_to_dest = hash_initialize (INITIAL_TABLE_SIZE, NULL, + src_to_dest_hash, + src_to_dest_compare, + src_to_dest_free); + if (src_to_dest == NULL) + xalloc_die (); +} + +/* Reset the hash structure in the global variable `htab' to + contain no entries. */ + +extern void +forget_all (void) +{ + hash_free (src_to_dest); +} diff --git a/src/cp-hash.h b/src/cp-hash.h new file mode 100644 index 0000000..d142925 --- /dev/null +++ b/src/cp-hash.h @@ -0,0 +1,6 @@ +void hash_init (void); +void forget_all (void); +void forget_created (ino_t ino, dev_t dev); +char *remember_copied (const char *node, ino_t ino, dev_t dev); +bool remember_created (char const *file); +char *src_to_dest_lookup (ino_t ino, dev_t dev); diff --git a/src/cp.c b/src/cp.c new file mode 100644 index 0000000..5759e0d --- /dev/null +++ b/src/cp.c @@ -0,0 +1,1072 @@ +/* cp.c -- file copying (main routines) + Copyright (C) 89, 90, 91, 1995-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Written by Torbjorn Granlund, David MacKenzie, and Jim Meyering. */ + +#include +#include +#include +#include + +#include "system.h" +#include "argmatch.h" +#include "backupfile.h" +#include "copy.h" +#include "cp-hash.h" +#include "error.h" +#include "filenamecat.h" +#include "lchmod.h" +#include "quote.h" +#include "quotearg.h" +#include "stat-time.h" +#include "utimens.h" +#include "acl.h" + +#define ASSIGN_BASENAME_STRDUPA(Dest, File_name) \ + do \ + { \ + char *tmp_abns_; \ + ASSIGN_STRDUPA (tmp_abns_, (File_name)); \ + Dest = last_component (tmp_abns_); \ + strip_trailing_slashes (Dest); \ + } \ + while (0) + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "cp" + +#define AUTHORS "Torbjorn Granlund", "David MacKenzie", "Jim Meyering" + +/* Used by do_copy, make_dir_parents_private, and re_protect + to keep a list of leading directories whose protections + need to be fixed after copying. */ +struct dir_attr +{ + mode_t mode; + bool restore_mode; + size_t slash_offset; + struct dir_attr *next; +}; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + COPY_CONTENTS_OPTION = CHAR_MAX + 1, + NO_PRESERVE_ATTRIBUTES_OPTION, + PARENTS_OPTION, + PRESERVE_ATTRIBUTES_OPTION, + REPLY_OPTION, + SPARSE_OPTION, + STRIP_TRAILING_SLASHES_OPTION, + UNLINK_DEST_BEFORE_OPENING +}; + +/* 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 + +/* The invocation name of this program. */ +char *program_name; + +/* If true, 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." */ +static bool parents_option = false; + +/* Remove any trailing slashes from each SOURCE argument. */ +static bool remove_trailing_slashes; + +static char const *const sparse_type_string[] = +{ + "never", "auto", "always", NULL +}; +static enum Sparse_type const sparse_type[] = +{ + SPARSE_NEVER, SPARSE_AUTO, SPARSE_ALWAYS +}; +ARGMATCH_VERIFY (sparse_type_string, sparse_type); + +/* Valid arguments to the `--reply' option. */ +static char const* const reply_args[] = +{ + "yes", "no", "query", NULL +}; +/* The values that correspond to the above strings. */ +static int const reply_vals[] = +{ + I_ALWAYS_YES, I_ALWAYS_NO, I_ASK_USER +}; +ARGMATCH_VERIFY (reply_args, reply_vals); + +static struct option const long_opts[] = +{ + {"archive", no_argument, NULL, 'a'}, + {"backup", optional_argument, NULL, 'b'}, + {"copy-contents", no_argument, NULL, COPY_CONTENTS_OPTION}, + {"dereference", no_argument, NULL, 'L'}, + {"force", no_argument, NULL, 'f'}, + {"interactive", no_argument, NULL, 'i'}, + {"link", no_argument, NULL, 'l'}, + {"no-dereference", no_argument, NULL, 'P'}, + {"no-preserve", required_argument, NULL, NO_PRESERVE_ATTRIBUTES_OPTION}, + {"no-target-directory", no_argument, NULL, 'T'}, + {"one-file-system", no_argument, NULL, 'x'}, + {"parents", no_argument, NULL, PARENTS_OPTION}, + {"path", no_argument, NULL, PARENTS_OPTION}, /* Deprecated. */ + {"preserve", optional_argument, NULL, PRESERVE_ATTRIBUTES_OPTION}, + {"recursive", no_argument, NULL, 'R'}, + {"remove-destination", no_argument, NULL, UNLINK_DEST_BEFORE_OPENING}, + {"reply", required_argument, NULL, REPLY_OPTION}, /* Deprecated 2005-07-03, + remove in 2008. */ + {"sparse", required_argument, NULL, SPARSE_OPTION}, + {"strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION}, + {"suffix", required_argument, NULL, 'S'}, + {"symbolic-link", no_argument, NULL, 's'}, + {"target-directory", required_argument, NULL, 't'}, + {"update", no_argument, NULL, 'u'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [-T] SOURCE DEST\n\ + or: %s [OPTION]... SOURCE... DIRECTORY\n\ + or: %s [OPTION]... -t DIRECTORY SOURCE...\n\ +"), + program_name, program_name, program_name); + fputs (_("\ +Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -a, --archive same as -dpPR\n\ + --backup[=CONTROL] make a backup of each existing destination file\n\ + -b like --backup but does not accept an argument\n\ + --copy-contents copy contents of special files when recursive\n\ + -d same as --no-dereference --preserve=link\n\ +"), stdout); + fputs (_("\ + -f, --force if an existing destination file cannot be\n\ + opened, remove it and try again\n\ + -i, --interactive prompt before overwrite\n\ + -H follow command-line symbolic links\n\ +"), stdout); + fputs (_("\ + -l, --link link files instead of copying\n\ + -L, --dereference always follow symbolic links\n\ +"), stdout); + fputs (_("\ + -P, --no-dereference never follow symbolic links\n\ +"), stdout); + fputs (_("\ + -p same as --preserve=mode,ownership,timestamps\n\ + --preserve[=ATTR_LIST] preserve the specified attributes (default:\n\ + mode,ownership,timestamps), if possible\n\ + additional attributes: links, all\n\ +"), stdout); + fputs (_("\ + --no-preserve=ATTR_LIST don't preserve the specified attributes\n\ + --parents use full source file name under DIRECTORY\n\ +"), stdout); + fputs (_("\ + -R, -r, --recursive copy directories recursively\n\ + --remove-destination remove each existing destination file before\n\ + attempting to open it (contrast with --force)\n\ +"), stdout); + fputs (_("\ + --sparse=WHEN control creation of sparse files\n\ + --strip-trailing-slashes remove any trailing slashes from each SOURCE\n\ + argument\n\ +"), stdout); + fputs (_("\ + -s, --symbolic-link make symbolic links instead of copying\n\ + -S, --suffix=SUFFIX override the usual backup suffix\n\ + -t, --target-directory=DIRECTORY copy all SOURCE arguments into DIRECTORY\n\ + -T, --no-target-directory treat DEST as a normal file\n\ +"), stdout); + fputs (_("\ + -u, --update copy only when the SOURCE file is newer\n\ + than the destination file or when the\n\ + destination file is missing\n\ + -v, --verbose explain what is being done\n\ + -x, --one-file-system stay on this file system\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +By default, sparse SOURCE files are detected by a crude heuristic and the\n\ +corresponding DEST file is made sparse as well. That is the behavior\n\ +selected by --sparse=auto. Specify --sparse=always to create a sparse DEST\n\ +file whenever the SOURCE file contains a long enough sequence of zero bytes.\n\ +Use --sparse=never to inhibit creation of sparse files.\n\ +\n\ +"), stdout); + fputs (_("\ +The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ +The version control method may be selected via the --backup option or through\n\ +the VERSION_CONTROL environment variable. Here are the values:\n\ +\n\ +"), stdout); + fputs (_("\ + none, off never make backups (even if --backup is given)\n\ + numbered, t make numbered backups\n\ + existing, nil numbered if numbered backups exist, simple otherwise\n\ + simple, never always make simple backups\n\ +"), stdout); + fputs (_("\ +\n\ +As a special case, cp makes a backup of SOURCE when the force and backup\n\ +options are given and SOURCE and DEST are the same name for an existing,\n\ +regular file.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Ensure that the parent directories of CONST_DST_NAME have the + correct protections, for the --parents 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_NAME 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_NAME that may need to have its attributes changed. + The command `cp --parents --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 true if the parent of CONST_DST_NAME and any intermediate + directories specified by ATTR_LIST have the proper permissions + when done. */ + +static bool +re_protect (char const *const_dst_name, size_t src_offset, + struct dir_attr *attr_list, const struct cp_options *x) +{ + struct dir_attr *p; + char *dst_name; /* A copy of CONST_DST_NAME we can change. */ + char *src_name; /* The source name in `dst_name'. */ + + ASSIGN_STRDUPA (dst_name, const_dst_name); + src_name = dst_name + src_offset; + + for (p = attr_list; p; p = p->next) + { + struct stat src_sb; + + dst_name[p->slash_offset] = '\0'; + + if (XSTAT (x, src_name, &src_sb)) + { + error (0, errno, _("failed to get attributes of %s"), + quote (src_name)); + return false; + } + + /* 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 (x->preserve_timestamps) + { + struct timespec timespec[2]; + + timespec[0] = get_stat_atime (&src_sb); + timespec[1] = get_stat_mtime (&src_sb); + + if (utimens (dst_name, timespec)) + { + error (0, errno, _("failed to preserve times for %s"), + quote (dst_name)); + return false; + } + } + + if (x->preserve_ownership) + { + if (chown (dst_name, src_sb.st_uid, src_sb.st_gid) != 0 + && ! chown_failure_ok (x)) + { + error (0, errno, _("failed to preserve ownership for %s"), + quote (dst_name)); + return false; + } + } + + if (x->preserve_mode) + { + if (copy_acl (src_name, -1, dst_name, -1, src_sb.st_mode)) + return false; + } + else if (p->restore_mode) + { + if (lchmod (dst_name, p->mode) != 0) + { + error (0, errno, _("failed to preserve permissions for %s"), + quote (dst_name)); + return false; + } + } + + dst_name[p->slash_offset] = '/'; + } + return true; +} + +/* Ensure that the parent directory of CONST_DIR exists, for + the --parents option. + + SRC_OFFSET is the index in CONST_DIR (which is a destination + directory) of the beginning of the source directory name. + Create any leading directories that don't already exist. + 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 if this function creates parent of CONST_DIR. + + Return true if parent of CONST_DIR exists as a directory with the proper + permissions when done. */ + +/* FIXME: Synch this function with the one in ../lib/mkdir-p.c. */ + +static bool +make_dir_parents_private (char const *const_dir, size_t src_offset, + char const *verbose_fmt_string, + struct dir_attr **attr_list, bool *new_dst, + const struct cp_options *x) +{ + struct stat stats; + char *dir; /* A copy of CONST_DIR we can change. */ + char *src; /* Source name in DIR. */ + char *dst_dir; /* Leading directory of DIR. */ + size_t dirlen; /* Length of DIR. */ + + ASSIGN_STRDUPA (dir, const_dir); + + src = dir + src_offset; + + dirlen = dir_len (dir); + dst_dir = alloca (dirlen + 1); + memcpy (dst_dir, dir, dirlen); + dst_dir[dirlen] = '\0'; + + *attr_list = NULL; + + if (XSTAT (x, dst_dir, &stats)) + { + /* A parent of CONST_DIR does not exist. + Make all missing intermediate directories. */ + char *slash; + + slash = src; + while (*slash == '/') + slash++; + while ((slash = strchr (slash, '/'))) + { + /* Add this directory to the list of directories whose modes need + fixing later. */ + struct dir_attr *new = xmalloc (sizeof *new); + new->slash_offset = slash - dir; + new->restore_mode = false; + new->next = *attr_list; + *attr_list = new; + + *slash = '\0'; + if (XSTAT (x, dir, &stats)) + { + mode_t src_mode; + mode_t omitted_permissions; + mode_t mkdir_mode; + int src_errno; + + /* This component does not exist. We must set + *new_dst and new->mode inside this loop because, + for example, in the command `cp --parents ../a/../b/c e_dir', + make_dir_parents_private creates only e_dir/../a if + ./b already exists. */ + *new_dst = true; + src_errno = (XSTAT (x, src, &stats) != 0 + ? errno + : S_ISDIR (stats.st_mode) + ? 0 + : ENOTDIR); + if (src_errno) + { + error (0, src_errno, _("failed to get attributes of %s"), + quote (src)); + return false; + } + src_mode = stats.st_mode; + + /* If the ownership or special mode bits might change, + omit some permissions at first, so unauthorized users + cannot nip in before the file is ready. */ + omitted_permissions = (src_mode + & (x->preserve_ownership + ? S_IRWXG | S_IRWXO + : x->preserve_mode + ? S_IWGRP | S_IWOTH + : 0)); + + /* POSIX says mkdir's behavior is implementation-defined when + (src_mode & ~S_IRWXUGO) != 0. However, common practice is + to ask mkdir to copy all the CHMOD_MODE_BITS, letting mkdir + decide what to do with S_ISUID | S_ISGID | S_ISVTX. */ + mkdir_mode = src_mode & CHMOD_MODE_BITS & ~omitted_permissions; + if (mkdir (dir, mkdir_mode) != 0) + { + error (0, errno, _("cannot make directory %s"), + quote (dir)); + return false; + } + else + { + if (verbose_fmt_string != NULL) + printf (verbose_fmt_string, src, dir); + } + + /* We need search and write permissions to the new directory + for writing the directory's contents. Check if these + permissions are there. */ + + if (lstat (dir, &stats)) + { + error (0, errno, _("failed to get attributes of %s"), + quote (dir)); + return false; + } + + + if (! x->preserve_mode) + { + if (omitted_permissions & ~stats.st_mode) + omitted_permissions &= ~ cached_umask (); + if (omitted_permissions & ~stats.st_mode + || (stats.st_mode & S_IRWXU) != S_IRWXU) + { + new->mode = stats.st_mode | omitted_permissions; + new->restore_mode = true; + } + } + + if ((stats.st_mode & S_IRWXU) != S_IRWXU) + { + /* Make the new directory searchable and writable. + The original permissions will be restored later. */ + + if (lchmod (dir, stats.st_mode | S_IRWXU) != 0) + { + error (0, errno, _("setting permissions for %s"), + quote (dir)); + return false; + } + } + } + else if (!S_ISDIR (stats.st_mode)) + { + error (0, 0, _("%s exists but is not a directory"), + quote (dir)); + return false; + } + else + *new_dst = false; + *slash++ = '/'; + + /* Avoid unnecessary calls to `stat' when given + file names containing multiple adjacent slashes. */ + while (*slash == '/') + slash++; + } + } + + /* We get here if the parent of DIR already exists. */ + + else if (!S_ISDIR (stats.st_mode)) + { + error (0, 0, _("%s exists but is not a directory"), quote (dst_dir)); + return false; + } + else + { + *new_dst = false; + } + return true; +} + +/* FILE is the last operand of this command. + Return true if FILE is a directory. + But report an error and exit if there is a problem accessing FILE, + or if FILE does not exist but would have to refer to an existing + directory if it referred to anything at all. + + If the file exists, store the file's status into *ST. + Otherwise, set *NEW_DST. */ + +static bool +target_directory_operand (char const *file, struct stat *st, bool *new_dst) +{ + int err = (stat (file, st) == 0 ? 0 : errno); + bool is_a_dir = !err && S_ISDIR (st->st_mode); + if (err) + { + if (err != ENOENT) + error (EXIT_FAILURE, err, _("accessing %s"), quote (file)); + *new_dst = true; + } + return is_a_dir; +} + +/* Scan the arguments, and copy each by calling copy. + Return true if successful. */ + +static bool +do_copy (int n_files, char **file, const char *target_directory, + bool no_target_directory, struct cp_options *x) +{ + struct stat sb; + bool new_dst = false; + bool ok = true; + + if (n_files <= !target_directory) + { + if (n_files <= 0) + error (0, 0, _("missing file operand")); + else + error (0, 0, _("missing destination file operand after %s"), + quote (file[0])); + usage (EXIT_FAILURE); + } + + if (no_target_directory) + { + if (target_directory) + error (EXIT_FAILURE, 0, + _("Cannot combine --target-directory (-t) " + "and --no-target-directory (-T)")); + if (2 < n_files) + { + error (0, 0, _("extra operand %s"), quote (file[2])); + usage (EXIT_FAILURE); + } + } + else if (!target_directory) + { + if (2 <= n_files + && target_directory_operand (file[n_files - 1], &sb, &new_dst)) + target_directory = file[--n_files]; + else if (2 < n_files) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (file[n_files - 1])); + } + + if (target_directory) + { + /* cp file1...filen edir + Copy the files `file1' through `filen' + to the existing directory `edir'. */ + int i; + + /* Initialize these hash tables only if we'll need them. + The problems they're used to detect can arise only if + there are two or more files to copy. */ + if (2 <= n_files) + { + dest_info_init (x); + src_info_init (x); + } + + for (i = 0; i < n_files; i++) + { + char *dst_name; + bool parent_exists = true; /* True if dir_name (dst_name) exists. */ + struct dir_attr *attr_list; + char *arg_in_concat = NULL; + char *arg = file[i]; + + /* Trailing slashes are meaningful (i.e., maybe worth preserving) + only in the source file names. */ + if (remove_trailing_slashes) + strip_trailing_slashes (arg); + + if (parents_option) + { + char *arg_no_trailing_slash; + + /* Use `arg' without trailing slashes in constructing destination + file names. Otherwise, we can end up trying to create a + directory via `mkdir ("dst/foo/"...', which is not portable. + It fails, due to the trailing slash, on at least + NetBSD 1.[34] systems. */ + ASSIGN_STRDUPA (arg_no_trailing_slash, arg); + strip_trailing_slashes (arg_no_trailing_slash); + + /* Append all of `arg' (minus any trailing slash) to `dest'. */ + dst_name = file_name_concat (target_directory, + arg_no_trailing_slash, + &arg_in_concat); + + /* For --parents, we have to make sure that the directory + dir_name (dst_name) exists. We may have to create a few + leading directories. */ + parent_exists = + (make_dir_parents_private + (dst_name, arg_in_concat - dst_name, + (x->verbose ? "%s -> %s\n" : NULL), + &attr_list, &new_dst, x)); + } + else + { + char *arg_base; + /* Append the last component of `arg' to `target_directory'. */ + + ASSIGN_BASENAME_STRDUPA (arg_base, arg); + /* For `cp -R source/.. dest', don't copy into `dest/..'. */ + dst_name = (STREQ (arg_base, "..") + ? xstrdup (target_directory) + : file_name_concat (target_directory, arg_base, + NULL)); + } + + if (!parent_exists) + { + /* make_dir_parents_private failed, so don't even + attempt the copy. */ + ok = false; + } + else + { + bool copy_into_self; + ok &= copy (arg, dst_name, new_dst, x, ©_into_self, NULL); + + if (parents_option) + ok &= re_protect (dst_name, arg_in_concat - dst_name, + attr_list, x); + } + + free (dst_name); + } + } + else /* !target_directory */ + { + char const *new_dest; + char const *source = file[0]; + char const *dest = file[1]; + bool unused; + + if (parents_option) + { + error (0, 0, + _("with --parents, the destination must be a directory")); + usage (EXIT_FAILURE); + } + + /* When the force and backup options have been specified and + the source and destination are the same name for an existing + regular file, convert the user's command, e.g., + `cp --force --backup foo foo' to `cp --force foo fooSUFFIX' + where SUFFIX is determined by any version control options used. */ + + if (x->unlink_dest_after_failed_open + && x->backup_type != no_backups + && STREQ (source, dest) + && !new_dst && S_ISREG (sb.st_mode)) + { + static struct cp_options x_tmp; + + new_dest = find_backup_file_name (dest, x->backup_type); + /* Set x->backup_type to `no_backups' so that the normal backup + mechanism is not used when performing the actual copy. + backup_type must be set to `no_backups' only *after* the above + call to find_backup_file_name -- that function uses + backup_type to determine the suffix it applies. */ + x_tmp = *x; + x_tmp.backup_type = no_backups; + x = &x_tmp; + } + else + { + new_dest = dest; + } + + ok = copy (source, new_dest, 0, x, &unused, NULL); + } + + return ok; +} + +static void +cp_option_init (struct cp_options *x) +{ + x->copy_as_regular = true; + x->dereference = DEREF_UNDEFINED; + x->unlink_dest_before_opening = false; + x->unlink_dest_after_failed_open = false; + x->hard_link = false; + x->interactive = I_UNSPECIFIED; + x->chown_privileges = chown_privileges (); + x->move_mode = false; + x->one_file_system = false; + + x->preserve_ownership = false; + x->preserve_links = false; + x->preserve_mode = false; + x->preserve_timestamps = false; + + x->require_preserve = false; + x->recursive = false; + x->sparse_mode = SPARSE_AUTO; + x->symbolic_link = false; + x->set_mode = false; + x->mode = 0; + + /* Not used. */ + x->stdin_tty = false; + + x->update = false; + x->verbose = false; + x->dest_info = NULL; + x->src_info = NULL; +} + +/* Given a string, ARG, containing a comma-separated list of arguments + to the --preserve option, set the appropriate fields of X to ON_OFF. */ +static void +decode_preserve_arg (char const *arg, struct cp_options *x, bool on_off) +{ + enum File_attribute + { + PRESERVE_MODE, + PRESERVE_TIMESTAMPS, + PRESERVE_OWNERSHIP, + PRESERVE_LINK, + PRESERVE_ALL + }; + static enum File_attribute const preserve_vals[] = + { + PRESERVE_MODE, PRESERVE_TIMESTAMPS, + PRESERVE_OWNERSHIP, PRESERVE_LINK, PRESERVE_ALL + }; + /* Valid arguments to the `--preserve' option. */ + static char const* const preserve_args[] = + { + "mode", "timestamps", + "ownership", "links", "all", NULL + }; + ARGMATCH_VERIFY (preserve_args, preserve_vals); + + char *arg_writable = xstrdup (arg); + char *s = arg_writable; + do + { + /* find next comma */ + char *comma = strchr (s, ','); + enum File_attribute val; + + /* If we found a comma, put a NUL in its place and advance. */ + if (comma) + *comma++ = 0; + + /* process S. */ + val = XARGMATCH ("--preserve", s, preserve_args, preserve_vals); + switch (val) + { + case PRESERVE_MODE: + x->preserve_mode = on_off; + break; + + case PRESERVE_TIMESTAMPS: + x->preserve_timestamps = on_off; + break; + + case PRESERVE_OWNERSHIP: + x->preserve_ownership = on_off; + break; + + case PRESERVE_LINK: + x->preserve_links = on_off; + break; + + case PRESERVE_ALL: + x->preserve_mode = on_off; + x->preserve_timestamps = on_off; + x->preserve_ownership = on_off; + x->preserve_links = on_off; + break; + + default: + abort (); + } + s = comma; + } + while (s); + + free (arg_writable); +} + +int +main (int argc, char **argv) +{ + int c; + bool ok; + bool make_backups = false; + char *backup_suffix_string; + char *version_control_string = NULL; + struct cp_options x; + bool copy_contents = false; + char *target_directory = NULL; + bool no_target_directory = false; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + cp_option_init (&x); + + /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless + we'll actually use backup_suffix_string. */ + backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); + + while ((c = getopt_long (argc, argv, "abdfHilLprst:uvxPRS:T", + long_opts, NULL)) + != -1) + { + switch (c) + { + case SPARSE_OPTION: + x.sparse_mode = XARGMATCH ("--sparse", optarg, + sparse_type_string, sparse_type); + break; + + case 'a': /* Like -dpPR. */ + x.dereference = DEREF_NEVER; + x.preserve_links = true; + x.preserve_ownership = true; + x.preserve_mode = true; + x.preserve_timestamps = true; + x.require_preserve = true; + x.recursive = true; + break; + + case 'b': + make_backups = true; + if (optarg) + version_control_string = optarg; + break; + + case COPY_CONTENTS_OPTION: + copy_contents = true; + break; + + case 'd': + x.preserve_links = true; + x.dereference = DEREF_NEVER; + break; + + case 'f': + x.unlink_dest_after_failed_open = true; + break; + + case 'H': + x.dereference = DEREF_COMMAND_LINE_ARGUMENTS; + break; + + case 'i': + x.interactive = I_ASK_USER; + break; + + case 'l': + x.hard_link = true; + break; + + case 'L': + x.dereference = DEREF_ALWAYS; + break; + + case 'P': + x.dereference = DEREF_NEVER; + break; + + case NO_PRESERVE_ATTRIBUTES_OPTION: + decode_preserve_arg (optarg, &x, false); + break; + + case PRESERVE_ATTRIBUTES_OPTION: + if (optarg == NULL) + { + /* Fall through to the case for `p' below. */ + } + else + { + decode_preserve_arg (optarg, &x, true); + x.require_preserve = true; + break; + } + + case 'p': + x.preserve_ownership = true; + x.preserve_mode = true; + x.preserve_timestamps = true; + x.require_preserve = true; + break; + + case PARENTS_OPTION: + parents_option = true; + break; + + case 'r': + case 'R': + x.recursive = true; + break; + + case REPLY_OPTION: /* Deprecated */ + x.interactive = XARGMATCH ("--reply", optarg, + reply_args, reply_vals); + error (0, 0, + _("the --reply option is deprecated; use -i or -f instead")); + break; + + case UNLINK_DEST_BEFORE_OPENING: + x.unlink_dest_before_opening = true; + break; + + case STRIP_TRAILING_SLASHES_OPTION: + remove_trailing_slashes = true; + break; + + case 's': + x.symbolic_link = true; + break; + + case 't': + if (target_directory) + error (EXIT_FAILURE, 0, + _("multiple target directories specified")); + else + { + struct stat st; + if (stat (optarg, &st) != 0) + error (EXIT_FAILURE, errno, _("accessing %s"), quote (optarg)); + if (! S_ISDIR (st.st_mode)) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (optarg)); + } + target_directory = optarg; + break; + + case 'T': + no_target_directory = true; + break; + + case 'u': + x.update = true; + break; + + case 'v': + x.verbose = true; + break; + + case 'x': + x.one_file_system = true; + break; + + case 'S': + make_backups = true; + backup_suffix_string = optarg; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (x.hard_link & x.symbolic_link) + { + error (0, 0, _("cannot make both hard and symbolic links")); + usage (EXIT_FAILURE); + } + + if (backup_suffix_string) + simple_backup_suffix = xstrdup (backup_suffix_string); + + x.backup_type = (make_backups + ? xget_version (_("backup type"), + version_control_string) + : no_backups); + + if (x.dereference == DEREF_UNDEFINED) + { + if (x.recursive) + /* This is compatible with FreeBSD. */ + x.dereference = DEREF_NEVER; + else + x.dereference = DEREF_ALWAYS; + } + + /* The key difference between -d (--no-dereference) and not is the version + of `stat' to call. */ + + if (x.recursive) + x.copy_as_regular = copy_contents; + + /* If --force (-f) was specified and we're in link-creation mode, + first remove any existing destination file. */ + if (x.unlink_dest_after_failed_open & (x.hard_link | x.symbolic_link)) + x.unlink_dest_before_opening = true; + + /* Allocate space for remembering copied and created files. */ + + hash_init (); + + ok = do_copy (argc - optind, argv + optind, + target_directory, no_target_directory, &x); + + forget_all (); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/csplit.c b/src/csplit.c new file mode 100644 index 0000000..c2105bc --- /dev/null +++ b/src/csplit.c @@ -0,0 +1,1515 @@ +/* csplit - split a file into sections determined by context lines + Copyright (C) 91, 1995-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Stuart Kemp, cpsrk@groper.jcu.edu.au. + Modified by David MacKenzie, djm@gnu.ai.mit.edu. */ + +#include + +#include +#include +#include + +#include "system.h" + +#include + +#include "error.h" +#include "fd-reopen.h" +#include "inttostr.h" +#include "quote.h" +#include "safe-read.h" +#include "stdio--.h" +#include "xstrtol.h" + +/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is + present. */ +#ifndef SA_NOCLDSTOP +# define SA_NOCLDSTOP 0 +# define sigprocmask(How, Set, Oset) /* empty */ +# define sigset_t int +# if ! HAVE_SIGINTERRUPT +# define siginterrupt(sig, flag) /* empty */ +# endif +#endif + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "csplit" + +#define AUTHORS "Stuart Kemp", "David MacKenzie" + +/* Increment size of area for control records. */ +#define ALLOC_SIZE 20 + +/* The default prefix for output file names. */ +#define DEFAULT_PREFIX "xx" + +/* A compiled pattern arg. */ +struct control +{ + intmax_t offset; /* Offset from regexp to split at. */ + uintmax_t lines_required; /* Number of lines required. */ + uintmax_t repeat; /* Repeat count. */ + int argnum; /* ARGV index. */ + bool repeat_forever; /* True if `*' used as a repeat count. */ + bool ignore; /* If true, produce no output (for regexp). */ + bool regexpr; /* True if regular expression was used. */ + struct re_pattern_buffer re_compiled; /* Compiled regular expression. */ +}; + +/* Initial size of data area in buffers. */ +#define START_SIZE 8191 + +/* Increment size for data area. */ +#define INCR_SIZE 2048 + +/* Number of lines kept in each node in line list. */ +#define CTRL_SIZE 80 + +#ifdef DEBUG +/* Some small values to test the algorithms. */ +# define START_SIZE 200 +# define INCR_SIZE 10 +# define CTRL_SIZE 1 +#endif + +/* A string with a length count. */ +struct cstring +{ + size_t len; + char *str; +}; + +/* Pointers to the beginnings of lines in the buffer area. + These structures are linked together if needed. */ +struct line +{ + size_t used; /* Number of offsets used in this struct. */ + size_t insert_index; /* Next offset to use when inserting line. */ + size_t retrieve_index; /* Next index to use when retrieving line. */ + struct cstring starts[CTRL_SIZE]; /* Lines in the data area. */ + struct line *next; /* Next in linked list. */ +}; + +/* The structure to hold the input lines. + Contains a pointer to the data area and a list containing + pointers to the individual lines. */ +struct buffer_record +{ + size_t bytes_alloc; /* Size of the buffer area. */ + size_t bytes_used; /* Bytes used in the buffer area. */ + uintmax_t start_line; /* First line number in this buffer. */ + uintmax_t first_available; /* First line that can be retrieved. */ + size_t num_lines; /* Number of complete lines in this buffer. */ + char *buffer; /* Data area. */ + struct line *line_start; /* Head of list of pointers to lines. */ + struct line *curr_line; /* The line start record currently in use. */ + struct buffer_record *next; +}; + +static void close_output_file (void); +static void create_output_file (void); +static void delete_all_files (bool); +static void save_line_to_file (const struct cstring *line); +void usage (int status); + +/* The name this program was run with. */ +char *program_name; + +/* Start of buffer list. */ +static struct buffer_record *head = NULL; + +/* Partially read line. */ +static char *hold_area = NULL; + +/* Number of bytes in `hold_area'. */ +static size_t hold_count = 0; + +/* Number of the last line in the buffers. */ +static uintmax_t last_line_number = 0; + +/* Number of the line currently being examined. */ +static uintmax_t current_line = 0; + +/* If true, we have read EOF. */ +static bool have_read_eof = false; + +/* Name of output files. */ +static char *volatile filename_space = NULL; + +/* Prefix part of output file names. */ +static char const *volatile prefix = NULL; + +/* Suffix part of output file names. */ +static char *volatile suffix = NULL; + +/* Number of digits to use in output file names. */ +static int volatile digits = 2; + +/* Number of files created so far. */ +static unsigned int volatile files_created = 0; + +/* Number of bytes written to current file. */ +static uintmax_t bytes_written; + +/* Output file pointer. */ +static FILE *output_stream = NULL; + +/* Output file name. */ +static char *output_filename = NULL; + +/* Perhaps it would be cleaner to pass arg values instead of indexes. */ +static char **global_argv; + +/* If true, do not print the count of bytes in each output file. */ +static bool suppress_count; + +/* If true, remove output files on error. */ +static bool volatile remove_files; + +/* If true, remove all output files which have a zero length. */ +static bool elide_empty_files; + +/* The compiled pattern arguments, which determine how to split + the input file. */ +static struct control *controls; + +/* Number of elements in `controls'. */ +static size_t control_used; + +/* The set of signals that are caught. */ +static sigset_t caught_signals; + +static struct option const longopts[] = +{ + {"digits", required_argument, NULL, 'n'}, + {"quiet", no_argument, NULL, 'q'}, + {"silent", no_argument, NULL, 's'}, + {"keep-files", no_argument, NULL, 'k'}, + {"elide-empty-files", no_argument, NULL, 'z'}, + {"prefix", required_argument, NULL, 'f'}, + {"suffix-format", required_argument, NULL, 'b'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Optionally remove files created so far; then exit. + Called when an error detected. */ + +static void +cleanup (void) +{ + sigset_t oldset; + + close_output_file (); + + sigprocmask (SIG_BLOCK, &caught_signals, &oldset); + delete_all_files (false); + sigprocmask (SIG_SETMASK, &oldset, NULL); +} + +static void cleanup_fatal (void) ATTRIBUTE_NORETURN; +static void +cleanup_fatal (void) +{ + cleanup (); + exit (EXIT_FAILURE); +} + +extern void +xalloc_die (void) +{ + error (0, 0, "%s", _("memory exhausted")); + cleanup_fatal (); +} + +static void +interrupt_handler (int sig) +{ + if (! SA_NOCLDSTOP) + signal (sig, SIG_IGN); + + delete_all_files (true); + + signal (sig, SIG_DFL); + raise (sig); +} + +/* Keep track of NUM bytes of a partial line in buffer START. + These bytes will be retrieved later when another large buffer is read. */ + +static void +save_to_hold_area (char *start, size_t num) +{ + free (hold_area); + hold_area = start; + hold_count = num; +} + +/* Read up to MAX_N_BYTES bytes from the input stream into DEST. + Return the number of bytes read. */ + +static size_t +read_input (char *dest, size_t max_n_bytes) +{ + size_t bytes_read; + + if (max_n_bytes == 0) + return 0; + + bytes_read = safe_read (STDIN_FILENO, dest, max_n_bytes); + + if (bytes_read == 0) + have_read_eof = true; + + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("read error")); + cleanup_fatal (); + } + + return bytes_read; +} + +/* Initialize existing line record P. */ + +static void +clear_line_control (struct line *p) +{ + p->used = 0; + p->insert_index = 0; + p->retrieve_index = 0; +} + +/* Return a new, initialized line record. */ + +static struct line * +new_line_control (void) +{ + struct line *p = xmalloc (sizeof *p); + + p->next = NULL; + clear_line_control (p); + + return p; +} + +/* Record LINE_START, which is the address of the start of a line + of length LINE_LEN in the large buffer, in the lines buffer of B. */ + +static void +keep_new_line (struct buffer_record *b, char *line_start, size_t line_len) +{ + struct line *l; + + /* If there is no existing area to keep line info, get some. */ + if (b->line_start == NULL) + b->line_start = b->curr_line = new_line_control (); + + /* If existing area for lines is full, get more. */ + if (b->curr_line->used == CTRL_SIZE) + { + b->curr_line->next = new_line_control (); + b->curr_line = b->curr_line->next; + } + + l = b->curr_line; + + /* Record the start of the line, and update counters. */ + l->starts[l->insert_index].str = line_start; + l->starts[l->insert_index].len = line_len; + l->used++; + l->insert_index++; +} + +/* Scan the buffer in B for newline characters + and record the line start locations and lengths in B. + Return the number of lines found in this buffer. + + There may be an incomplete line at the end of the buffer; + a pointer is kept to this area, which will be used when + the next buffer is filled. */ + +static size_t +record_line_starts (struct buffer_record *b) +{ + char *line_start; /* Start of current line. */ + char *line_end; /* End of each line found. */ + size_t bytes_left; /* Length of incomplete last line. */ + size_t lines; /* Number of lines found. */ + size_t line_length; /* Length of each line found. */ + + if (b->bytes_used == 0) + return 0; + + lines = 0; + line_start = b->buffer; + bytes_left = b->bytes_used; + + for (;;) + { + line_end = memchr (line_start, '\n', bytes_left); + if (line_end == NULL) + break; + line_length = line_end - line_start + 1; + keep_new_line (b, line_start, line_length); + bytes_left -= line_length; + line_start = line_end + 1; + lines++; + } + + /* Check for an incomplete last line. */ + if (bytes_left) + { + if (have_read_eof) + { + keep_new_line (b, line_start, bytes_left); + lines++; + } + else + save_to_hold_area (xmemdup (line_start, bytes_left), bytes_left); + } + + b->num_lines = lines; + b->first_available = b->start_line = last_line_number + 1; + last_line_number += lines; + + return lines; +} + +/* Return a new buffer with room to store SIZE bytes, plus + an extra byte for safety. */ + +static struct buffer_record * +create_new_buffer (size_t size) +{ + struct buffer_record *new_buffer = xmalloc (sizeof *new_buffer); + + new_buffer->buffer = xmalloc (size + 1); + + new_buffer->bytes_alloc = size; + new_buffer->line_start = new_buffer->curr_line = NULL; + + return new_buffer; +} + +/* Return a new buffer of at least MINSIZE bytes. If a buffer of at + least that size is currently free, use it, otherwise create a new one. */ + +static struct buffer_record * +get_new_buffer (size_t min_size) +{ + struct buffer_record *new_buffer; /* Buffer to return. */ + size_t alloc_size; /* Actual size that will be requested. */ + + alloc_size = START_SIZE; + if (alloc_size < min_size) + { + size_t s = min_size - alloc_size + INCR_SIZE - 1; + alloc_size += s - s % INCR_SIZE; + } + + new_buffer = create_new_buffer (alloc_size); + + new_buffer->num_lines = 0; + new_buffer->bytes_used = 0; + new_buffer->start_line = new_buffer->first_available = last_line_number + 1; + new_buffer->next = NULL; + + return new_buffer; +} + +static void +free_buffer (struct buffer_record *buf) +{ + free (buf->buffer); + buf->buffer = NULL; +} + +/* Append buffer BUF to the linked list of buffers that contain + some data yet to be processed. */ + +static void +save_buffer (struct buffer_record *buf) +{ + struct buffer_record *p; + + buf->next = NULL; + buf->curr_line = buf->line_start; + + if (head == NULL) + head = buf; + else + { + for (p = head; p->next; p = p->next) + /* Do nothing. */ ; + p->next = buf; + } +} + +/* Fill a buffer of input. + + Set the initial size of the buffer to a default. + Fill the buffer (from the hold area and input stream) + and find the individual lines. + If no lines are found (the buffer is too small to hold the next line), + release the current buffer (whose contents would have been put in the + hold area) and repeat the process with another large buffer until at least + one entire line has been read. + + Return true if a new buffer was obtained, otherwise false + (in which case end-of-file must have been encountered). */ + +static bool +load_buffer (void) +{ + struct buffer_record *b; + size_t bytes_wanted = START_SIZE; /* Minimum buffer size. */ + size_t bytes_avail; /* Size of new buffer created. */ + size_t lines_found; /* Number of lines in this new buffer. */ + char *p; /* Place to load into buffer. */ + + if (have_read_eof) + return false; + + /* We must make the buffer at least as large as the amount of data + in the partial line left over from the last call. */ + if (bytes_wanted < hold_count) + bytes_wanted = hold_count; + + while (1) + { + b = get_new_buffer (bytes_wanted); + bytes_avail = b->bytes_alloc; /* Size of buffer returned. */ + p = b->buffer; + + /* First check the `holding' area for a partial line. */ + if (hold_count) + { + memcpy (p, hold_area, hold_count); + p += hold_count; + b->bytes_used += hold_count; + bytes_avail -= hold_count; + hold_count = 0; + } + + b->bytes_used += read_input (p, bytes_avail); + + lines_found = record_line_starts (b); + if (!lines_found) + free_buffer (b); + + if (lines_found || have_read_eof) + break; + + if (xalloc_oversized (2, b->bytes_alloc)) + xalloc_die (); + bytes_wanted = 2 * b->bytes_alloc; + free_buffer (b); + free (b); + } + + if (lines_found) + save_buffer (b); + else + free (b); + + return lines_found != 0; +} + +/* Return the line number of the first line that has not yet been retrieved. */ + +static uintmax_t +get_first_line_in_buffer (void) +{ + if (head == NULL && !load_buffer ()) + error (EXIT_FAILURE, errno, _("input disappeared")); + + return head->first_available; +} + +/* Return a pointer to the logical first line in the buffer and make the + next line the logical first line. + Return NULL if there is no more input. */ + +static struct cstring * +remove_line (void) +{ + /* If non-NULL, this is the buffer for which the previous call + returned the final line. So now, presuming that line has been + processed, we can free the buffer and reset this pointer. */ + static struct buffer_record *prev_buf = NULL; + + struct cstring *line; /* Return value. */ + struct line *l; /* For convenience. */ + + if (prev_buf) + { + free_buffer (prev_buf); + prev_buf = NULL; + } + + if (head == NULL && !load_buffer ()) + return NULL; + + if (current_line < head->first_available) + current_line = head->first_available; + + ++(head->first_available); + + l = head->curr_line; + + line = &l->starts[l->retrieve_index]; + + /* Advance index to next line. */ + if (++l->retrieve_index == l->used) + { + /* Go on to the next line record. */ + head->curr_line = l->next; + if (head->curr_line == NULL || head->curr_line->used == 0) + { + /* Go on to the next data block. + but first record the current one so we can free it + once the line we're returning has been processed. */ + prev_buf = head; + head = head->next; + } + } + + return line; +} + +/* Search the buffers for line LINENUM, reading more input if necessary. + Return a pointer to the line, or NULL if it is not found in the file. */ + +static struct cstring * +find_line (uintmax_t linenum) +{ + struct buffer_record *b; + + if (head == NULL && !load_buffer ()) + return NULL; + + if (linenum < head->start_line) + return NULL; + + for (b = head;;) + { + if (linenum < b->start_line + b->num_lines) + { + /* The line is in this buffer. */ + struct line *l; + size_t offset; /* How far into the buffer the line is. */ + + l = b->line_start; + offset = linenum - b->start_line; + /* Find the control record. */ + while (offset >= CTRL_SIZE) + { + l = l->next; + offset -= CTRL_SIZE; + } + return &l->starts[offset]; + } + if (b->next == NULL && !load_buffer ()) + return NULL; + b = b->next; /* Try the next data block. */ + } +} + +/* Return true if at least one more line is available for input. */ + +static bool +no_more_lines (void) +{ + return find_line (current_line + 1) == NULL; +} + +/* Open NAME as standard input. */ + +static void +set_input_file (const char *name) +{ + if (! STREQ (name, "-") && fd_reopen (STDIN_FILENO, name, O_RDONLY, 0) < 0) + error (EXIT_FAILURE, errno, _("cannot open %s for reading"), quote (name)); +} + +/* Write all lines from the beginning of the buffer up to, but + not including, line LAST_LINE, to the current output file. + If IGNORE is true, do not output lines selected here. + ARGNUM is the index in ARGV of the current pattern. */ + +static void +write_to_file (uintmax_t last_line, bool ignore, int argnum) +{ + struct cstring *line; + uintmax_t first_line; /* First available input line. */ + uintmax_t lines; /* Number of lines to output. */ + uintmax_t i; + + first_line = get_first_line_in_buffer (); + + if (first_line > last_line) + { + error (0, 0, _("%s: line number out of range"), global_argv[argnum]); + cleanup_fatal (); + } + + lines = last_line - first_line; + + for (i = 0; i < lines; i++) + { + line = remove_line (); + if (line == NULL) + { + error (0, 0, _("%s: line number out of range"), global_argv[argnum]); + cleanup_fatal (); + } + if (!ignore) + save_line_to_file (line); + } +} + +/* Output any lines left after all regexps have been processed. */ + +static void +dump_rest_of_file (void) +{ + struct cstring *line; + + while ((line = remove_line ()) != NULL) + save_line_to_file (line); +} + +/* Handle an attempt to read beyond EOF under the control of record P, + on iteration REPETITION if nonzero. */ + +static void handle_line_error (const struct control *, uintmax_t) + ATTRIBUTE_NORETURN; +static void +handle_line_error (const struct control *p, uintmax_t repetition) +{ + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + + fprintf (stderr, _("%s: %s: line number out of range"), + program_name, quote (umaxtostr (p->lines_required, buf))); + if (repetition) + fprintf (stderr, _(" on repetition %s\n"), umaxtostr (repetition, buf)); + else + fprintf (stderr, "\n"); + + cleanup_fatal (); +} + +/* Determine the line number that marks the end of this file, + then get those lines and save them to the output file. + P is the control record. + REPETITION is the repetition number. */ + +static void +process_line_count (const struct control *p, uintmax_t repetition) +{ + uintmax_t linenum; + uintmax_t last_line_to_save = p->lines_required * (repetition + 1); + struct cstring *line; + + create_output_file (); + + linenum = get_first_line_in_buffer (); + + while (linenum++ < last_line_to_save) + { + line = remove_line (); + if (line == NULL) + handle_line_error (p, repetition); + save_line_to_file (line); + } + + close_output_file (); + + /* Ensure that the line number specified is not 1 greater than + the number of lines in the file. */ + if (no_more_lines ()) + handle_line_error (p, repetition); +} + +static void regexp_error (struct control *, uintmax_t, bool) ATTRIBUTE_NORETURN; +static void +regexp_error (struct control *p, uintmax_t repetition, bool ignore) +{ + fprintf (stderr, _("%s: %s: match not found"), + program_name, quote (global_argv[p->argnum])); + + if (repetition) + { + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + fprintf (stderr, _(" on repetition %s\n"), umaxtostr (repetition, buf)); + } + else + fprintf (stderr, "\n"); + + if (!ignore) + { + dump_rest_of_file (); + close_output_file (); + } + cleanup_fatal (); +} + +/* Read the input until a line matches the regexp in P, outputting + it unless P->IGNORE is true. + REPETITION is this repeat-count; 0 means the first time. */ + +static void +process_regexp (struct control *p, uintmax_t repetition) +{ + struct cstring *line; /* From input file. */ + size_t line_len; /* To make "$" in regexps work. */ + uintmax_t break_line; /* First line number of next file. */ + bool ignore = p->ignore; /* If true, skip this section. */ + regoff_t ret; + + if (!ignore) + create_output_file (); + + /* If there is no offset for the regular expression, or + it is positive, then it is not necessary to buffer the lines. */ + + if (p->offset >= 0) + { + for (;;) + { + line = find_line (++current_line); + if (line == NULL) + { + if (p->repeat_forever) + { + if (!ignore) + { + dump_rest_of_file (); + close_output_file (); + } + exit (EXIT_SUCCESS); + } + else + regexp_error (p, repetition, ignore); + } + line_len = line->len; + if (line->str[line_len - 1] == '\n') + line_len--; + ret = re_search (&p->re_compiled, line->str, line_len, + 0, line_len, NULL); + if (ret == -2) + { + error (0, 0, _("error in regular expression search")); + cleanup_fatal (); + } + if (ret == -1) + { + line = remove_line (); + if (!ignore) + save_line_to_file (line); + } + else + break; + } + } + else + { + /* Buffer the lines. */ + for (;;) + { + line = find_line (++current_line); + if (line == NULL) + { + if (p->repeat_forever) + { + if (!ignore) + { + dump_rest_of_file (); + close_output_file (); + } + exit (EXIT_SUCCESS); + } + else + regexp_error (p, repetition, ignore); + } + line_len = line->len; + if (line->str[line_len - 1] == '\n') + line_len--; + ret = re_search (&p->re_compiled, line->str, line_len, + 0, line_len, NULL); + if (ret == -2) + { + error (0, 0, _("error in regular expression search")); + cleanup_fatal (); + } + if (ret != -1) + break; + } + } + + /* Account for any offset from this regexp. */ + break_line = current_line + p->offset; + + write_to_file (break_line, ignore, p->argnum); + + if (!ignore) + close_output_file (); + + if (p->offset > 0) + current_line = break_line; +} + +/* Split the input file according to the control records we have built. */ + +static void +split_file (void) +{ + size_t i; + + for (i = 0; i < control_used; i++) + { + uintmax_t j; + if (controls[i].regexpr) + { + for (j = 0; (controls[i].repeat_forever + || j <= controls[i].repeat); j++) + process_regexp (&controls[i], j); + } + else + { + for (j = 0; (controls[i].repeat_forever + || j <= controls[i].repeat); j++) + process_line_count (&controls[i], j); + } + } + + create_output_file (); + dump_rest_of_file (); + close_output_file (); +} + +/* Return the name of output file number NUM. + + This function is called from a signal handler, so it should invoke + only reentrant functions that are async-signal-safe. POSIX does + not guarantee this for the functions called below, but we don't + know of any hosts where this implementation isn't safe. */ + +static char * +make_filename (unsigned int num) +{ + strcpy (filename_space, prefix); + if (suffix) + sprintf (filename_space + strlen (prefix), suffix, num); + else + sprintf (filename_space + strlen (prefix), "%0*u", digits, num); + return filename_space; +} + +/* Create the next output file. */ + +static void +create_output_file (void) +{ + sigset_t oldset; + bool fopen_ok; + int fopen_errno; + + output_filename = make_filename (files_created); + + /* Create the output file in a critical section, to avoid races. */ + sigprocmask (SIG_BLOCK, &caught_signals, &oldset); + output_stream = fopen (output_filename, "w"); + fopen_ok = (output_stream != NULL); + fopen_errno = errno; + files_created += fopen_ok; + sigprocmask (SIG_SETMASK, &oldset, NULL); + + if (! fopen_ok) + { + error (0, fopen_errno, "%s", output_filename); + cleanup_fatal (); + } + bytes_written = 0; +} + +/* If requested, delete all the files we have created. This function + must be called only from critical sections. */ + +static void +delete_all_files (bool in_signal_handler) +{ + unsigned int i; + + if (! remove_files) + return; + + for (i = 0; i < files_created; i++) + { + const char *name = make_filename (i); + if (unlink (name) != 0 && !in_signal_handler) + error (0, errno, "%s", name); + } + + files_created = 0; +} + +/* Close the current output file and print the count + of characters in this file. */ + +static void +close_output_file (void) +{ + if (output_stream) + { + if (ferror (output_stream)) + { + error (0, 0, _("write error for %s"), quote (output_filename)); + output_stream = NULL; + cleanup_fatal (); + } + if (fclose (output_stream) != 0) + { + error (0, errno, "%s", output_filename); + output_stream = NULL; + cleanup_fatal (); + } + if (bytes_written == 0 && elide_empty_files) + { + sigset_t oldset; + bool unlink_ok; + int unlink_errno; + + /* Remove the output file in a critical section, to avoid races. */ + sigprocmask (SIG_BLOCK, &caught_signals, &oldset); + unlink_ok = (unlink (output_filename) == 0); + unlink_errno = errno; + files_created -= unlink_ok; + sigprocmask (SIG_SETMASK, &oldset, NULL); + + if (! unlink_ok) + error (0, unlink_errno, "%s", output_filename); + } + else + { + if (!suppress_count) + { + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + fprintf (stdout, "%s\n", umaxtostr (bytes_written, buf)); + } + } + output_stream = NULL; + } +} + +/* Save line LINE to the output file and + increment the character count for the current file. */ + +static void +save_line_to_file (const struct cstring *line) +{ + fwrite (line->str, sizeof (char), line->len, output_stream); + bytes_written += line->len; +} + +/* Return a new, initialized control record. */ + +static struct control * +new_control_record (void) +{ + static size_t control_allocated = 0; /* Total space allocated. */ + struct control *p; + + if (control_used == control_allocated) + controls = X2NREALLOC (controls, &control_allocated); + p = &controls[control_used++]; + p->regexpr = false; + p->repeat = 0; + p->repeat_forever = false; + p->lines_required = 0; + p->offset = 0; + return p; +} + +/* Check if there is a numeric offset after a regular expression. + STR is the entire command line argument. + P is the control record for this regular expression. + NUM is the numeric part of STR. */ + +static void +check_for_offset (struct control *p, const char *str, const char *num) +{ + if (xstrtoimax (num, NULL, 10, &p->offset, "") != LONGINT_OK) + error (EXIT_FAILURE, 0, _("%s: integer expected after delimiter"), str); +} + +/* Given that the first character of command line arg STR is '{', + make sure that the rest of the string is a valid repeat count + and store its value in P. + ARGNUM is the ARGV index of STR. */ + +static void +parse_repeat_count (int argnum, struct control *p, char *str) +{ + uintmax_t val; + char *end; + + end = str + strlen (str) - 1; + if (*end != '}') + error (EXIT_FAILURE, 0, _("%s: `}' is required in repeat count"), str); + *end = '\0'; + + if (str+1 == end-1 && *(str+1) == '*') + p->repeat_forever = true; + else + { + if (xstrtoumax (str + 1, NULL, 10, &val, "") != LONGINT_OK) + { + error (EXIT_FAILURE, 0, + _("%s}: integer required between `{' and `}'"), + global_argv[argnum]); + } + p->repeat = val; + } + + *end = '}'; +} + +/* Extract the regular expression from STR and check for a numeric offset. + STR should start with the regexp delimiter character. + Return a new control record for the regular expression. + ARGNUM is the ARGV index of STR. + Unless IGNORE is true, mark these lines for output. */ + +static struct control * +extract_regexp (int argnum, bool ignore, char const *str) +{ + size_t len; /* Number of bytes in this regexp. */ + char delim = *str; + char const *closing_delim; + struct control *p; + const char *err; + + closing_delim = strrchr (str + 1, delim); + if (closing_delim == NULL) + error (EXIT_FAILURE, 0, + _("%s: closing delimiter `%c' missing"), str, delim); + + len = closing_delim - str - 1; + p = new_control_record (); + p->argnum = argnum; + p->ignore = ignore; + + p->regexpr = true; + p->re_compiled.buffer = NULL; + p->re_compiled.allocated = 0; + p->re_compiled.fastmap = xmalloc (UCHAR_MAX + 1); + p->re_compiled.translate = NULL; + re_syntax_options = + RE_SYNTAX_POSIX_BASIC & ~RE_CONTEXT_INVALID_DUP & ~RE_NO_EMPTY_RANGES; + err = re_compile_pattern (str + 1, len, &p->re_compiled); + if (err) + { + error (0, 0, _("%s: invalid regular expression: %s"), str, err); + cleanup_fatal (); + } + + if (closing_delim[1]) + check_for_offset (p, str, closing_delim + 1); + + return p; +} + +/* Extract the break patterns from args START through ARGC - 1 of ARGV. + After each pattern, check if the next argument is a repeat count. */ + +static void +parse_patterns (int argc, int start, char **argv) +{ + int i; /* Index into ARGV. */ + struct control *p; /* New control record created. */ + uintmax_t val; + static uintmax_t last_val = 0; + + for (i = start; i < argc; i++) + { + if (*argv[i] == '/' || *argv[i] == '%') + { + p = extract_regexp (i, *argv[i] == '%', argv[i]); + } + else + { + p = new_control_record (); + p->argnum = i; + + if (xstrtoumax (argv[i], NULL, 10, &val, "") != LONGINT_OK) + error (EXIT_FAILURE, 0, _("%s: invalid pattern"), argv[i]); + if (val == 0) + error (EXIT_FAILURE, 0, + _("%s: line number must be greater than zero"), + argv[i]); + if (val < last_val) + { + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + error (EXIT_FAILURE, 0, + _("line number %s is smaller than preceding line number, %s"), + quote (argv[i]), umaxtostr (last_val, buf)); + } + + if (val == last_val) + error (0, 0, + _("warning: line number %s is the same as preceding line number"), + quote (argv[i])); + + last_val = val; + + p->lines_required = val; + } + + if (i + 1 < argc && *argv[i + 1] == '{') + { + /* We have a repeat count. */ + i++; + parse_repeat_count (i, p, argv[i]); + } + } +} + +static unsigned int +get_format_flags (char **format_ptr) +{ + unsigned int count = 0; + + for (; **format_ptr; (*format_ptr)++) + { + switch (**format_ptr) + { + case '-': + break; + + case '+': + case ' ': + count |= 1; + break; + + case '#': + count |= 2; /* Allow for 0x prefix preceding an `x' conversion. */ + break; + + default: + return count; + } + } + return count; +} + +static size_t +get_format_width (char **format_ptr) +{ + unsigned long int val = 0; + + if (ISDIGIT (**format_ptr) + && (xstrtoul (*format_ptr, format_ptr, 10, &val, NULL) != LONGINT_OK + || SIZE_MAX < val)) + error (EXIT_FAILURE, 0, _("invalid format width")); + + /* Allow for enough octal digits to represent the value of UINT_MAX, + even if the field width is less than that. */ + return MAX (val, (sizeof (unsigned int) * CHAR_BIT + 2) / 3); +} + +static size_t +get_format_prec (char **format_ptr) +{ + if (**format_ptr != '.') + return 0; + (*format_ptr)++; + + if (! ISDIGIT (**format_ptr)) + return 0; + else + { + unsigned long int val; + if (xstrtoul (*format_ptr, format_ptr, 10, &val, NULL) != LONGINT_OK + || SIZE_MAX < val) + error (EXIT_FAILURE, 0, _("invalid format precision")); + return val; + } +} + +static void +get_format_conv_type (char **format_ptr) +{ + unsigned char ch = *(*format_ptr)++; + + switch (ch) + { + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + break; + + case 0: + error (EXIT_FAILURE, 0, _("missing conversion specifier in suffix")); + break; + + default: + if (isprint (ch)) + error (EXIT_FAILURE, 0, + _("invalid conversion specifier in suffix: %c"), ch); + else + error (EXIT_FAILURE, 0, + _("invalid conversion specifier in suffix: \\%.3o"), ch); + } +} + +static size_t +max_out (char *format) +{ + size_t out_count = 0; + bool percent = false; + + while (*format) + { + if (*format++ != '%') + out_count++; + else if (*format == '%') + { + format++; + out_count++; + } + else + { + if (percent) + error (EXIT_FAILURE, 0, + _("too many %% conversion specifications in suffix")); + percent = true; + out_count += get_format_flags (&format); + { + size_t width = get_format_width (&format); + size_t prec = get_format_prec (&format); + + out_count += MAX (width, prec); + } + get_format_conv_type (&format); + } + } + + if (! percent) + error (EXIT_FAILURE, 0, + _("missing %% conversion specification in suffix")); + + return out_count; +} + +int +main (int argc, char **argv) +{ + int optc; + unsigned long int val; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + global_argv = argv; + controls = NULL; + control_used = 0; + suppress_count = false; + remove_files = true; + prefix = DEFAULT_PREFIX; + + while ((optc = getopt_long (argc, argv, "f:b:kn:sqz", longopts, NULL)) != -1) + switch (optc) + { + case 'f': + prefix = optarg; + break; + + case 'b': + suffix = optarg; + break; + + case 'k': + remove_files = false; + break; + + case 'n': + if (xstrtoul (optarg, NULL, 10, &val, "") != LONGINT_OK + || val > INT_MAX) + error (EXIT_FAILURE, 0, _("%s: invalid number"), optarg); + digits = val; + break; + + case 's': + case 'q': + suppress_count = true; + break; + + case 'z': + elide_empty_files = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + + if (argc - optind < 2) + { + if (argc <= optind) + error (0, 0, _("missing operand")); + else + error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); + usage (EXIT_FAILURE); + } + + if (suffix) + filename_space = xmalloc (strlen (prefix) + max_out (suffix) + 2); + else + filename_space = xmalloc (strlen (prefix) + digits + 2); + + set_input_file (argv[optind++]); + + parse_patterns (argc, optind, argv); + + { + int i; + static int const sig[] = + { + /* The usual suspects. */ + SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, +#ifdef SIGPOLL + SIGPOLL, +#endif +#ifdef SIGPROF + SIGPROF, +#endif +#ifdef SIGVTALRM + SIGVTALRM, +#endif +#ifdef SIGXCPU + SIGXCPU, +#endif +#ifdef SIGXFSZ + SIGXFSZ, +#endif + }; + enum { nsigs = sizeof sig / sizeof sig[0] }; + +#if SA_NOCLDSTOP + struct sigaction act; + + sigemptyset (&caught_signals); + for (i = 0; i < nsigs; i++) + { + sigaction (sig[i], NULL, &act); + if (act.sa_handler != SIG_IGN) + sigaddset (&caught_signals, sig[i]); + } + + act.sa_handler = interrupt_handler; + act.sa_mask = caught_signals; + act.sa_flags = 0; + + for (i = 0; i < nsigs; i++) + if (sigismember (&caught_signals, sig[i])) + sigaction (sig[i], &act, NULL); +#else + for (i = 0; i < nsigs; i++) + if (signal (sig[i], SIG_IGN) != SIG_IGN) + { + signal (sig[i], interrupt_handler); + siginterrupt (sig[i], 1); + } +#endif + } + + split_file (); + + if (close (STDIN_FILENO) != 0) + { + error (0, errno, _("read error")); + cleanup_fatal (); + } + + exit (EXIT_SUCCESS); +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... FILE PATTERN...\n\ +"), + program_name); + fputs (_("\ +Output pieces of FILE separated by PATTERN(s) to files `xx00', `xx01', ...,\n\ +and output byte counts of each piece to standard output.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -b, --suffix-format=FORMAT use sprintf FORMAT instead of %02d\n\ + -f, --prefix=PREFIX use PREFIX instead of `xx'\n\ + -k, --keep-files do not remove output files on errors\n\ +"), stdout); + fputs (_("\ + -n, --digits=DIGITS use specified number of digits instead of 2\n\ + -s, --quiet, --silent do not print counts of output file sizes\n\ + -z, --elide-empty-files remove empty output files\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Read standard input if FILE is -. Each PATTERN may be:\n\ +"), stdout); + fputs (_("\ +\n\ + INTEGER copy up to but not including specified line number\n\ + /REGEXP/[OFFSET] copy up to but not including a matching line\n\ + %REGEXP%[OFFSET] skip to, but not including a matching line\n\ + {INTEGER} repeat the previous pattern specified number of times\n\ + {*} repeat the previous pattern as many times as possible\n\ +\n\ +A line OFFSET is a required `+' or `-' followed by a positive integer.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} diff --git a/src/cut.c b/src/cut.c new file mode 100644 index 0000000..c9b8359 --- /dev/null +++ b/src/cut.c @@ -0,0 +1,884 @@ +/* cut - remove parts of lines of files + Copyright (C) 1997-2006 Free Software Foundation, Inc. + Copyright (C) 1984 David M. Ihnat + + 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by David Ihnat. */ + +/* POSIX changes, bug fixes, long-named options, and cleanup + by David MacKenzie . + + Rewrite cut_fields and cut_bytes -- Jim Meyering. */ + +#include + +#include +#include +#include +#include +#include "system.h" + +#include "error.h" +#include "getndelim2.h" +#include "hash.h" +#include "quote.h" +#include "xstrndup.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "cut" + +#define AUTHORS "David Ihnat", "David MacKenzie", "Jim Meyering" + +#define FATAL_ERROR(Message) \ + do \ + { \ + error (0, 0, (Message)); \ + usage (EXIT_FAILURE); \ + } \ + while (0) + +/* Append LOW, HIGH to the list RP of range pairs, allocating additional + space if necessary. Update local variable N_RP. When allocating, + update global variable N_RP_ALLOCATED. */ + +#define ADD_RANGE_PAIR(rp, low, high) \ + do \ + { \ + if (n_rp >= n_rp_allocated) \ + { \ + (rp) = X2NREALLOC (rp, &n_rp_allocated); \ + } \ + rp[n_rp].lo = (low); \ + rp[n_rp].hi = (high); \ + ++n_rp; \ + } \ + while (0) + +struct range_pair + { + size_t lo; + size_t hi; + }; + +/* This buffer is used to support the semantics of the -s option + (or lack of same) when the specified field list includes (does + not include) the first field. In both of those cases, the entire + first field must be read into this buffer to determine whether it + is followed by a delimiter or a newline before any of it may be + output. Otherwise, cut_fields can do the job without using this + buffer. */ +static char *field_1_buffer; + +/* The number of bytes allocated for FIELD_1_BUFFER. */ +static size_t field_1_bufsize; + +/* The largest field or byte index used as an endpoint of a closed + or degenerate range specification; this doesn't include the starting + index of right-open-ended ranges. For example, with either range spec + `2-5,9-', `2-3,5,9-' this variable would be set to 5. */ +static size_t max_range_endpoint; + +/* If nonzero, this is the index of the first field in a range that goes + to end of line. */ +static size_t eol_range_start; + +/* This is a bit vector. + In byte mode, which bytes to output. + In field mode, which DELIM-separated fields to output. + Both bytes and fields are numbered starting with 1, + so the zeroth bit of this array is unused. + A field or byte K has been selected if + (K <= MAX_RANGE_ENDPOINT and is_printable_field(K)) + || (EOL_RANGE_START > 0 && K >= EOL_RANGE_START). */ +static unsigned char *printable_field; + +enum operating_mode + { + undefined_mode, + + /* Output characters that are in the given bytes. */ + byte_mode, + + /* Output the given delimeter-separated fields. */ + field_mode + }; + +/* The name this program was run with. */ +char *program_name; + +static enum operating_mode operating_mode; + +/* If true do not output lines containing no delimeter characters. + Otherwise, all such lines are printed. This option is valid only + with field mode. */ +static bool suppress_non_delimited; + +/* If nonzero, print all bytes, characters, or fields _except_ + those that were specified. */ +static bool complement; + +/* The delimeter character for field mode. */ +static unsigned char delim; + +/* True if the --output-delimiter=STRING option was specified. */ +static bool output_delimiter_specified; + +/* The length of output_delimiter_string. */ +static size_t output_delimiter_length; + +/* The output field separator string. Defaults to the 1-character + string consisting of the input delimiter. */ +static char *output_delimiter_string; + +/* True if we have ever read standard input. */ +static bool have_read_stdin; + +#define HT_RANGE_START_INDEX_INITIAL_CAPACITY 31 + +/* The set of range-start indices. For example, given a range-spec list like + `-b1,3-5,4-9,15-', the following indices will be recorded here: 1, 3, 15. + Note that although `4' looks like a range-start index, it is in the middle + of the `3-5' range, so it doesn't count. + This table is created/used IFF output_delimiter_specified is set. */ +static Hash_table *range_start_ht; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + OUTPUT_DELIMITER_OPTION = CHAR_MAX + 1, + COMPLEMENT_OPTION +}; + +static struct option const longopts[] = +{ + {"bytes", required_argument, NULL, 'b'}, + {"characters", required_argument, NULL, 'c'}, + {"fields", required_argument, NULL, 'f'}, + {"delimiter", required_argument, NULL, 'd'}, + {"only-delimited", no_argument, NULL, 's'}, + {"output-delimiter", required_argument, NULL, OUTPUT_DELIMITER_OPTION}, + {"complement", no_argument, NULL, COMPLEMENT_OPTION}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + fputs (_("\ +Print selected parts of lines from each FILE to standard output.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -b, --bytes=LIST select only these bytes\n\ + -c, --characters=LIST select only these characters\n\ + -d, --delimiter=DELIM use DELIM instead of TAB for field delimiter\n\ +"), stdout); + fputs (_("\ + -f, --fields=LIST select only these fields; also print any line\n\ + that contains no delimiter character, unless\n\ + the -s option is specified\n\ + -n (ignored)\n\ +"), stdout); + fputs (_("\ + --complement complement the set of selected bytes, characters\n\ + or fields.\n\ +"), stdout); + fputs (_("\ + -s, --only-delimited do not print lines not containing delimiters\n\ + --output-delimiter=STRING use STRING as the output delimiter\n\ + the default is to use the input delimiter\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Use one, and only one of -b, -c or -f. Each LIST is made up of one\n\ +range, or many ranges separated by commas. Selected input is written\n\ +in the same order that it is read, and is written exactly once.\n\ +"), stdout); + fputs (_("\ +Each range is one of:\n\ +\n\ + N N'th byte, character or field, counted from 1\n\ + N- from N'th byte, character or field, to end of line\n\ + N-M from N'th to M'th (included) byte, character or field\n\ + -M from first to M'th (included) byte, character or field\n\ +\n\ +With no FILE, or when FILE is -, read standard input.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +static inline void +mark_range_start (size_t i) +{ + /* Record the fact that `i' is a range-start index. */ + void *ent_from_table = hash_insert (range_start_ht, (void*) i); + if (ent_from_table == NULL) + { + /* Insertion failed due to lack of memory. */ + xalloc_die (); + } + assert ((size_t) ent_from_table == i); +} + +static inline void +mark_printable_field (size_t i) +{ + size_t n = i / CHAR_BIT; + printable_field[n] |= (1 << (i % CHAR_BIT)); +} + +static inline bool +is_printable_field (size_t i) +{ + size_t n = i / CHAR_BIT; + return (printable_field[n] >> (i % CHAR_BIT)) & 1; +} + +static size_t +hash_int (const void *x, size_t tablesize) +{ +#ifdef UINTPTR_MAX + uintptr_t y = (uintptr_t) x; +#else + size_t y = (size_t) x; +#endif + return y % tablesize; +} + +static bool +hash_compare_ints (void const *x, void const *y) +{ + return (x == y) ? true : false; +} + +static bool +is_range_start_index (size_t i) +{ + return hash_lookup (range_start_ht, (void *) i) ? true : false; +} + +/* Return nonzero if the K'th field or byte is printable. + When returning nonzero, if RANGE_START is non-NULL, + set *RANGE_START to true if K is the beginning of a range, and to + false otherwise. */ + +static bool +print_kth (size_t k, bool *range_start) +{ + bool k_selected + = ((0 < eol_range_start && eol_range_start <= k) + || (k <= max_range_endpoint && is_printable_field (k))); + + bool is_selected = k_selected ^ complement; + if (range_start && is_selected) + *range_start = is_range_start_index (k); + + return is_selected; +} + +/* Comparison function for qsort to order the list of + struct range_pairs. */ +static int +compare_ranges (const void *a, const void *b) +{ + int a_start = ((const struct range_pair *) a)->lo; + int b_start = ((const struct range_pair *) b)->lo; + return a_start < b_start ? -1 : a_start > b_start; +} + +/* Given the list of field or byte range specifications FIELDSTR, set + MAX_RANGE_ENDPOINT and allocate and initialize the PRINTABLE_FIELD + array. If there is a right-open-ended range, set EOL_RANGE_START + to its starting index. FIELDSTR should be composed of one or more + numbers or ranges of numbers, separated by blanks or commas. + Incomplete ranges may be given: `-m' means `1-m'; `n-' means `n' + through end of line. Return true if FIELDSTR contains at least + one field specification, false otherwise. */ + +/* FIXME-someday: What if the user wants to cut out the 1,000,000-th + field of some huge input file? This function shouldn't have to + allocate a table of a million bits just so we can test every + field < 10^6 with an array dereference. Instead, consider using + an adaptive approach: if the range of selected fields is too large, + but only a few fields/byte-offsets are actually selected, use a + hash table. If the range of selected fields is too large, and + too many are selected, then resort to using the range-pairs (the + `rp' array) directly. */ + +static bool +set_fields (const char *fieldstr) +{ + size_t initial = 1; /* Value of first number in a range. */ + size_t value = 0; /* If nonzero, a number being accumulated. */ + bool dash_found = false; /* True if a '-' is found in this field. */ + bool field_found = false; /* True if at least one field spec + has been processed. */ + + struct range_pair *rp = NULL; + size_t n_rp = 0; + size_t n_rp_allocated = 0; + size_t i; + bool in_digits = false; + + /* Collect and store in RP the range end points. + It also sets EOL_RANGE_START if appropriate. */ + + for (;;) + { + if (*fieldstr == '-') + { + in_digits = false; + /* Starting a range. */ + if (dash_found) + FATAL_ERROR (_("invalid byte or field list")); + dash_found = true; + fieldstr++; + + if (value) + { + initial = value; + value = 0; + } + else + initial = 1; + } + else if (*fieldstr == ',' || isblank (*fieldstr) || *fieldstr == '\0') + { + in_digits = false; + /* Ending the string, or this field/byte sublist. */ + if (dash_found) + { + dash_found = false; + + /* A range. Possibilites: -n, m-n, n-. + In any case, `initial' contains the start of the range. */ + if (value == 0) + { + /* `n-'. From `initial' to end of line. */ + eol_range_start = initial; + field_found = true; + } + else + { + /* `m-n' or `-n' (1-n). */ + if (value < initial) + FATAL_ERROR (_("invalid byte or field list")); + + /* Is there already a range going to end of line? */ + if (eol_range_start != 0) + { + /* Yes. Is the new sequence already contained + in the old one? If so, no processing is + necessary. */ + if (initial < eol_range_start) + { + /* No, the new sequence starts before the + old. Does the old range going to end of line + extend into the new range? */ + if (eol_range_start <= value) + { + /* Yes. Simply move the end of line marker. */ + eol_range_start = initial; + } + else + { + /* No. A simple range, before and disjoint from + the range going to end of line. Fill it. */ + ADD_RANGE_PAIR (rp, initial, value); + } + + /* In any case, some fields were selected. */ + field_found = true; + } + } + else + { + /* There is no range going to end of line. */ + ADD_RANGE_PAIR (rp, initial, value); + field_found = true; + } + value = 0; + } + } + else if (value != 0) + { + /* A simple field number, not a range. */ + ADD_RANGE_PAIR (rp, value, value); + value = 0; + field_found = true; + } + + if (*fieldstr == '\0') + { + break; + } + + fieldstr++; + } + else if (ISDIGIT (*fieldstr)) + { + /* Record beginning of digit string, in case we have to + complain about it. */ + static char const *num_start; + if (!in_digits || !num_start) + num_start = fieldstr; + in_digits = true; + + /* Detect overflow. */ + if (!DECIMAL_DIGIT_ACCUMULATE (value, *fieldstr - '0', size_t)) + { + /* In case the user specified -c4294967296,22, + complain only about the first number. */ + /* Determine the length of the offending number. */ + size_t len = strspn (num_start, "0123456789"); + char *bad_num = xstrndup (num_start, len); + if (operating_mode == byte_mode) + error (0, 0, + _("byte offset %s is too large"), quote (bad_num)); + else + error (0, 0, + _("field number %s is too large"), quote (bad_num)); + free (bad_num); + exit (EXIT_FAILURE); + } + + fieldstr++; + } + else + FATAL_ERROR (_("invalid byte or field list")); + } + + max_range_endpoint = 0; + for (i = 0; i < n_rp; i++) + { + if (rp[i].hi > max_range_endpoint) + max_range_endpoint = rp[i].hi; + } + + /* Allocate an array large enough so that it may be indexed by + the field numbers corresponding to all finite ranges + (i.e. `2-6' or `-4', but not `5-') in FIELDSTR. */ + + printable_field = xzalloc (max_range_endpoint / CHAR_BIT + 1); + + qsort (rp, n_rp, sizeof (rp[0]), compare_ranges); + + /* Set the array entries corresponding to integers in the ranges of RP. */ + for (i = 0; i < n_rp; i++) + { + size_t j; + size_t rsi_candidate; + + /* Record the range-start indices, i.e., record each start + index that is not part of any other (lo..hi] range. */ + rsi_candidate = complement ? rp[i].hi + 1 : rp[i].lo; + if (output_delimiter_specified + && !is_printable_field (rsi_candidate)) + mark_range_start (rsi_candidate); + + for (j = rp[i].lo; j <= rp[i].hi; j++) + mark_printable_field (j); + } + + if (output_delimiter_specified + && !complement + && eol_range_start && !is_printable_field (eol_range_start)) + mark_range_start (eol_range_start); + + free (rp); + + return field_found; +} + +/* Read from stream STREAM, printing to standard output any selected bytes. */ + +static void +cut_bytes (FILE *stream) +{ + size_t byte_idx; /* Number of bytes in the line so far. */ + /* Whether to begin printing delimiters between ranges for the current line. + Set after we've begun printing data corresponding to the first range. */ + bool print_delimiter; + + byte_idx = 0; + print_delimiter = false; + while (1) + { + int c; /* Each character from the file. */ + + c = getc (stream); + + if (c == '\n') + { + putchar ('\n'); + byte_idx = 0; + print_delimiter = false; + } + else if (c == EOF) + { + if (byte_idx > 0) + putchar ('\n'); + break; + } + else + { + bool range_start; + bool *rs = output_delimiter_specified ? &range_start : NULL; + if (print_kth (++byte_idx, rs)) + { + if (rs && *rs && print_delimiter) + { + fwrite (output_delimiter_string, sizeof (char), + output_delimiter_length, stdout); + } + print_delimiter = true; + putchar (c); + } + } + } +} + +/* Read from stream STREAM, printing to standard output any selected fields. */ + +static void +cut_fields (FILE *stream) +{ + int c; + size_t field_idx = 1; + bool found_any_selected_field = false; + bool buffer_first_field; + + c = getc (stream); + if (c == EOF) + return; + + ungetc (c, stream); + + /* To support the semantics of the -s flag, we may have to buffer + all of the first field to determine whether it is `delimited.' + But that is unnecessary if all non-delimited lines must be printed + and the first field has been selected, or if non-delimited lines + must be suppressed and the first field has *not* been selected. + That is because a non-delimited line has exactly one field. */ + buffer_first_field = (suppress_non_delimited ^ !print_kth (1, NULL)); + + while (1) + { + if (field_idx == 1 && buffer_first_field) + { + ssize_t len; + size_t n_bytes; + + len = getndelim2 (&field_1_buffer, &field_1_bufsize, 0, + GETNLINE_NO_LIMIT, delim, '\n', stream); + if (len < 0) + { + free (field_1_buffer); + field_1_buffer = NULL; + if (ferror (stream) || feof (stream)) + break; + xalloc_die (); + } + + n_bytes = len; + assert (n_bytes != 0); + + /* If the first field extends to the end of line (it is not + delimited) and we are printing all non-delimited lines, + print this one. */ + if (to_uchar (field_1_buffer[n_bytes - 1]) != delim) + { + if (suppress_non_delimited) + { + /* Empty. */ + } + else + { + fwrite (field_1_buffer, sizeof (char), n_bytes, stdout); + /* Make sure the output line is newline terminated. */ + if (field_1_buffer[n_bytes - 1] != '\n') + putchar ('\n'); + } + continue; + } + if (print_kth (1, NULL)) + { + /* Print the field, but not the trailing delimiter. */ + fwrite (field_1_buffer, sizeof (char), n_bytes - 1, stdout); + found_any_selected_field = true; + } + ++field_idx; + } + + if (c != EOF) + { + if (print_kth (field_idx, NULL)) + { + if (found_any_selected_field) + { + fwrite (output_delimiter_string, sizeof (char), + output_delimiter_length, stdout); + } + found_any_selected_field = true; + + while ((c = getc (stream)) != delim && c != '\n' && c != EOF) + { + putchar (c); + } + } + else + { + while ((c = getc (stream)) != delim && c != '\n' && c != EOF) + { + /* Empty. */ + } + } + } + + if (c == '\n') + { + c = getc (stream); + if (c != EOF) + { + ungetc (c, stream); + c = '\n'; + } + } + + if (c == delim) + ++field_idx; + else if (c == '\n' || c == EOF) + { + if (found_any_selected_field + || !(suppress_non_delimited && field_idx == 1)) + putchar ('\n'); + if (c == EOF) + break; + field_idx = 1; + found_any_selected_field = false; + } + } +} + +static void +cut_stream (FILE *stream) +{ + if (operating_mode == byte_mode) + cut_bytes (stream); + else + cut_fields (stream); +} + +/* Process file FILE to standard output. + Return true if successful. */ + +static bool +cut_file (char const *file) +{ + FILE *stream; + + if (STREQ (file, "-")) + { + have_read_stdin = true; + stream = stdin; + } + else + { + stream = fopen (file, "r"); + if (stream == NULL) + { + error (0, errno, "%s", file); + return false; + } + } + + cut_stream (stream); + + if (ferror (stream)) + { + error (0, errno, "%s", file); + return false; + } + if (STREQ (file, "-")) + clearerr (stream); /* Also clear EOF. */ + else if (fclose (stream) == EOF) + { + error (0, errno, "%s", file); + return false; + } + return true; +} + +int +main (int argc, char **argv) +{ + int optc; + bool ok; + bool delim_specified = false; + char *spec_list_string IF_LINT(= NULL); + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + operating_mode = undefined_mode; + + /* By default, all non-delimited lines are printed. */ + suppress_non_delimited = false; + + delim = '\0'; + have_read_stdin = false; + + while ((optc = getopt_long (argc, argv, "b:c:d:f:ns", longopts, NULL)) != -1) + { + switch (optc) + { + case 'b': + case 'c': + /* Build the byte list. */ + if (operating_mode != undefined_mode) + FATAL_ERROR (_("only one type of list may be specified")); + operating_mode = byte_mode; + spec_list_string = optarg; + break; + + case 'f': + /* Build the field list. */ + if (operating_mode != undefined_mode) + FATAL_ERROR (_("only one type of list may be specified")); + operating_mode = field_mode; + spec_list_string = optarg; + break; + + case 'd': + /* New delimiter. */ + /* Interpret -d '' to mean `use the NUL byte as the delimiter.' */ + if (optarg[0] != '\0' && optarg[1] != '\0') + FATAL_ERROR (_("the delimiter must be a single character")); + delim = optarg[0]; + delim_specified = true; + break; + + case OUTPUT_DELIMITER_OPTION: + output_delimiter_specified = true; + /* Interpret --output-delimiter='' to mean + `use the NUL byte as the delimiter.' */ + output_delimiter_length = (optarg[0] == '\0' + ? 1 : strlen (optarg)); + output_delimiter_string = xstrdup (optarg); + break; + + case 'n': + break; + + case 's': + suppress_non_delimited = true; + break; + + case COMPLEMENT_OPTION: + complement = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (operating_mode == undefined_mode) + FATAL_ERROR (_("you must specify a list of bytes, characters, or fields")); + + if (delim != '\0' && operating_mode != field_mode) + FATAL_ERROR (_("an input delimiter may be specified only\ + when operating on fields")); + + if (suppress_non_delimited && operating_mode != field_mode) + FATAL_ERROR (_("suppressing non-delimited lines makes sense\n\ +\tonly when operating on fields")); + + if (output_delimiter_specified) + { + range_start_ht = hash_initialize (HT_RANGE_START_INDEX_INITIAL_CAPACITY, + NULL, hash_int, + hash_compare_ints, NULL); + if (range_start_ht == NULL) + xalloc_die (); + + } + + if (! set_fields (spec_list_string)) + { + if (operating_mode == field_mode) + FATAL_ERROR (_("missing list of fields")); + else + FATAL_ERROR (_("missing list of positions")); + } + + if (!delim_specified) + delim = '\t'; + + if (output_delimiter_string == NULL) + { + static char dummy[2]; + dummy[0] = delim; + dummy[1] = '\0'; + output_delimiter_string = dummy; + output_delimiter_length = 1; + } + + if (optind == argc) + ok = cut_file ("-"); + else + for (ok = true; optind < argc; optind++) + ok &= cut_file (argv[optind]); + + if (range_start_ht) + hash_free (range_start_ht); + + if (have_read_stdin && fclose (stdin) == EOF) + { + error (0, errno, "-"); + ok = false; + } + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/date.c b/src/date.c new file mode 100644 index 0000000..c64ab1c --- /dev/null +++ b/src/date.c @@ -0,0 +1,563 @@ +/* date - print or set the system date and time + Copyright (C) 1989-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + David MacKenzie */ + +#include +#include +#include +#include +#if HAVE_LANGINFO_CODESET +# include +#endif + +#include "system.h" +#include "argmatch.h" +#include "error.h" +#include "getdate.h" +#include "getline.h" +#include "inttostr.h" +#include "posixtm.h" +#include "quote.h" +#include "stat-time.h" +#include "fprintftime.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "date" + +#define AUTHORS "David MacKenzie" + +int putenv (); + +static bool show_date (const char *format, struct timespec when); + +enum Time_spec +{ + /* Display only the date. */ + TIME_SPEC_DATE, + /* Display date, hours, minutes, and seconds. */ + TIME_SPEC_SECONDS, + /* Similar, but display nanoseconds. */ + TIME_SPEC_NS, + + /* Put these last, since they aren't valid for --rfc-3339. */ + + /* Display date and hour. */ + TIME_SPEC_HOURS, + /* Display date, hours, and minutes. */ + TIME_SPEC_MINUTES +}; + +static char const *const time_spec_string[] = +{ + /* Put "hours" and "minutes" first, since they aren't valid for + --rfc-3339. */ + "hours", "minutes", + "date", "seconds", "ns", NULL +}; +static enum Time_spec const time_spec[] = +{ + TIME_SPEC_HOURS, TIME_SPEC_MINUTES, + TIME_SPEC_DATE, TIME_SPEC_SECONDS, TIME_SPEC_NS +}; +ARGMATCH_VERIFY (time_spec_string, time_spec); + +/* A format suitable for Internet RFC 2822. */ +static char const rfc_2822_format[] = "%a, %d %b %Y %H:%M:%S %z"; + +/* The name this program was run with, for error messages. */ +char *program_name; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + RFC_3339_OPTION = CHAR_MAX + 1 +}; + +static char const short_options[] = "d:f:I::r:Rs:u"; + +static struct option const long_options[] = +{ + {"date", required_argument, NULL, 'd'}, + {"file", required_argument, NULL, 'f'}, + {"iso-8601", optional_argument, NULL, 'I'}, /* Deprecated. */ + {"reference", required_argument, NULL, 'r'}, + {"rfc-822", no_argument, NULL, 'R'}, + {"rfc-2822", no_argument, NULL, 'R'}, + {"rfc-3339", required_argument, NULL, RFC_3339_OPTION}, + {"set", required_argument, NULL, 's'}, + {"uct", no_argument, NULL, 'u'}, + {"utc", no_argument, NULL, 'u'}, + {"universal", no_argument, NULL, 'u'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +#if LOCALTIME_CACHE +# define TZSET tzset () +#else +# define TZSET /* empty */ +#endif + +#ifdef _DATE_FMT +# define DATE_FMT_LANGINFO() nl_langinfo (_DATE_FMT) +#else +# define DATE_FMT_LANGINFO() "" +#endif + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [+FORMAT]\n\ + or: %s [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]\n\ +"), + program_name, program_name); + fputs (_("\ +Display the current time in the given FORMAT, or set the system date.\n\ +\n\ + -d, --date=STRING display time described by STRING, not `now'\n\ + -f, --file=DATEFILE like --date once for each line of DATEFILE\n\ +"), stdout); + fputs (_("\ + -r, --reference=FILE display the last modification time of FILE\n\ + -R, --rfc-2822 output date and time in RFC 2822 format.\n\ + Example: Mon, 07 Aug 2006 12:34:56 -0600\n\ +"), stdout); + fputs (_("\ + --rfc-3339=TIMESPEC output date and time in RFC 3339 format.\n\ + TIMESPEC=`date', `seconds', or `ns' for\n\ + date and time to the indicated precision.\n\ + Date and time components are separated by\n\ + a single space: 2006-08-07 12:34:56-06:00\n\ + -s, --set=STRING set time described by STRING\n\ + -u, --utc, --universal print or set Coordinated Universal Time\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +FORMAT controls the output. The only valid option for the second form\n\ +specifies Coordinated Universal Time. Interpreted sequences are:\n\ +\n\ + %% a literal %\n\ + %a locale's abbreviated weekday name (e.g., Sun)\n\ +"), stdout); + fputs (_("\ + %A locale's full weekday name (e.g., Sunday)\n\ + %b locale's abbreviated month name (e.g., Jan)\n\ + %B locale's full month name (e.g., January)\n\ + %c locale's date and time (e.g., Thu Mar 3 23:05:25 2005)\n\ +"), stdout); + fputs (_("\ + %C century; like %Y, except omit last two digits (e.g., 21)\n\ + %d day of month (e.g, 01)\n\ + %D date; same as %m/%d/%y\n\ + %e day of month, space padded; same as %_d\n\ +"), stdout); + fputs (_("\ + %F full date; same as %Y-%m-%d\n\ + %g last two digits of year of ISO week number (see %G)\n\ + %G year of ISO week number (see %V); normally useful only with %V\n\ +"), stdout); + fputs (_("\ + %h same as %b\n\ + %H hour (00..23)\n\ + %I hour (01..12)\n\ + %j day of year (001..366)\n\ +"), stdout); + fputs (_("\ + %k hour ( 0..23)\n\ + %l hour ( 1..12)\n\ + %m month (01..12)\n\ + %M minute (00..59)\n\ +"), stdout); + fputs (_("\ + %n a newline\n\ + %N nanoseconds (000000000..999999999)\n\ + %p locale's equivalent of either AM or PM; blank if not known\n\ + %P like %p, but lower case\n\ + %r locale's 12-hour clock time (e.g., 11:11:04 PM)\n\ + %R 24-hour hour and minute; same as %H:%M\n\ + %s seconds since 1970-01-01 00:00:00 UTC\n\ +"), stdout); + fputs (_("\ + %S second (00..60)\n\ + %t a tab\n\ + %T time; same as %H:%M:%S\n\ + %u day of week (1..7); 1 is Monday\n\ +"), stdout); + fputs (_("\ + %U week number of year, with Sunday as first day of week (00..53)\n\ + %V ISO week number, with Monday as first day of week (01..53)\n\ + %w day of week (0..6); 0 is Sunday\n\ + %W week number of year, with Monday as first day of week (00..53)\n\ +"), stdout); + fputs (_("\ + %x locale's date representation (e.g., 12/31/99)\n\ + %X locale's time representation (e.g., 23:13:48)\n\ + %y last two digits of year (00..99)\n\ + %Y year\n\ +"), stdout); + fputs (_("\ + %z +hhmm numeric timezone (e.g., -0400)\n\ + %:z +hh:mm numeric timezone (e.g., -04:00)\n\ + %::z +hh:mm:ss numeric time zone (e.g., -04:00:00)\n\ + %:::z numeric time zone with : to necessary precision (e.g., -04, +05:30)\n\ + %Z alphabetic time zone abbreviation (e.g., EDT)\n\ +\n\ +By default, date pads numeric fields with zeroes.\n\ +"), stdout); + fputs (_("\ +The following optional flags may follow `%':\n\ +\n\ + - (hyphen) do not pad the field\n\ + _ (underscore) pad with spaces\n\ + 0 (zero) pad with zeros\n\ + ^ use upper case if possible\n\ + # use opposite case if possible\n\ +"), stdout); + fputs (_("\ +\n\ +After any flags comes an optional field width, as a decimal number;\n\ +then an optional modifier, which is either\n\ +E to use the locale's alternate representations if available, or\n\ +O to use the locale's alternate numeric symbols if available.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Parse each line in INPUT_FILENAME as with --date and display each + resulting time and date. If the file cannot be opened, tell why + then exit. Issue a diagnostic for any lines that cannot be parsed. + Return true if successful. */ + +static bool +batch_convert (const char *input_filename, const char *format) +{ + bool ok; + FILE *in_stream; + char *line; + size_t buflen; + struct timespec when; + + if (STREQ (input_filename, "-")) + { + input_filename = _("standard input"); + in_stream = stdin; + } + else + { + in_stream = fopen (input_filename, "r"); + if (in_stream == NULL) + { + error (EXIT_FAILURE, errno, "%s", quote (input_filename)); + } + } + + line = NULL; + buflen = 0; + ok = true; + while (1) + { + ssize_t line_length = getline (&line, &buflen, in_stream); + if (line_length < 0) + { + /* FIXME: detect/handle error here. */ + break; + } + + if (! get_date (&when, line, NULL)) + { + if (line[line_length - 1] == '\n') + line[line_length - 1] = '\0'; + error (0, 0, _("invalid date %s"), quote (line)); + ok = false; + } + else + { + ok &= show_date (format, when); + } + } + + if (fclose (in_stream) == EOF) + error (EXIT_FAILURE, errno, "%s", quote (input_filename)); + + free (line); + + return ok; +} + +int +main (int argc, char **argv) +{ + int optc; + const char *datestr = NULL; + const char *set_datestr = NULL; + struct timespec when; + bool set_date = false; + char const *format = NULL; + char *batch_file = NULL; + char *reference = NULL; + struct stat refstats; + bool ok; + int option_specified_date; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, short_options, long_options, NULL)) + != -1) + { + char const *new_format = NULL; + + switch (optc) + { + case 'd': + datestr = optarg; + break; + case 'f': + batch_file = optarg; + break; + case RFC_3339_OPTION: + { + static char const rfc_3339_format[][32] = + { + "%Y-%m-%d", + "%Y-%m-%d %H:%M:%S%:z", + "%Y-%m-%d %H:%M:%S.%N%:z" + }; + enum Time_spec i = + XARGMATCH ("--rfc-3339", optarg, + time_spec_string + 2, time_spec + 2); + new_format = rfc_3339_format[i]; + break; + } + case 'I': + { + static char const iso_8601_format[][32] = + { + "%Y-%m-%d", + "%Y-%m-%dT%H:%M:%S%z", + "%Y-%m-%dT%H:%M:%S,%N%z", + "%Y-%m-%dT%H%z", + "%Y-%m-%dT%H:%M%z" + }; + enum Time_spec i = + (optarg + ? XARGMATCH ("--iso-8601", optarg, time_spec_string, time_spec) + : TIME_SPEC_DATE); + new_format = iso_8601_format[i]; + break; + } + case 'r': + reference = optarg; + break; + case 'R': + new_format = rfc_2822_format; + break; + case 's': + set_datestr = optarg; + set_date = true; + break; + case 'u': + /* POSIX says that `date -u' is equivalent to setting the TZ + environment variable, so this option should do nothing other + than setting TZ. */ + if (putenv ("TZ=UTC0") != 0) + xalloc_die (); + TZSET; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + + if (new_format) + { + if (format) + error (EXIT_FAILURE, 0, _("multiple output formats specified")); + format = new_format; + } + } + + option_specified_date = ((datestr ? 1 : 0) + + (batch_file ? 1 : 0) + + (reference ? 1 : 0)); + + if (option_specified_date > 1) + { + error (0, 0, + _("the options to specify dates for printing are mutually exclusive")); + usage (EXIT_FAILURE); + } + + if (set_date && option_specified_date) + { + error (0, 0, + _("the options to print and set the time may not be used together")); + usage (EXIT_FAILURE); + } + + if (optind < argc) + { + if (optind + 1 < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); + usage (EXIT_FAILURE); + } + + if (argv[optind][0] == '+') + { + if (format) + error (EXIT_FAILURE, 0, _("multiple output formats specified")); + format = argv[optind++] + 1; + } + else if (set_date || option_specified_date) + { + error (0, 0, + _("the argument %s lacks a leading `+';\n" + "When using an option to specify date(s), any non-option\n" + "argument must be a format string beginning with `+'."), + quote (argv[optind])); + usage (EXIT_FAILURE); + } + } + + if (!format) + { + format = DATE_FMT_LANGINFO (); + if (! *format) + { + /* Do not wrap the following literal format string with _(...). + For example, suppose LC_ALL is unset, LC_TIME="POSIX", + and LANG="ko_KR". In that case, POSIX says that LC_TIME + determines the format and contents of date and time strings + written by date, which means "date" must generate output + using the POSIX locale; but adding _() would cause "date" + to use a Korean translation of the format. */ + format = "%a %b %e %H:%M:%S %Z %Y"; + } + } + + if (batch_file != NULL) + ok = batch_convert (batch_file, format); + else + { + bool valid_date = true; + ok = true; + + if (!option_specified_date && !set_date) + { + if (optind < argc) + { + /* Prepare to set system clock to the specified date/time + given in the POSIX-format. */ + set_date = true; + datestr = argv[optind]; + valid_date = posixtime (&when.tv_sec, + datestr, + (PDS_TRAILING_YEAR + | PDS_CENTURY | PDS_SECONDS)); + when.tv_nsec = 0; /* FIXME: posixtime should set this. */ + } + else + { + /* Prepare to print the current date/time. */ + gettime (&when); + } + } + else + { + /* (option_specified_date || set_date) */ + if (reference != NULL) + { + if (stat (reference, &refstats) != 0) + error (EXIT_FAILURE, errno, "%s", reference); + when = get_stat_mtime (&refstats); + } + else + { + if (set_datestr) + datestr = set_datestr; + valid_date = get_date (&when, datestr, NULL); + } + } + + if (! valid_date) + error (EXIT_FAILURE, 0, _("invalid date %s"), quote (datestr)); + + if (set_date) + { + /* Set the system clock to the specified date, then regardless of + the success of that operation, format and print that date. */ + if (settime (&when) != 0) + { + error (0, errno, _("cannot set date")); + ok = false; + } + } + + ok &= show_date (format, when); + } + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} + +/* Display the date and/or time in WHEN according to the format specified + in FORMAT, followed by a newline. Return true if successful. */ + +static bool +show_date (const char *format, struct timespec when) +{ + struct tm *tm; + + tm = localtime (&when.tv_sec); + if (! tm) + { + char buf[INT_BUFSIZE_BOUND (intmax_t)]; + error (0, 0, _("time %s is out of range"), + (TYPE_SIGNED (time_t) + ? imaxtostr (when.tv_sec, buf) + : umaxtostr (when.tv_sec, buf))); + return false; + } + + if (format == rfc_2822_format) + setlocale (LC_TIME, "C"); + fprintftime (stdout, format, tm, 0, when.tv_nsec); + fputc ('\n', stdout); + if (format == rfc_2822_format) + setlocale (LC_TIME, ""); + + return true; +} diff --git a/src/dcgen b/src/dcgen new file mode 100755 index 0000000..5df3c56 --- /dev/null +++ b/src/dcgen @@ -0,0 +1,57 @@ +#!/usr/bin/perl -w +# dcgen -- convert dircolors.hin to dircolors.h. + +# Copyright (C) 1996, 1998, 2004, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +# written by Jim Meyering + +require 5.002; +use strict; +(my $ME = $0) =~ s|.*/||; + +# A global destructor to close standard output with error checking. +sub END +{ + defined fileno STDOUT + or return; + close STDOUT + and return; + warn "$ME: closing standard output: $!\n"; + $? ||= 1; +} + +my @line; +while (<>) + { + chomp; + s/[[:blank:]]+/ /g; + $_ + and push @line, $_; + } + +my $indent = ' '; + +print "static char const G_line[] =\n{\n"; +foreach (@line) + { + s/./'$&',/g; + s/'\\'/'\\\\'/g; + s/'''/'\\''/g; + print "$indent${_}0,\n"; + } +print "};\n"; diff --git a/src/dd.c b/src/dd.c new file mode 100644 index 0000000..27a4a08 --- /dev/null +++ b/src/dd.c @@ -0,0 +1,1744 @@ +/* dd -- convert a file while copying it. + Copyright (C) 85, 90, 91, 1995-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Paul Rubin, David MacKenzie, and Stuart Kemp. */ + +#include + +#define SWAB_ALIGN_OFFSET 2 + +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "fd-reopen.h" +#include "gethrxtime.h" +#include "getpagesize.h" +#include "human.h" +#include "long-options.h" +#include "quote.h" +#include "xstrtol.h" +#include "xtime.h" + +static void process_signals (void); + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "dd" + +#define AUTHORS "Paul Rubin", "David MacKenzie", "Stuart Kemp" + +/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is + present. SA_NODEFER and SA_RESETHAND are XSI extensions. */ +#ifndef SA_NOCLDSTOP +# define SA_NOCLDSTOP 0 +# define sigprocmask(How, Set, Oset) /* empty */ +# define sigset_t int +# if ! HAVE_SIGINTERRUPT +# define siginterrupt(sig, flag) /* empty */ +# endif +#endif +#ifndef SA_NODEFER +# define SA_NODEFER 0 +#endif +#ifndef SA_RESETHAND +# define SA_RESETHAND 0 +#endif + +#ifndef SIGINFO +# define SIGINFO SIGUSR1 +#endif + +#if ! HAVE_FDATASYNC +# define fdatasync(fd) (errno = ENOSYS, -1) +#endif + +#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 + +/* How many bytes to add to the input and output block sizes before invoking + malloc. See dd_copy for details. INPUT_BLOCK_SLOP must be no less than + OUTPUT_BLOCK_SLOP. */ +#define INPUT_BLOCK_SLOP (2 * SWAB_ALIGN_OFFSET + 2 * page_size - 1) +#define OUTPUT_BLOCK_SLOP (page_size - 1) + +/* Maximum blocksize for the given SLOP. + Keep it smaller than SIZE_MAX - SLOP, so that we can + allocate buffers that size. Keep it smaller than SSIZE_MAX, for + the benefit of system calls like "read". And keep it smaller than + OFF_T_MAX, for the benefit of the large-offset seek code. */ +#define MAX_BLOCKSIZE(slop) MIN (SIZE_MAX - (slop), MIN (SSIZE_MAX, OFF_T_MAX)) + +/* Conversions bit masks. */ +enum + { + C_ASCII = 01, + + C_EBCDIC = 02, + C_IBM = 04, + C_BLOCK = 010, + C_UNBLOCK = 020, + C_LCASE = 040, + C_UCASE = 0100, + C_SWAB = 0200, + C_NOERROR = 0400, + C_NOTRUNC = 01000, + C_SYNC = 02000, + + /* Use separate input and output buffers, and combine partial + input blocks. */ + C_TWOBUFS = 04000, + + C_NOCREAT = 010000, + C_EXCL = 020000, + C_FDATASYNC = 040000, + C_FSYNC = 0100000 + }; + +/* Status bit masks. */ +enum + { + STATUS_NOXFER = 01 + }; + +/* The name this program was run with. */ +char *program_name; + +/* The name of the input file, or NULL for the standard input. */ +static char const *input_file = NULL; + +/* The name of the output file, or NULL for the standard output. */ +static char const *output_file = NULL; + +/* The page size on this host. */ +static size_t page_size; + +/* The number of bytes in which atomic reads are done. */ +static size_t input_blocksize = 0; + +/* The number of bytes in which atomic writes are done. */ +static size_t output_blocksize = 0; + +/* Conversion buffer size, in bytes. 0 prevents conversions. */ +static size_t conversion_blocksize = 0; + +/* Skip this many records of `input_blocksize' bytes before input. */ +static uintmax_t skip_records = 0; + +/* Skip this many records of `output_blocksize' bytes before output. */ +static uintmax_t seek_records = 0; + +/* Copy only this many records. The default is effectively infinity. */ +static uintmax_t max_records = (uintmax_t) -1; + +/* Bit vector of conversions to apply. */ +static int conversions_mask = 0; + +/* Open flags for the input and output files. */ +static int input_flags = 0; +static int output_flags = 0; + +/* Status flags for what is printed to stderr. */ +static int status_flags = 0; + +/* If nonzero, filter characters through the translation table. */ +static bool translation_needed = false; + +/* Number of partial blocks written. */ +static uintmax_t w_partial = 0; + +/* Number of full blocks written. */ +static uintmax_t w_full = 0; + +/* Number of partial blocks read. */ +static uintmax_t r_partial = 0; + +/* Number of full blocks read. */ +static uintmax_t r_full = 0; + +/* Number of bytes written. */ +static uintmax_t w_bytes = 0; + +/* Time that dd started. */ +static xtime_t start_time; + +/* True if input is seekable. */ +static bool input_seekable; + +/* Error number corresponding to initial attempt to lseek input. + If ESPIPE, do not issue any more diagnostics about it. */ +static int input_seek_errno; + +/* File offset of the input, in bytes, along with a flag recording + whether it overflowed. The offset is valid only if the input is + seekable and if the offset has not overflowed. */ +static uintmax_t input_offset; +static bool input_offset_overflow; + +/* Records truncated by conv=block. */ +static uintmax_t r_truncate = 0; + +/* Output representation of newline and space characters. + They change if we're converting to EBCDIC. */ +static char newline_character = '\n'; +static char space_character = ' '; + +/* Output buffer. */ +static char *obuf; + +/* Current index into `obuf'. */ +static size_t oc = 0; + +/* Index into current line, for `conv=block' and `conv=unblock'. */ +static size_t col = 0; + +/* The set of signals that are caught. */ +static sigset_t caught_signals; + +/* If nonzero, the value of the pending fatal signal. */ +static sig_atomic_t volatile interrupt_signal; + +/* A count of the number of pending info signals that have been received. */ +static sig_atomic_t volatile info_signal_count; + +/* A longest symbol in the struct symbol_values tables below. */ +#define LONGEST_SYMBOL "fdatasync" + +/* A symbol and the corresponding integer value. */ +struct symbol_value +{ + char symbol[sizeof LONGEST_SYMBOL]; + int value; +}; + +/* Conversion symbols, for conv="...". */ +static struct symbol_value const 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. */ + {"nocreat", C_NOCREAT}, /* Do not create output file. */ + {"excl", C_EXCL}, /* Fail if the output file already exists. */ + {"notrunc", C_NOTRUNC}, /* Do not truncate output file. */ + {"sync", C_SYNC}, /* Pad input records to ibs with NULs. */ + {"fdatasync", C_FDATASYNC}, /* Synchronize output data before finishing. */ + {"fsync", C_FSYNC}, /* Also synchronize output metadata. */ + {"", 0} +}; + +/* Flags, for iflag="..." and oflag="...". */ +static struct symbol_value const flags[] = +{ + {"append", O_APPEND}, + {"binary", O_BINARY}, + {"direct", O_DIRECT}, + {"directory", O_DIRECTORY}, + {"dsync", O_DSYNC}, + {"noatime", O_NOATIME}, + {"noctty", O_NOCTTY}, + {"nofollow", HAVE_WORKING_O_NOFOLLOW ? O_NOFOLLOW : 0}, + {"nolinks", O_NOLINKS}, + {"nonblock", O_NONBLOCK}, + {"sync", O_SYNC}, + {"text", O_TEXT}, + {"", 0} +}; + +/* Status, for status="...". */ +static struct symbol_value const statuses[] = +{ + {"noxfer", STATUS_NOXFER}, + {"", 0} +}; + +/* Translation table formed by applying successive transformations. */ +static unsigned char trans_table[256]; + +static char const ascii_to_ebcdic[] = +{ + '\000', '\001', '\002', '\003', '\067', '\055', '\056', '\057', + '\026', '\005', '\045', '\013', '\014', '\015', '\016', '\017', + '\020', '\021', '\022', '\023', '\074', '\075', '\062', '\046', + '\030', '\031', '\077', '\047', '\034', '\035', '\036', '\037', + '\100', '\117', '\177', '\173', '\133', '\154', '\120', '\175', + '\115', '\135', '\134', '\116', '\153', '\140', '\113', '\141', + '\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367', + '\370', '\371', '\172', '\136', '\114', '\176', '\156', '\157', + '\174', '\301', '\302', '\303', '\304', '\305', '\306', '\307', + '\310', '\311', '\321', '\322', '\323', '\324', '\325', '\326', + '\327', '\330', '\331', '\342', '\343', '\344', '\345', '\346', + '\347', '\350', '\351', '\112', '\340', '\132', '\137', '\155', + '\171', '\201', '\202', '\203', '\204', '\205', '\206', '\207', + '\210', '\211', '\221', '\222', '\223', '\224', '\225', '\226', + '\227', '\230', '\231', '\242', '\243', '\244', '\245', '\246', + '\247', '\250', '\251', '\300', '\152', '\320', '\241', '\007', + '\040', '\041', '\042', '\043', '\044', '\025', '\006', '\027', + '\050', '\051', '\052', '\053', '\054', '\011', '\012', '\033', + '\060', '\061', '\032', '\063', '\064', '\065', '\066', '\010', + '\070', '\071', '\072', '\073', '\004', '\024', '\076', '\341', + '\101', '\102', '\103', '\104', '\105', '\106', '\107', '\110', + '\111', '\121', '\122', '\123', '\124', '\125', '\126', '\127', + '\130', '\131', '\142', '\143', '\144', '\145', '\146', '\147', + '\150', '\151', '\160', '\161', '\162', '\163', '\164', '\165', + '\166', '\167', '\170', '\200', '\212', '\213', '\214', '\215', + '\216', '\217', '\220', '\232', '\233', '\234', '\235', '\236', + '\237', '\240', '\252', '\253', '\254', '\255', '\256', '\257', + '\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267', + '\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277', + '\312', '\313', '\314', '\315', '\316', '\317', '\332', '\333', + '\334', '\335', '\336', '\337', '\352', '\353', '\354', '\355', + '\356', '\357', '\372', '\373', '\374', '\375', '\376', '\377' +}; + +static char const ascii_to_ibm[] = +{ + '\000', '\001', '\002', '\003', '\067', '\055', '\056', '\057', + '\026', '\005', '\045', '\013', '\014', '\015', '\016', '\017', + '\020', '\021', '\022', '\023', '\074', '\075', '\062', '\046', + '\030', '\031', '\077', '\047', '\034', '\035', '\036', '\037', + '\100', '\132', '\177', '\173', '\133', '\154', '\120', '\175', + '\115', '\135', '\134', '\116', '\153', '\140', '\113', '\141', + '\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367', + '\370', '\371', '\172', '\136', '\114', '\176', '\156', '\157', + '\174', '\301', '\302', '\303', '\304', '\305', '\306', '\307', + '\310', '\311', '\321', '\322', '\323', '\324', '\325', '\326', + '\327', '\330', '\331', '\342', '\343', '\344', '\345', '\346', + '\347', '\350', '\351', '\255', '\340', '\275', '\137', '\155', + '\171', '\201', '\202', '\203', '\204', '\205', '\206', '\207', + '\210', '\211', '\221', '\222', '\223', '\224', '\225', '\226', + '\227', '\230', '\231', '\242', '\243', '\244', '\245', '\246', + '\247', '\250', '\251', '\300', '\117', '\320', '\241', '\007', + '\040', '\041', '\042', '\043', '\044', '\025', '\006', '\027', + '\050', '\051', '\052', '\053', '\054', '\011', '\012', '\033', + '\060', '\061', '\032', '\063', '\064', '\065', '\066', '\010', + '\070', '\071', '\072', '\073', '\004', '\024', '\076', '\341', + '\101', '\102', '\103', '\104', '\105', '\106', '\107', '\110', + '\111', '\121', '\122', '\123', '\124', '\125', '\126', '\127', + '\130', '\131', '\142', '\143', '\144', '\145', '\146', '\147', + '\150', '\151', '\160', '\161', '\162', '\163', '\164', '\165', + '\166', '\167', '\170', '\200', '\212', '\213', '\214', '\215', + '\216', '\217', '\220', '\232', '\233', '\234', '\235', '\236', + '\237', '\240', '\252', '\253', '\254', '\255', '\256', '\257', + '\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267', + '\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277', + '\312', '\313', '\314', '\315', '\316', '\317', '\332', '\333', + '\334', '\335', '\336', '\337', '\352', '\353', '\354', '\355', + '\356', '\357', '\372', '\373', '\374', '\375', '\376', '\377' +}; + +static char const ebcdic_to_ascii[] = +{ + '\000', '\001', '\002', '\003', '\234', '\011', '\206', '\177', + '\227', '\215', '\216', '\013', '\014', '\015', '\016', '\017', + '\020', '\021', '\022', '\023', '\235', '\205', '\010', '\207', + '\030', '\031', '\222', '\217', '\034', '\035', '\036', '\037', + '\200', '\201', '\202', '\203', '\204', '\012', '\027', '\033', + '\210', '\211', '\212', '\213', '\214', '\005', '\006', '\007', + '\220', '\221', '\026', '\223', '\224', '\225', '\226', '\004', + '\230', '\231', '\232', '\233', '\024', '\025', '\236', '\032', + '\040', '\240', '\241', '\242', '\243', '\244', '\245', '\246', + '\247', '\250', '\133', '\056', '\074', '\050', '\053', '\041', + '\046', '\251', '\252', '\253', '\254', '\255', '\256', '\257', + '\260', '\261', '\135', '\044', '\052', '\051', '\073', '\136', + '\055', '\057', '\262', '\263', '\264', '\265', '\266', '\267', + '\270', '\271', '\174', '\054', '\045', '\137', '\076', '\077', + '\272', '\273', '\274', '\275', '\276', '\277', '\300', '\301', + '\302', '\140', '\072', '\043', '\100', '\047', '\075', '\042', + '\303', '\141', '\142', '\143', '\144', '\145', '\146', '\147', + '\150', '\151', '\304', '\305', '\306', '\307', '\310', '\311', + '\312', '\152', '\153', '\154', '\155', '\156', '\157', '\160', + '\161', '\162', '\313', '\314', '\315', '\316', '\317', '\320', + '\321', '\176', '\163', '\164', '\165', '\166', '\167', '\170', + '\171', '\172', '\322', '\323', '\324', '\325', '\326', '\327', + '\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337', + '\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347', + '\173', '\101', '\102', '\103', '\104', '\105', '\106', '\107', + '\110', '\111', '\350', '\351', '\352', '\353', '\354', '\355', + '\175', '\112', '\113', '\114', '\115', '\116', '\117', '\120', + '\121', '\122', '\356', '\357', '\360', '\361', '\362', '\363', + '\134', '\237', '\123', '\124', '\125', '\126', '\127', '\130', + '\131', '\132', '\364', '\365', '\366', '\367', '\370', '\371', + '\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067', + '\070', '\071', '\372', '\373', '\374', '\375', '\376', '\377' +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPERAND]...\n\ + or: %s OPTION\n\ +"), + program_name, program_name); + fputs (_("\ +Copy a file, converting and formatting according to the operands.\n\ +\n\ + bs=BYTES force ibs=BYTES and obs=BYTES\n\ + cbs=BYTES convert BYTES bytes at a time\n\ + conv=CONVS convert the file as per the comma separated symbol list\n\ + count=BLOCKS copy only BLOCKS input blocks\n\ + ibs=BYTES read BYTES bytes at a time\n\ +"), stdout); + fputs (_("\ + if=FILE read from FILE instead of stdin\n\ + iflag=FLAGS read as per the comma separated symbol list\n\ + obs=BYTES write BYTES bytes at a time\n\ + of=FILE write to FILE instead of stdout\n\ + oflag=FLAGS write as per the comma separated symbol list\n\ + seek=BLOCKS skip BLOCKS obs-sized blocks at start of output\n\ + skip=BLOCKS skip BLOCKS ibs-sized blocks at start of input\n\ + status=noxfer suppress transfer statistics\n\ +"), stdout); + fputs (_("\ +\n\ +BLOCKS and BYTES may be followed by the following multiplicative suffixes:\n\ +xM M, c 1, w 2, b 512, kB 1000, K 1024, MB 1000*1000, M 1024*1024,\n\ +GB 1000*1000*1000, G 1024*1024*1024, and so on for T, P, E, Z, Y.\n\ +\n\ +Each CONV symbol may be:\n\ +\n\ +"), stdout); + fputs (_("\ + ascii from EBCDIC to ASCII\n\ + ebcdic from ASCII to EBCDIC\n\ + ibm from ASCII to alternate EBCDIC\n\ + block pad newline-terminated records with spaces to cbs-size\n\ + unblock replace trailing spaces in cbs-size records with newline\n\ + lcase change upper case to lower case\n\ +"), stdout); + fputs (_("\ + nocreat do not create the output file\n\ + excl fail if the output file already exists\n\ + notrunc do not truncate the output file\n\ + ucase change lower case to upper case\n\ + swab swap every pair of input bytes\n\ +"), stdout); + fputs (_("\ + noerror continue after read errors\n\ + sync pad every input block with NULs to ibs-size; when used\n\ + with block or unblock, pad with spaces rather than NULs\n\ + fdatasync physically write output file data before finishing\n\ + fsync likewise, but also write metadata\n\ +"), stdout); + fputs (_("\ +\n\ +Each FLAG symbol may be:\n\ +\n\ + append append mode (makes sense only for output; conv=notrunc suggested)\n\ +"), stdout); + if (O_DIRECT) + fputs (_(" direct use direct I/O for data\n"), stdout); + if (O_DIRECTORY) + fputs (_(" directory fail unless a directory\n"), stdout); + if (O_DSYNC) + fputs (_(" dsync use synchronized I/O for data\n"), stdout); + if (O_SYNC) + fputs (_(" sync likewise, but also for metadata\n"), stdout); + if (O_NONBLOCK) + fputs (_(" nonblock use non-blocking I/O\n"), stdout); + if (O_NOATIME) + fputs (_(" noatime do not update access time\n"), stdout); + if (O_NOCTTY) + fputs (_(" noctty do not assign controlling terminal from file\n"), + stdout); + if (HAVE_WORKING_O_NOFOLLOW) + fputs (_(" nofollow do not follow symlinks\n"), stdout); + if (O_NOLINKS) + fputs (_(" nolinks fail if multiply-linked\n"), stdout); + if (O_BINARY) + fputs (_(" binary use binary I/O for data\n"), stdout); + if (O_TEXT) + fputs (_(" text use text I/O for data\n"), stdout); + + { + char const *siginfo_name = (SIGINFO == SIGUSR1 ? "USR1" : "INFO"); + printf (_("\ +\n\ +Sending a %s signal to a running `dd' process makes it\n\ +print I/O statistics to standard error and then resume copying.\n\ +\n\ + $ dd if=/dev/zero of=/dev/null& pid=$!\n\ + $ kill -%s $pid; sleep 1; kill $pid\n\ + 18335302+0 records in\n\ + 18335302+0 records out\n\ + 9387674624 bytes (9.4 GB) copied, 34.6279 seconds, 271 MB/s\n\ +\n\ +Options are:\n\ +\n\ +"), + siginfo_name, siginfo_name); + } + + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +static void +translate_charset (char const *new_trans) +{ + int i; + + for (i = 0; i < 256; i++) + trans_table[i] = new_trans[trans_table[i]]; + translation_needed = true; +} + +/* Return true if I has more than one bit set. I must be nonnegative. */ + +static inline bool +multiple_bits_set (int i) +{ + return (i & (i - 1)) != 0; +} + +/* Print transfer statistics. */ + +static void +print_stats (void) +{ + xtime_t now = gethrxtime (); + char hbuf[LONGEST_HUMAN_READABLE + 1]; + int human_opts = + (human_autoscale | human_round_to_nearest + | human_space_before_unit | human_SI | human_B); + double delta_s; + char const *bytes_per_second; + + fprintf (stderr, + _("%"PRIuMAX"+%"PRIuMAX" records in\n" + "%"PRIuMAX"+%"PRIuMAX" records out\n"), + r_full, r_partial, w_full, w_partial); + + if (r_truncate != 0) + fprintf (stderr, + ngettext ("%"PRIuMAX" truncated record\n", + "%"PRIuMAX" truncated records\n", + select_plural (r_truncate)), + r_truncate); + + if (status_flags & STATUS_NOXFER) + return; + + /* Use integer arithmetic to compute the transfer rate, + since that makes it easy to use SI abbreviations. */ + + fprintf (stderr, + ngettext ("%"PRIuMAX" byte (%s) copied", + "%"PRIuMAX" bytes (%s) copied", + select_plural (w_bytes)), + w_bytes, + human_readable (w_bytes, hbuf, human_opts, 1, 1)); + + if (start_time < now) + { + double XTIME_PRECISIONe0 = XTIME_PRECISION; + uintmax_t delta_xtime = now; + delta_xtime -= start_time; + delta_s = delta_xtime / XTIME_PRECISIONe0; + bytes_per_second = human_readable (w_bytes, hbuf, human_opts, + XTIME_PRECISION, delta_xtime); + } + else + { + delta_s = 0; + bytes_per_second = _("Infinity B"); + } + + /* TRANSLATORS: The two instances of "s" in this string are the SI + symbol "s" (meaning second), and should not be translated. + + This format used to be: + + ngettext (", %g second, %s/s\n", ", %g seconds, %s/s\n", delta_s == 1) + + but that was incorrect for languages like Polish. To fix this + bug we now use SI symbols even though they're a bit more + confusing in English. */ + fprintf (stderr, _(", %g s, %s/s\n"), delta_s, bytes_per_second); +} + +static void +cleanup (void) +{ + if (close (STDIN_FILENO) < 0) + error (EXIT_FAILURE, errno, + _("closing input file %s"), quote (input_file)); + + /* Don't remove this call to close, even though close_stdout + closes standard output. This close is necessary when cleanup + is called as part of a signal handler. */ + if (close (STDOUT_FILENO) < 0) + error (EXIT_FAILURE, errno, + _("closing output file %s"), quote (output_file)); +} + +static inline void ATTRIBUTE_NORETURN +quit (int code) +{ + cleanup (); + print_stats (); + process_signals (); + exit (code); +} + +/* An ordinary signal was received; arrange for the program to exit. */ + +static void +interrupt_handler (int sig) +{ + if (! SA_RESETHAND) + signal (sig, SIG_DFL); + interrupt_signal = sig; +} + +/* An info signal was received; arrange for the program to print status. */ + +static void +siginfo_handler (int sig) +{ + if (! SA_NOCLDSTOP) + signal (sig, siginfo_handler); + info_signal_count++; +} + +/* Install the signal handlers. */ + +static void +install_signal_handlers (void) +{ + bool catch_siginfo = ! (SIGINFO == SIGUSR1 && getenv ("POSIXLY_CORRECT")); + +#if SA_NOCLDSTOP + + struct sigaction act; + sigemptyset (&caught_signals); + if (catch_siginfo) + { + sigaction (SIGINFO, NULL, &act); + if (act.sa_handler != SIG_IGN) + sigaddset (&caught_signals, SIGINFO); + } + sigaction (SIGINT, NULL, &act); + if (act.sa_handler != SIG_IGN) + sigaddset (&caught_signals, SIGINT); + act.sa_mask = caught_signals; + + if (sigismember (&caught_signals, SIGINFO)) + { + act.sa_handler = siginfo_handler; + act.sa_flags = 0; + sigaction (SIGINFO, &act, NULL); + } + + if (sigismember (&caught_signals, SIGINT)) + { + /* POSIX 1003.1-2001 says SA_RESETHAND implies SA_NODEFER, + but this is not true on Solaris 8 at least. It doesn't + hurt to use SA_NODEFER here, so leave it in. */ + act.sa_handler = interrupt_handler; + act.sa_flags = SA_NODEFER | SA_RESETHAND; + sigaction (SIGINT, &act, NULL); + } + +#else + + if (catch_siginfo && signal (SIGINFO, SIG_IGN) != SIG_IGN) + { + signal (SIGINFO, siginfo_handler); + siginterrupt (SIGINFO, 1); + } + if (signal (SIGINT, SIG_IGN) != SIG_IGN) + { + signal (SIGINT, interrupt_handler); + siginterrupt (SIGINT, 1); + } +#endif +} + +/* Process any pending signals. If signals are caught, this function + should be called periodically. Ideally there should never be an + unbounded amount of time when signals are not being processed. */ + +static void +process_signals (void) +{ + while (interrupt_signal | info_signal_count) + { + int interrupt; + int infos; + sigset_t oldset; + + sigprocmask (SIG_BLOCK, &caught_signals, &oldset); + + /* Reload interrupt_signal and info_signal_count, in case a new + signal was handled before sigprocmask took effect. */ + interrupt = interrupt_signal; + infos = info_signal_count; + + if (infos) + info_signal_count = infos - 1; + + sigprocmask (SIG_SETMASK, &oldset, NULL); + + if (interrupt) + cleanup (); + print_stats (); + if (interrupt) + raise (interrupt); + } +} + +/* Read from FD into the buffer BUF of size SIZE, processing any + signals that arrive before bytes are read. Return the number of + bytes read if successful, -1 (setting errno) on failure. */ + +static ssize_t +iread (int fd, char *buf, size_t size) +{ + for (;;) + { + ssize_t nread; + process_signals (); + nread = read (fd, buf, size); + if (! (nread < 0 && errno == EINTR)) + return nread; + } +} + +/* Write to FD the buffer BUF of size SIZE, processing any signals + that arrive. Return the number of bytes written, setting errno if + this is less than SIZE. Keep trying if there are partial + writes. */ + +static size_t +iwrite (int fd, char const *buf, size_t size) +{ + size_t total_written = 0; + + while (total_written < size) + { + ssize_t nwritten; + process_signals (); + nwritten = write (fd, buf + total_written, size - total_written); + if (nwritten < 0) + { + if (errno != EINTR) + break; + } + else if (nwritten == 0) + { + /* Some buggy drivers return 0 when one tries to write beyond + a device's end. (Example: Linux 1.2.13 on /dev/fd0.) + Set errno to ENOSPC so they get a sensible diagnostic. */ + errno = ENOSPC; + break; + } + else + total_written += nwritten; + } + + return total_written; +} + +/* Write, then empty, the output buffer `obuf'. */ + +static void +write_output (void) +{ + size_t nwritten = iwrite (STDOUT_FILENO, obuf, output_blocksize); + w_bytes += nwritten; + if (nwritten != output_blocksize) + { + error (0, errno, _("writing to %s"), quote (output_file)); + if (nwritten != 0) + w_partial++; + quit (EXIT_FAILURE); + } + else + w_full++; + oc = 0; +} + +/* Interpret one "conv=..." or similar operand STR according to the + symbols in TABLE, returning the flags specified. If the operand + cannot be parsed, use ERROR_MSGID to generate a diagnostic. + As a by product, this function replaces each `,' in STR with a NUL byte. */ + +static int +parse_symbols (char *str, struct symbol_value const *table, + char const *error_msgid) +{ + int value = 0; + + do + { + struct symbol_value const *entry; + char *new = strchr (str, ','); + if (new != NULL) + *new++ = '\0'; + for (entry = table; ; entry++) + { + if (! entry->symbol[0]) + { + error (0, 0, _(error_msgid), quote (str)); + usage (EXIT_FAILURE); + } + if (STREQ (entry->symbol, str)) + { + if (! entry->value) + error (EXIT_FAILURE, 0, _(error_msgid), quote (str)); + value |= entry->value; + break; + } + } + str = new; + } + while (str); + + return value; +} + +/* Return the value of STR, interpreted as a non-negative decimal integer, + optionally multiplied by various values. + Set *INVALID if STR does not represent a number in this format. */ + +static uintmax_t +parse_integer (const char *str, bool *invalid) +{ + uintmax_t n; + char *suffix; + enum strtol_error e = xstrtoumax (str, &suffix, 10, &n, "bcEGkKMPTwYZ0"); + + if (e == LONGINT_INVALID_SUFFIX_CHAR && *suffix == 'x') + { + uintmax_t multiplier = parse_integer (suffix + 1, invalid); + + if (multiplier != 0 && n * multiplier / multiplier != n) + { + *invalid = true; + return 0; + } + + n *= multiplier; + } + else if (e != LONGINT_OK) + { + *invalid = true; + return 0; + } + + return n; +} + +static void +scanargs (int argc, char **argv) +{ + int i; + size_t blocksize = 0; + + for (i = optind; i < argc; i++) + { + char *name, *val; + + name = argv[i]; + val = strchr (name, '='); + if (val == NULL) + { + error (0, 0, _("unrecognized operand %s"), quote (name)); + usage (EXIT_FAILURE); + } + *val++ = '\0'; + + if (STREQ (name, "if")) + input_file = val; + else if (STREQ (name, "of")) + output_file = val; + else if (STREQ (name, "conv")) + conversions_mask |= parse_symbols (val, conversions, + N_("invalid conversion: %s")); + else if (STREQ (name, "iflag")) + input_flags |= parse_symbols (val, flags, + N_("invalid input flag: %s")); + else if (STREQ (name, "oflag")) + output_flags |= parse_symbols (val, flags, + N_("invalid output flag: %s")); + else if (STREQ (name, "status")) + status_flags |= parse_symbols (val, statuses, + N_("invalid status flag: %s")); + else + { + bool invalid = false; + uintmax_t n = parse_integer (val, &invalid); + + if (STREQ (name, "ibs")) + { + invalid |= ! (0 < n && n <= MAX_BLOCKSIZE (INPUT_BLOCK_SLOP)); + input_blocksize = n; + conversions_mask |= C_TWOBUFS; + } + else if (STREQ (name, "obs")) + { + invalid |= ! (0 < n && n <= MAX_BLOCKSIZE (OUTPUT_BLOCK_SLOP)); + output_blocksize = n; + conversions_mask |= C_TWOBUFS; + } + else if (STREQ (name, "bs")) + { + invalid |= ! (0 < n && n <= MAX_BLOCKSIZE (INPUT_BLOCK_SLOP)); + blocksize = n; + } + else if (STREQ (name, "cbs")) + { + invalid |= ! (0 < n && n <= SIZE_MAX); + conversion_blocksize = n; + } + else if (STREQ (name, "skip")) + skip_records = n; + else if (STREQ (name, "seek")) + seek_records = n; + else if (STREQ (name, "count")) + max_records = n; + else + { + error (0, 0, _("unrecognized operand %s=%s"), + quote_n (0, name), quote_n (1, val)); + usage (EXIT_FAILURE); + } + + if (invalid) + error (EXIT_FAILURE, 0, _("invalid number %s"), quote (val)); + } + } + + if (blocksize) + input_blocksize = output_blocksize = blocksize; + + /* If bs= was given, both `input_blocksize' and `output_blocksize' will + have been set to positive values. If either has not been set, + bs= was not given, so make sure two buffers are used. */ + if (input_blocksize == 0 || output_blocksize == 0) + conversions_mask |= C_TWOBUFS; + if (input_blocksize == 0) + input_blocksize = DEFAULT_BLOCKSIZE; + if (output_blocksize == 0) + output_blocksize = DEFAULT_BLOCKSIZE; + if (conversion_blocksize == 0) + conversions_mask &= ~(C_BLOCK | C_UNBLOCK); + + if (input_flags & (O_DSYNC | O_SYNC)) + input_flags |= O_RSYNC; + + if (multiple_bits_set (conversions_mask & (C_ASCII | C_EBCDIC | C_IBM))) + error (EXIT_FAILURE, 0, _("cannot combine any two of {ascii,ebcdic,ibm}")); + if (multiple_bits_set (conversions_mask & (C_BLOCK | C_UNBLOCK))) + error (EXIT_FAILURE, 0, _("cannot combine block and unblock")); + if (multiple_bits_set (conversions_mask & (C_LCASE | C_UCASE))) + error (EXIT_FAILURE, 0, _("cannot combine lcase and ucase")); + if (multiple_bits_set (conversions_mask & (C_EXCL | C_NOCREAT))) + error (EXIT_FAILURE, 0, _("cannot combine excl and nocreat")); +} + +/* Fix up translation table. */ + +static void +apply_translations (void) +{ + int i; + + if (conversions_mask & C_ASCII) + translate_charset (ebcdic_to_ascii); + + if (conversions_mask & C_UCASE) + { + for (i = 0; i < 256; i++) + trans_table[i] = toupper (trans_table[i]); + translation_needed = true; + } + else if (conversions_mask & C_LCASE) + { + for (i = 0; i < 256; i++) + trans_table[i] = tolower (trans_table[i]); + translation_needed = true; + } + + 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[' ']; + } +} + +/* Apply the character-set translations specified by the user + to the NREAD bytes in BUF. */ + +static void +translate_buffer (char *buf, size_t nread) +{ + char *cp; + size_t i; + + for (i = nread, cp = buf; i; i--, cp++) + *cp = trans_table[to_uchar (*cp)]; +} + +/* If true, the last char from the previous call to `swab_buffer' + is saved in `saved_char'. */ +static bool char_is_saved = false; + +/* Odd char from previous call. */ +static 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. */ + +static char * +swab_buffer (char *buf, size_t *nread) +{ + char *bufstart = buf; + char *cp; + size_t i; + + /* Is a char left from last time? */ + if (char_is_saved) + { + *--bufstart = saved_char; + (*nread)++; + char_is_saved = false; + } + + if (*nread & 1) + { + /* An odd number of chars are in the buffer. */ + saved_char = bufstart[--*nread]; + char_is_saved = true; + } + + /* 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; +} + +/* Add OFFSET to the input offset, setting the overflow flag if + necessary. */ + +static void +advance_input_offset (uintmax_t offset) +{ + input_offset += offset; + if (input_offset < offset) + input_offset_overflow = true; +} + +/* This is a wrapper for lseek. It detects and warns about a kernel + bug that makes lseek a no-op for tape devices, even though the kernel + lseek return value suggests that the function succeeded. + + The parameters are the same as those of the lseek function, but + with the addition of FILENAME, the name of the file associated with + descriptor FDESC. The file name is used solely in the warning that's + printed when the bug is detected. Return the same value that lseek + would have returned, but when the lseek bug is detected, return -1 + to indicate that lseek failed. + + The offending behavior has been confirmed with an Exabyte SCSI tape + drive accessed via /dev/nst0 on both Linux-2.2.17 and Linux-2.4.16. */ + +#ifdef __linux__ + +# include + +# define MT_SAME_POSITION(P, Q) \ + ((P).mt_resid == (Q).mt_resid \ + && (P).mt_fileno == (Q).mt_fileno \ + && (P).mt_blkno == (Q).mt_blkno) + +static off_t +skip_via_lseek (char const *filename, int fdesc, off_t offset, int whence) +{ + struct mtget s1; + struct mtget s2; + bool got_original_tape_position = (ioctl (fdesc, MTIOCGET, &s1) == 0); + /* known bad device type */ + /* && s.mt_type == MT_ISSCSI2 */ + + off_t new_position = lseek (fdesc, offset, whence); + if (0 <= new_position + && got_original_tape_position + && ioctl (fdesc, MTIOCGET, &s2) == 0 + && MT_SAME_POSITION (s1, s2)) + { + error (0, 0, _("warning: working around lseek kernel bug for file (%s)\n\ + of mt_type=0x%0lx -- see for the list of types"), + filename, s2.mt_type); + errno = 0; + new_position = -1; + } + + return new_position; +} +#else +# define skip_via_lseek(Filename, Fd, Offset, Whence) lseek (Fd, Offset, Whence) +#endif + +/* 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. RECORDS must be + nonzero. If fdesc is STDIN_FILENO, advance the input offset. + Return the number of records remaining, i.e., that were not skipped + because EOF was reached. */ + +static uintmax_t +skip (int fdesc, char const *file, uintmax_t records, size_t blocksize, + char *buf) +{ + uintmax_t offset = records * blocksize; + + /* Try lseek and if an error indicates it was an inappropriate operation -- + or if the file offset is not representable as an off_t -- + fall back on using read. */ + + errno = 0; + if (records <= OFF_T_MAX / blocksize + && 0 <= skip_via_lseek (file, fdesc, offset, SEEK_CUR)) + { + if (fdesc == STDIN_FILENO) + advance_input_offset (offset); + return 0; + } + else + { + int lseek_errno = errno; + + do + { + ssize_t nread = iread (fdesc, buf, blocksize); + if (nread < 0) + { + if (fdesc == STDIN_FILENO) + { + error (0, errno, _("reading %s"), quote (file)); + if (conversions_mask & C_NOERROR) + { + print_stats (); + continue; + } + } + else + error (0, lseek_errno, _("%s: cannot seek"), quote (file)); + quit (EXIT_FAILURE); + } + + if (nread == 0) + break; + if (fdesc == STDIN_FILENO) + advance_input_offset (nread); + } + while (--records != 0); + + return records; + } +} + +/* Advance the input by NBYTES if possible, after a read error. + The input file offset may or may not have advanced after the failed + read; adjust it to point just after the bad record regardless. + Return true if successful, or if the input is already known to not + be seekable. */ + +static bool +advance_input_after_read_error (size_t nbytes) +{ + if (! input_seekable) + { + if (input_seek_errno == ESPIPE) + return true; + errno = input_seek_errno; + } + else + { + off_t offset; + advance_input_offset (nbytes); + input_offset_overflow |= (OFF_T_MAX < input_offset); + if (input_offset_overflow) + { + error (0, 0, _("offset overflow while reading file %s"), + quote (input_file)); + return false; + } + offset = lseek (STDIN_FILENO, 0, SEEK_CUR); + if (0 <= offset) + { + off_t diff; + if (offset == input_offset) + return true; + diff = input_offset - offset; + if (! (0 <= diff && diff <= nbytes)) + error (0, 0, _("warning: invalid file offset after failed read")); + if (0 <= skip_via_lseek (input_file, STDIN_FILENO, diff, SEEK_CUR)) + return true; + if (errno == 0) + error (0, 0, _("cannot work around kernel bug after all")); + } + } + + error (0, errno, _("%s: cannot seek"), quote (input_file)); + return false; +} + +/* Copy NREAD bytes of BUF, with no conversions. */ + +static void +copy_simple (char const *buf, size_t nread) +{ + const char *start = buf; /* First uncopied char in BUF. */ + + do + { + size_t nfree = MIN (nread, output_blocksize - oc); + + memcpy (obuf + oc, start, 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). */ + +static void +copy_with_block (char const *buf, size_t nread) +{ + size_t i; + + for (i = nread; i; i--, buf++) + { + if (*buf == newline_character) + { + if (col < conversion_blocksize) + { + size_t j; + for (j = col; j < conversion_blocksize; j++) + 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). */ + +static void +copy_with_unblock (char const *buf, size_t nread) +{ + size_t i; + char c; + static size_t 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 + { + /* `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); + --pending_spaces; + } + output_char (c); + } + } +} + +/* Set the file descriptor flags for FD that correspond to the nonzero bits + in ADD_FLAGS. The file's name is NAME. */ + +static void +set_fd_flags (int fd, int add_flags, char const *name) +{ + /* Ignore file creation flags that are no-ops on file descriptors. */ + add_flags &= ~ (O_NOCTTY | O_NOFOLLOW); + + if (add_flags) + { + int old_flags = fcntl (fd, F_GETFL); + int new_flags = old_flags | add_flags; + bool ok = true; + if (old_flags < 0) + ok = false; + else if (old_flags != new_flags) + { + if (new_flags & (O_DIRECTORY | O_NOLINKS)) + { + /* NEW_FLAGS contains at least one file creation flag that + requires some checking of the open file descriptor. */ + struct stat st; + if (fstat (fd, &st) != 0) + ok = false; + else if ((new_flags & O_DIRECTORY) && ! S_ISDIR (st.st_mode)) + { + errno = ENOTDIR; + ok = false; + } + else if ((new_flags & O_NOLINKS) && 1 < st.st_nlink) + { + errno = EMLINK; + ok = false; + } + new_flags &= ~ (O_DIRECTORY | O_NOLINKS); + } + + if (ok && old_flags != new_flags + && fcntl (fd, F_SETFL, new_flags) == -1) + ok = false; + } + + if (!ok) + error (EXIT_FAILURE, errno, _("setting flags for %s"), quote (name)); + } +} + +/* The main loop. */ + +static int +dd_copy (void) +{ + char *ibuf, *bufstart; /* Input buffer. */ + /* These are declared static so that even though we don't free the + buffers, valgrind will recognize that there is no "real" leak. */ + static char *real_buf; /* real buffer address before alignment */ + static char *real_obuf; + ssize_t nread; /* Bytes read in the current block. */ + + /* If nonzero, then the previously read block was partial and + PARTREAD was its size. */ + size_t partread = 0; + + int exit_status = EXIT_SUCCESS; + size_t n_bytes_read; + + /* Leave at least one extra byte at the beginning and end of `ibuf' + for conv=swab, but keep the buffer address even. But some peculiar + device drivers work only with word-aligned buffers, so leave an + extra two bytes. */ + + /* Some devices require alignment on a sector or page boundary + (e.g. character disk devices). Align the input buffer to a + page boundary to cover all bases. Note that due to the swab + algorithm, we must have at least one byte in the page before + the input buffer; thus we allocate 2 pages of slop in the + real buffer. 8k above the blocksize shouldn't bother anyone. + + The page alignment is necessary on any linux system that supports + either the SGI raw I/O patch or Steven Tweedies raw I/O patch. + It is necessary when accessing raw (i.e. character special) disk + devices on Unixware or other SVR4-derived system. */ + + real_buf = xmalloc (input_blocksize + INPUT_BLOCK_SLOP); + ibuf = real_buf; + ibuf += SWAB_ALIGN_OFFSET; /* allow space for swab */ + + ibuf = ptr_align (ibuf, page_size); + + if (conversions_mask & C_TWOBUFS) + { + /* Page-align the output buffer, too. */ + real_obuf = xmalloc (output_blocksize + OUTPUT_BLOCK_SLOP); + obuf = ptr_align (real_obuf, page_size); + } + else + { + real_obuf = NULL; + obuf = ibuf; + } + + if (skip_records != 0) + { + skip (STDIN_FILENO, input_file, skip_records, input_blocksize, ibuf); + /* POSIX doesn't say what to do when dd detects it has been + asked to skip past EOF, so I assume it's non-fatal if the + call to 'skip' returns nonzero. FIXME: maybe give a warning. */ + } + + if (seek_records != 0) + { + uintmax_t write_records = skip (STDOUT_FILENO, output_file, + seek_records, output_blocksize, obuf); + + if (write_records != 0) + { + memset (obuf, 0, output_blocksize); + + do + if (iwrite (STDOUT_FILENO, obuf, output_blocksize) + != output_blocksize) + { + error (0, errno, _("writing to %s"), quote (output_file)); + quit (EXIT_FAILURE); + } + while (--write_records != 0); + } + } + + if (max_records == 0) + return exit_status; + + while (1) + { + if (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)) + memset (ibuf, + (conversions_mask & (C_BLOCK | C_UNBLOCK)) ? ' ' : '\0', + input_blocksize); + + nread = iread (STDIN_FILENO, ibuf, input_blocksize); + + if (nread == 0) + break; /* EOF. */ + + if (nread < 0) + { + error (0, errno, _("reading %s"), quote (input_file)); + if (conversions_mask & C_NOERROR) + { + print_stats (); + /* Seek past the bad block if possible. */ + if (!advance_input_after_read_error (input_blocksize - partread)) + { + exit_status = EXIT_FAILURE; + + /* Suppress duplicate diagnostics. */ + input_seekable = false; + input_seek_errno = ESPIPE; + } + if ((conversions_mask & C_SYNC) && !partread) + /* Replace the missing input with null bytes and + proceed normally. */ + nread = 0; + else + continue; + } + else + { + /* Write any partial block. */ + exit_status = EXIT_FAILURE; + break; + } + } + + n_bytes_read = nread; + advance_input_offset (nread); + + if (n_bytes_read < input_blocksize) + { + r_partial++; + partread = n_bytes_read; + if (conversions_mask & C_SYNC) + { + if (!(conversions_mask & C_NOERROR)) + /* If C_NOERROR, we zeroed the block before reading. */ + memset (ibuf + n_bytes_read, + (conversions_mask & (C_BLOCK | C_UNBLOCK)) ? ' ' : '\0', + input_blocksize - n_bytes_read); + n_bytes_read = input_blocksize; + } + } + else + { + r_full++; + partread = 0; + } + + if (ibuf == obuf) /* If not C_TWOBUFS. */ + { + size_t nwritten = iwrite (STDOUT_FILENO, obuf, n_bytes_read); + w_bytes += nwritten; + if (nwritten != n_bytes_read) + { + error (0, errno, _("writing %s"), quote (output_file)); + return EXIT_FAILURE; + } + else if (n_bytes_read == input_blocksize) + w_full++; + else + w_partial++; + continue; + } + + /* Do any translations on the whole buffer at once. */ + + if (translation_needed) + translate_buffer (ibuf, n_bytes_read); + + if (conversions_mask & C_SWAB) + bufstart = swab_buffer (ibuf, &n_bytes_read); + else + bufstart = ibuf; + + if (conversions_mask & C_BLOCK) + copy_with_block (bufstart, n_bytes_read); + else if (conversions_mask & C_UNBLOCK) + copy_with_unblock (bufstart, n_bytes_read); + else + copy_simple (bufstart, n_bytes_read); + } + + /* 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. */ + size_t i; + for (i = col; i < conversion_blocksize; i++) + 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) + { + size_t nwritten = iwrite (STDOUT_FILENO, obuf, oc); + w_bytes += nwritten; + if (nwritten != 0) + w_partial++; + if (nwritten != oc) + { + error (0, errno, _("writing %s"), quote (output_file)); + return EXIT_FAILURE; + } + } + + if ((conversions_mask & C_FDATASYNC) && fdatasync (STDOUT_FILENO) != 0) + { + if (errno != ENOSYS && errno != EINVAL) + { + error (0, errno, _("fdatasync failed for %s"), quote (output_file)); + exit_status = EXIT_FAILURE; + } + conversions_mask |= C_FSYNC; + } + + if (conversions_mask & C_FSYNC) + while (fsync (STDOUT_FILENO) != 0) + if (errno != EINTR) + { + error (0, errno, _("fsync failed for %s"), quote (output_file)); + return EXIT_FAILURE; + } + + return exit_status; +} + +int +main (int argc, char **argv) +{ + int i; + int exit_status; + off_t offset; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + /* Arrange to close stdout if parse_long_options exits. */ + atexit (close_stdout); + + page_size = getpagesize (); + + parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + /* 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_file = _("standard input"); + set_fd_flags (STDIN_FILENO, input_flags, input_file); + } + else + { + if (fd_reopen (STDIN_FILENO, input_file, O_RDONLY | input_flags, 0) < 0) + error (EXIT_FAILURE, errno, _("opening %s"), quote (input_file)); + } + + offset = lseek (STDIN_FILENO, 0, SEEK_CUR); + input_seekable = (0 <= offset); + input_offset = offset; + input_seek_errno = errno; + + if (output_file == NULL) + { + output_file = _("standard output"); + set_fd_flags (STDOUT_FILENO, output_flags, output_file); + } + else + { + mode_t perms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + int opts + = (output_flags + | (conversions_mask & C_NOCREAT ? 0 : O_CREAT) + | (conversions_mask & C_EXCL ? O_EXCL : 0) + | (seek_records || (conversions_mask & C_NOTRUNC) ? 0 : O_TRUNC)); + + /* Open the output file with *read* access only if we might + need to read to satisfy a `seek=' request. If we can't read + the file, go ahead with write-only access; it might work. */ + if ((! seek_records + || fd_reopen (STDOUT_FILENO, output_file, O_RDWR | opts, perms) < 0) + && (fd_reopen (STDOUT_FILENO, output_file, O_WRONLY | opts, perms) + < 0)) + error (EXIT_FAILURE, errno, _("opening %s"), quote (output_file)); + +#if HAVE_FTRUNCATE + if (seek_records != 0 && !(conversions_mask & C_NOTRUNC)) + { + uintmax_t size = seek_records * output_blocksize; + unsigned long int obs = output_blocksize; + + if (OFF_T_MAX / output_blocksize < seek_records) + error (EXIT_FAILURE, 0, + _("offset too large: " + "cannot truncate to a length of seek=%"PRIuMAX"" + " (%lu-byte) blocks"), + seek_records, obs); + + if (ftruncate (STDOUT_FILENO, size) != 0) + { + /* Complain only when ftruncate fails on a regular file, a + directory, or a shared memory object, as POSIX 1003.1-2004 + specifies ftruncate's behavior only for these file types. + For example, do not complain when Linux 2.4 ftruncate + fails on /dev/fd0. */ + int ftruncate_errno = errno; + struct stat stdout_stat; + if (fstat (STDOUT_FILENO, &stdout_stat) != 0) + error (EXIT_FAILURE, errno, _("cannot fstat %s"), + quote (output_file)); + if (S_ISREG (stdout_stat.st_mode) + || S_ISDIR (stdout_stat.st_mode) + || S_TYPEISSHM (&stdout_stat)) + error (EXIT_FAILURE, ftruncate_errno, + _("truncating at %"PRIuMAX" bytes in output file %s"), + size, quote (output_file)); + } + } +#endif + } + + install_signal_handlers (); + + start_time = gethrxtime (); + + exit_status = dd_copy (); + + quit (exit_status); +} diff --git a/src/df.c b/src/df.c new file mode 100644 index 0000000..609787e --- /dev/null +++ b/src/df.c @@ -0,0 +1,967 @@ +/* df - summarize free disk space + Copyright (C) 91, 1995-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by David MacKenzie . + --human-readable and --megabyte options added by lm@sgi.com. + --si and large file support added by eggert@twinsun.com. */ + +#include +#include +#include +#include + +#include "system.h" +#include "canonicalize.h" +#include "error.h" +#include "fsusage.h" +#include "human.h" +#include "inttostr.h" +#include "mountlist.h" +#include "quote.h" +#include "save-cwd.h" +#include "xgetcwd.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "df" + +#define AUTHORS \ + "Torbjorn Granlund", "David MacKenzie", "Paul Eggert" + +/* Name this program was run with. */ +char *program_name; + +/* If true, show inode information. */ +static bool inode_format; + +/* If true, show even file systems with zero size or + uninteresting types. */ +static bool show_all_fs; + +/* If true, show only local file systems. */ +static bool show_local_fs; + +/* If true, output data for each file system corresponding to a + command line argument -- even if it's a dummy (automounter) entry. */ +static bool show_listed_fs; + +/* Human-readable options for output. */ +static int human_output_opts; + +/* The units to use when printing sizes. */ +static uintmax_t output_block_size; + +/* If true, use the POSIX output format. */ +static bool posix_format; + +/* True if a file system has been processed for output. */ +static bool file_systems_processed; + +/* If true, invoke the `sync' system call before getting any usage data. + Using this option can make df very slow, especially with many or very + busy disks. Note that this may make a difference on some systems -- + SunOS 4.1.3, for one. It is *not* necessary on Linux. */ +static bool require_sync; + +/* Desired exit status. */ +static int exit_status; + +/* A file system type to display. */ + +struct fs_type_list +{ + char *fs_name; + struct fs_type_list *fs_next; +}; + +/* Linked list of file system types to display. + If `fs_select_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 file system types; let the user specify any file system type + they want to, and if there are any file systems of that type, they + will be shown. + + Some file system types: + 4.2 4.3 ufs nfs swap ignore io vm efs dbg */ + +static struct fs_type_list *fs_select_list; + +/* Linked list of file system types to omit. + If the list is empty, don't exclude any types. */ + +static struct fs_type_list *fs_exclude_list; + +/* Linked list of mounted file systems. */ +static struct mount_entry *mount_list; + +/* If true, print file system type as well. */ +static bool print_type; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + NO_SYNC_OPTION = CHAR_MAX + 1, + /* FIXME: --kilobytes is deprecated (but not -k); remove in late 2006 */ + KILOBYTES_LONG_OPTION, + SYNC_OPTION +}; + +static struct option const long_options[] = +{ + {"all", no_argument, NULL, 'a'}, + {"block-size", required_argument, NULL, 'B'}, + {"inodes", no_argument, NULL, 'i'}, + {"human-readable", no_argument, NULL, 'h'}, + {"si", no_argument, NULL, 'H'}, + {"kilobytes", no_argument, NULL, KILOBYTES_LONG_OPTION}, + {"local", no_argument, NULL, 'l'}, + {"megabytes", no_argument, NULL, 'm'}, /* obsolescent */ + {"portability", no_argument, NULL, 'P'}, + {"print-type", no_argument, NULL, 'T'}, + {"sync", no_argument, NULL, SYNC_OPTION}, + {"no-sync", no_argument, NULL, NO_SYNC_OPTION}, + {"type", required_argument, NULL, 't'}, + {"exclude-type", required_argument, NULL, 'x'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +static void +print_header (void) +{ + char buf[MAX (LONGEST_HUMAN_READABLE + 1, INT_BUFSIZE_BOUND (uintmax_t))]; + + if (print_type) + fputs (_("Filesystem Type"), stdout); + else + fputs (_("Filesystem "), stdout); + + if (inode_format) + printf (_(" Inodes IUsed IFree IUse%%")); + else if (human_output_opts & human_autoscale) + { + if (human_output_opts & human_base_1024) + printf (_(" Size Used Avail Use%%")); + else + printf (_(" Size Used Avail Use%%")); + } + else if (posix_format) + printf (_(" %s-blocks Used Available Capacity"), + umaxtostr (output_block_size, buf)); + else + { + int opts = (human_suppress_point_zero + | human_autoscale | human_SI + | (human_output_opts + & (human_group_digits | human_base_1024 | human_B))); + + /* Prefer the base that makes the human-readable value more exact, + if there is a difference. */ + + uintmax_t q1000 = output_block_size; + uintmax_t q1024 = output_block_size; + bool divisible_by_1000; + bool divisible_by_1024; + + do + { + divisible_by_1000 = q1000 % 1000 == 0; q1000 /= 1000; + divisible_by_1024 = q1024 % 1024 == 0; q1024 /= 1024; + } + while (divisible_by_1000 & divisible_by_1024); + + if (divisible_by_1000 < divisible_by_1024) + opts |= human_base_1024; + if (divisible_by_1024 < divisible_by_1000) + opts &= ~human_base_1024; + if (! (opts & human_base_1024)) + opts |= human_B; + + printf (_(" %4s-blocks Used Available Use%%"), + human_readable (output_block_size, buf, opts, 1, 1)); + } + + printf (_(" Mounted on\n")); +} + +/* Is FSTYPE a type of file system that should be listed? */ + +static bool +selected_fstype (const char *fstype) +{ + const struct fs_type_list *fsp; + + if (fs_select_list == NULL || fstype == NULL) + return true; + for (fsp = fs_select_list; fsp; fsp = fsp->fs_next) + if (STREQ (fstype, fsp->fs_name)) + return true; + return false; +} + +/* Is FSTYPE a type of file system that should be omitted? */ + +static bool +excluded_fstype (const char *fstype) +{ + const struct fs_type_list *fsp; + + if (fs_exclude_list == NULL || fstype == NULL) + return false; + for (fsp = fs_exclude_list; fsp; fsp = fsp->fs_next) + if (STREQ (fstype, fsp->fs_name)) + return true; + return false; +} + +/* Like human_readable (N, BUF, human_output_opts, INPUT_UNITS, OUTPUT_UNITS), + except: + + - If NEGATIVE, then N represents a negative number, + expressed in two's complement. + - Otherwise, return "-" if N is UINTMAX_MAX. */ + +static char const * +df_readable (bool negative, uintmax_t n, char *buf, + uintmax_t input_units, uintmax_t output_units) +{ + if (n == UINTMAX_MAX && !negative) + return "-"; + else + { + char *p = human_readable (negative ? -n : n, buf + negative, + human_output_opts, input_units, output_units); + if (negative) + *--p = '-'; + return p; + } +} + +/* Display a space listing for the disk device with absolute file name DISK. + If MOUNT_POINT is non-NULL, it is the name of the root of the + file system on DISK. + If STAT_FILE is non-null, it is the name of a file within the file + system that the user originally asked for; this provides better + diagnostics, and sometimes it provides better results on networked + file systems that give different free-space results depending on + where in the file system you probe. + If FSTYPE is non-NULL, it is the type of the file system on DISK. + If MOUNT_POINT is non-NULL, then DISK may be NULL -- certain systems may + not be able to produce statistics in this case. + ME_DUMMY and ME_REMOTE are the mount entry flags. */ + +static void +show_dev (char const *disk, char const *mount_point, + char const *stat_file, char const *fstype, + bool me_dummy, bool me_remote) +{ + struct fs_usage fsu; + char buf[3][LONGEST_HUMAN_READABLE + 2]; + int width; + int col1_adjustment = 0; + int use_width; + uintmax_t input_units; + uintmax_t output_units; + uintmax_t total; + uintmax_t available; + bool negate_available; + uintmax_t available_to_root; + uintmax_t used; + bool negate_used; + double pct = -1; + + if (me_remote & show_local_fs) + return; + + if (me_dummy & !show_all_fs & !show_listed_fs) + return; + + if (!selected_fstype (fstype) || excluded_fstype (fstype)) + return; + + /* If MOUNT_POINT is NULL, then the file system is not mounted, and this + program reports on the file system that the special file is on. + It would be better to report on the unmounted file system, + but statfs doesn't do that on most systems. */ + if (!stat_file) + stat_file = mount_point ? mount_point : disk; + + if (get_fs_usage (stat_file, disk, &fsu)) + { + error (0, errno, "%s", quote (stat_file)); + exit_status = EXIT_FAILURE; + return; + } + + if (fsu.fsu_blocks == 0 && !show_all_fs && !show_listed_fs) + return; + + if (! file_systems_processed) + { + file_systems_processed = true; + print_header (); + } + + if (! disk) + disk = "-"; /* unknown */ + if (! fstype) + fstype = "-"; /* unknown */ + + /* df.c reserved 5 positions for fstype, + but that does not suffice for type iso9660 */ + if (print_type) + { + size_t disk_name_len = strlen (disk); + size_t fstype_len = strlen (fstype); + if (disk_name_len + fstype_len < 18) + printf ("%s%*s ", disk, 18 - (int) disk_name_len, fstype); + else if (!posix_format) + printf ("%s\n%18s ", disk, fstype); + else + printf ("%s %s", disk, fstype); + } + else + { + if (strlen (disk) > 20 && !posix_format) + printf ("%s\n%20s", disk, ""); + else + printf ("%-20s", disk); + } + + if (inode_format) + { + width = 7; + use_width = 5; + input_units = output_units = 1; + total = fsu.fsu_files; + available = fsu.fsu_ffree; + negate_available = false; + available_to_root = available; + } + else + { + if (human_output_opts & human_autoscale) + width = 5 + ! (human_output_opts & human_base_1024); + else + { + width = 9; + if (posix_format) + { + uintmax_t b; + col1_adjustment = -3; + for (b = output_block_size; 9 < b; b /= 10) + col1_adjustment++; + } + } + use_width = ((posix_format + && ! (human_output_opts & human_autoscale)) + ? 8 : 4); + input_units = fsu.fsu_blocksize; + output_units = output_block_size; + total = fsu.fsu_blocks; + available = fsu.fsu_bavail; + negate_available = (fsu.fsu_bavail_top_bit_set + & (available != UINTMAX_MAX)); + available_to_root = fsu.fsu_bfree; + } + + used = UINTMAX_MAX; + negate_used = false; + if (total != UINTMAX_MAX && available_to_root != UINTMAX_MAX) + { + used = total - available_to_root; + negate_used = (total < available_to_root); + } + + printf (" %*s %*s %*s ", + width + col1_adjustment, + df_readable (false, total, + buf[0], input_units, output_units), + width, df_readable (negate_used, used, + buf[1], input_units, output_units), + width, df_readable (negate_available, available, + buf[2], input_units, output_units)); + + if (used == UINTMAX_MAX || available == UINTMAX_MAX) + ; + else if (!negate_used + && used <= TYPE_MAXIMUM (uintmax_t) / 100 + && used + available != 0 + && (used + available < used) == negate_available) + { + uintmax_t u100 = used * 100; + uintmax_t nonroot_total = used + available; + pct = u100 / nonroot_total + (u100 % nonroot_total != 0); + } + else + { + /* The calculation cannot be done easily with integer + arithmetic. Fall back on floating point. This can suffer + from minor rounding errors, but doing it exactly requires + multiple precision arithmetic, and it's not worth the + aggravation. */ + double u = negate_used ? - (double) - used : used; + double a = negate_available ? - (double) - available : available; + double nonroot_total = u + a; + if (nonroot_total) + { + long int lipct = pct = u * 100 / nonroot_total; + double ipct = lipct; + + /* Like `pct = ceil (dpct);', but avoid ceil so that + the math library needn't be linked. */ + if (ipct - 1 < pct && pct <= ipct + 1) + pct = ipct + (ipct < pct); + } + } + + if (0 <= pct) + printf ("%*.0f%%", use_width - 1, pct); + else + printf ("%*s", use_width, "- "); + + if (mount_point) + { +#ifdef HIDE_AUTOMOUNT_PREFIX + /* Don't print the first directory name in MOUNT_POINT if it's an + artifact of an automounter. This is a bit too aggressive to be + the default. */ + if (strncmp ("/auto/", mount_point, 6) == 0) + mount_point += 5; + else if (strncmp ("/tmp_mnt/", mount_point, 9) == 0) + mount_point += 8; +#endif + printf (" %s", mount_point); + } + putchar ('\n'); +} + +/* Return the root mountpoint of the file system on which FILE exists, in + malloced storage. FILE_STAT should be the result of stating FILE. + Give a diagnostic and return NULL if unable to determine the mount point. + Exit if unable to restore current working directory. */ +static char * +find_mount_point (const char *file, const struct stat *file_stat) +{ + struct saved_cwd cwd; + struct stat last_stat; + char *mp = NULL; /* The malloced mount point. */ + + if (save_cwd (&cwd) != 0) + { + error (0, errno, _("cannot get current directory")); + return NULL; + } + + if (S_ISDIR (file_stat->st_mode)) + /* FILE is a directory, so just chdir there directly. */ + { + last_stat = *file_stat; + if (chdir (file) < 0) + { + error (0, errno, _("cannot change to directory %s"), quote (file)); + return NULL; + } + } + else + /* FILE is some other kind of file; use its directory. */ + { + char *xdir = dir_name (file); + char *dir; + ASSIGN_STRDUPA (dir, xdir); + free (xdir); + + if (chdir (dir) < 0) + { + error (0, errno, _("cannot change to directory %s"), quote (dir)); + return NULL; + } + + if (stat (".", &last_stat) < 0) + { + error (0, errno, _("cannot stat current directory (now %s)"), + quote (dir)); + goto done; + } + } + + /* Now walk up FILE's parents until we find another file system or /, + chdiring as we go. LAST_STAT holds stat information for the last place + we visited. */ + for (;;) + { + struct stat st; + if (stat ("..", &st) < 0) + { + error (0, errno, _("cannot stat %s"), quote ("..")); + goto done; + } + if (st.st_dev != last_stat.st_dev || st.st_ino == last_stat.st_ino) + /* cwd is the mount point. */ + break; + if (chdir ("..") < 0) + { + error (0, errno, _("cannot change to directory %s"), quote ("..")); + goto done; + } + last_stat = st; + } + + /* Finally reached a mount point, see what it's called. */ + mp = xgetcwd (); + +done: + /* Restore the original cwd. */ + { + int save_errno = errno; + if (restore_cwd (&cwd) != 0) + error (EXIT_FAILURE, errno, + _("failed to return to initial working directory")); + free_cwd (&cwd); + errno = save_errno; + } + + return mp; +} + +/* If DISK corresponds to a mount point, show its usage + and return true. Otherwise, return false. */ +static bool +show_disk (char const *disk) +{ + struct mount_entry const *me; + struct mount_entry const *best_match = NULL; + + for (me = mount_list; me; me = me->me_next) + if (STREQ (disk, me->me_devname)) + best_match = me; + + if (best_match) + { + show_dev (best_match->me_devname, best_match->me_mountdir, NULL, + best_match->me_type, best_match->me_dummy, + best_match->me_remote); + return true; + } + + return false; +} + +/* Figure out which device file or directory POINT is mounted on + and show its disk usage. + STATP must be the result of `stat (POINT, STATP)'. */ +static void +show_point (const char *point, const struct stat *statp) +{ + struct stat disk_stats; + struct mount_entry *me; + struct mount_entry const *best_match = NULL; + + /* If POINT is an absolute file name, see if we can find the + mount point without performing any extra stat calls at all. */ + if (*point == '/') + { + /* Find the best match: prefer non-dummies, and then prefer the + last match if there are ties. */ + + for (me = mount_list; me; me = me->me_next) + if (STREQ (me->me_mountdir, point) && !STREQ (me->me_type, "lofs") + && (!best_match || best_match->me_dummy || !me->me_dummy)) + best_match = me; + } + + /* Calculate the real absolute file name for POINT, and use that to find + the mount point. This avoids statting unavailable mount points, + which can hang df. */ + if (! best_match) + { + char *resolved = canonicalize_file_name (point); + + if (resolved && resolved[0] == '/') + { + size_t resolved_len = strlen (resolved); + size_t best_match_len = 0; + + for (me = mount_list; me; me = me->me_next) + if (!STREQ (me->me_type, "lofs") + && (!best_match || best_match->me_dummy || !me->me_dummy)) + { + size_t len = strlen (me->me_mountdir); + if (best_match_len <= len && len <= resolved_len + && (len == 1 /* root file system */ + || ((len == resolved_len || resolved[len] == '/') + && strncmp (me->me_mountdir, resolved, len) == 0))) + { + best_match = me; + best_match_len = len; + } + } + } + + free (resolved); + + if (best_match + && (stat (best_match->me_mountdir, &disk_stats) != 0 + || disk_stats.st_dev != statp->st_dev)) + best_match = NULL; + } + + if (! best_match) + 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 + { + /* Report only I/O errors. Other errors might be + caused by shadowed mount points, which means POINT + can't possibly be on this file system. */ + if (errno == EIO) + { + error (0, errno, "%s", quote (me->me_mountdir)); + exit_status = EXIT_FAILURE; + } + + /* So we won't try and fail repeatedly. */ + me->me_dev = (dev_t) -2; + } + } + + if (statp->st_dev == me->me_dev + && !STREQ (me->me_type, "lofs") + && (!best_match || best_match->me_dummy || !me->me_dummy)) + { + /* Skip bogus mtab entries. */ + if (stat (me->me_mountdir, &disk_stats) != 0 + || disk_stats.st_dev != me->me_dev) + me->me_dev = (dev_t) -2; + else + best_match = me; + } + } + + if (best_match) + show_dev (best_match->me_devname, best_match->me_mountdir, point, + best_match->me_type, best_match->me_dummy, best_match->me_remote); + else + { + /* We couldn't find the mount entry corresponding to POINT. Go ahead and + print as much info as we can; methods that require the device to be + present will fail at a later point. */ + + /* Find the actual mount point. */ + char *mp = find_mount_point (point, statp); + if (mp) + { + show_dev (NULL, mp, NULL, NULL, false, false); + free (mp); + } + } +} + +/* Determine what kind of node NAME is and show the disk usage + for it. STATP is the results of `stat' on NAME. */ + +static void +show_entry (char const *name, struct stat const *statp) +{ + if ((S_ISBLK (statp->st_mode) || S_ISCHR (statp->st_mode)) + && show_disk (name)) + return; + + show_point (name, statp); +} + +/* Show all mounted file systems, except perhaps those that are of + an unselected type or are empty. */ + +static void +show_all_entries (void) +{ + struct mount_entry *me; + + for (me = mount_list; me; me = me->me_next) + show_dev (me->me_devname, me->me_mountdir, NULL, me->me_type, + me->me_dummy, me->me_remote); +} + +/* Add FSTYPE to the list of file system types to display. */ + +static void +add_fs_type (const char *fstype) +{ + struct fs_type_list *fsp; + + fsp = xmalloc (sizeof *fsp); + fsp->fs_name = (char *) fstype; + fsp->fs_next = fs_select_list; + fs_select_list = fsp; +} + +/* Add FSTYPE to the list of file system types to be omitted. */ + +static void +add_excluded_fs_type (const char *fstype) +{ + struct fs_type_list *fsp; + + fsp = xmalloc (sizeof *fsp); + fsp->fs_name = (char *) fstype; + fsp->fs_next = fs_exclude_list; + fs_exclude_list = fsp; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name); + fputs (_("\ +Show information about the file system on which each FILE resides,\n\ +or all file systems by default.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -a, --all include dummy file systems\n\ + -B, --block-size=SIZE use SIZE-byte blocks\n\ + -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)\n\ + -H, --si likewise, but use powers of 1000 not 1024\n\ +"), stdout); + fputs (_("\ + -i, --inodes list inode information instead of block usage\n\ + -k like --block-size=1K\n\ + -l, --local limit listing to local file systems\n\ + --no-sync do not invoke sync before getting usage info (default)\n\ +"), stdout); + fputs (_("\ + -P, --portability use the POSIX output format\n\ + --sync invoke sync before getting usage info\n\ + -t, --type=TYPE limit listing to file systems of type TYPE\n\ + -T, --print-type print file system type\n\ + -x, --exclude-type=TYPE limit listing to file systems not of type TYPE\n\ + -v (ignored)\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\n\ +SIZE may be (or may be an integer optionally followed by) one of following:\n\ +kB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int c; + struct stat *stats IF_LINT (= 0); + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + fs_select_list = NULL; + fs_exclude_list = NULL; + inode_format = false; + show_all_fs = false; + show_listed_fs = false; + human_output_opts = -1; + print_type = false; + file_systems_processed = false; + posix_format = false; + exit_status = EXIT_SUCCESS; + + while ((c = getopt_long (argc, argv, "aB:iF:hHklmPTt:vx:", long_options, NULL)) + != -1) + { + switch (c) + { + case 'a': + show_all_fs = true; + break; + case 'B': + human_output_opts = human_options (optarg, true, &output_block_size); + break; + case 'i': + inode_format = true; + break; + case 'h': + human_output_opts = human_autoscale | human_SI | human_base_1024; + output_block_size = 1; + break; + case 'H': + human_output_opts = human_autoscale | human_SI; + output_block_size = 1; + break; + case KILOBYTES_LONG_OPTION: + error (0, 0, + _("the --kilobytes option is deprecated; use -k instead")); + /* fall through */ + case 'k': + human_output_opts = 0; + output_block_size = 1024; + break; + case 'l': + show_local_fs = true; + break; + case 'm': /* obsolescent */ + human_output_opts = 0; + output_block_size = 1024 * 1024; + break; + case 'T': + print_type = true; + break; + case 'P': + posix_format = true; + break; + case SYNC_OPTION: + require_sync = true; + break; + case NO_SYNC_OPTION: + require_sync = false; + break; + + case 'F': + /* Accept -F as a synonym for -t for compatibility with Solaris. */ + case 't': + add_fs_type (optarg); + break; + + case 'v': /* For SysV compatibility. */ + /* ignore */ + break; + case 'x': + add_excluded_fs_type (optarg); + break; + + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (human_output_opts == -1) + { + if (posix_format) + { + human_output_opts = 0; + output_block_size = (getenv ("POSIXLY_CORRECT") ? 512 : 1024); + } + else + human_output_opts = human_options (getenv ("DF_BLOCK_SIZE"), false, + &output_block_size); + } + + /* Fail if the same file system type was both selected and excluded. */ + { + bool match = false; + struct fs_type_list *fs_incl; + for (fs_incl = fs_select_list; fs_incl; fs_incl = fs_incl->fs_next) + { + struct fs_type_list *fs_excl; + for (fs_excl = fs_exclude_list; fs_excl; fs_excl = fs_excl->fs_next) + { + if (STREQ (fs_incl->fs_name, fs_excl->fs_name)) + { + error (0, 0, + _("file system type %s both selected and excluded"), + quote (fs_incl->fs_name)); + match = true; + break; + } + } + } + if (match) + exit (EXIT_FAILURE); + } + + if (optind < argc) + { + int i; + + /* stat all the given entries to make sure they get automounted, + if necessary, before reading the file system table. */ + stats = xnmalloc (argc - optind, sizeof *stats); + for (i = optind; i < argc; ++i) + { + if (stat (argv[i], &stats[i - optind])) + { + error (0, errno, "%s", quote (argv[i])); + exit_status = EXIT_FAILURE; + argv[i] = NULL; + } + } + } + + mount_list = + read_file_system_list ((fs_select_list != NULL + || fs_exclude_list != NULL + || print_type + || show_local_fs)); + + if (mount_list == NULL) + { + /* Couldn't read the table of mounted file systems. + Fail if df was invoked with no file name arguments; + Otherwise, merely give a warning and proceed. */ + const char *warning = (optind < argc ? _("Warning: ") : ""); + int status = (optind < argc ? 0 : EXIT_FAILURE); + error (status, errno, + _("%scannot read table of mounted file systems"), warning); + } + + if (require_sync) + sync (); + + if (optind < argc) + { + int i; + + /* Display explicitly requested empty file systems. */ + show_listed_fs = true; + + for (i = optind; i < argc; ++i) + if (argv[i]) + show_entry (argv[i], &stats[i - optind]); + } + else + show_all_entries (); + + if (! file_systems_processed) + error (EXIT_FAILURE, 0, _("no file systems processed")); + + exit (exit_status); +} diff --git a/src/dircolors.c b/src/dircolors.c new file mode 100644 index 0000000..82eb1e0 --- /dev/null +++ b/src/dircolors.c @@ -0,0 +1,512 @@ +/* dircolors - output commands to set the LS_COLOR environment variable + Copyright (C) 1996-2007 Free Software Foundation, Inc. + Copyright (C) 1994, 1995, 1997, 1998, 1999, 2000 H. Peter Anvin + + 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include + +#include +#include +#include + +#include "system.h" +#include "dircolors.h" +#include "c-strcase.h" +#include "error.h" +#include "getline.h" +#include "obstack.h" +#include "quote.h" +#include "xstrndup.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "dircolors" + +#define AUTHORS "H. Peter Anvin" + +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free + +enum Shell_syntax +{ + SHELL_SYNTAX_BOURNE, + SHELL_SYNTAX_C, + SHELL_SYNTAX_UNKNOWN +}; + +#define APPEND_CHAR(C) obstack_1grow (&lsc_obstack, C) +#define APPEND_TWO_CHAR_STRING(S) \ + do \ + { \ + APPEND_CHAR (S[0]); \ + APPEND_CHAR (S[1]); \ + } \ + while (0) + +/* Accumulate in this obstack the value for the LS_COLORS environment + variable. */ +static struct obstack lsc_obstack; + +static const char *const slack_codes[] = +{ + "NORMAL", "NORM", "FILE", "DIR", "LNK", "LINK", + "SYMLINK", "ORPHAN", "MISSING", "FIFO", "PIPE", "SOCK", "BLK", "BLOCK", + "CHR", "CHAR", "DOOR", "EXEC", "LEFT", "LEFTCODE", "RIGHT", "RIGHTCODE", + "END", "ENDCODE", "SUID", "SETUID", "SGID", "SETGID", "STICKY", + "OTHER_WRITABLE", "OWR", "STICKY_OTHER_WRITABLE", "OWT", NULL +}; + +static const char *const ls_codes[] = +{ + "no", "no", "fi", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi", + "so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec", + "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", NULL +}; +#define array_len(Array) (sizeof (Array) / sizeof *(Array)) +verify (array_len (slack_codes) == array_len (ls_codes)); + +static struct option const long_options[] = + { + {"bourne-shell", no_argument, NULL, 'b'}, + {"sh", no_argument, NULL, 'b'}, + {"csh", no_argument, NULL, 'c'}, + {"c-shell", no_argument, NULL, 'c'}, + {"print-database", no_argument, NULL, 'p'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} + }; + +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name); + fputs (_("\ +Output commands to set the LS_COLORS environment variable.\n\ +\n\ +Determine format of output:\n\ + -b, --sh, --bourne-shell output Bourne shell code to set LS_COLORS\n\ + -c, --csh, --c-shell output C shell code to set LS_COLORS\n\ + -p, --print-database output defaults\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +If FILE is specified, read it to determine which colors to use for which\n\ +file types and extensions. Otherwise, a precompiled database is used.\n\ +For details on the format of these files, run `dircolors --print-database'.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + + exit (status); +} + +/* If the SHELL environment variable is set to `csh' or `tcsh,' + assume C shell. Else Bourne shell. */ + +static enum Shell_syntax +guess_shell_syntax (void) +{ + char *shell; + + shell = getenv ("SHELL"); + if (shell == NULL || *shell == '\0') + return SHELL_SYNTAX_UNKNOWN; + + shell = last_component (shell); + + if (STREQ (shell, "csh") || STREQ (shell, "tcsh")) + return SHELL_SYNTAX_C; + + return SHELL_SYNTAX_BOURNE; +} + +static void +parse_line (char const *line, char **keyword, char **arg) +{ + char const *p; + char const *keyword_start; + char const *arg_start; + + *keyword = NULL; + *arg = NULL; + + for (p = line; isspace (to_uchar (*p)); ++p) + continue; + + /* Ignore blank lines and shell-style comments. */ + if (*p == '\0' || *p == '#') + return; + + keyword_start = p; + + while (!isspace (to_uchar (*p)) && *p != '\0') + { + ++p; + } + + *keyword = xstrndup (keyword_start, p - keyword_start); + if (*p == '\0') + return; + + do + { + ++p; + } + while (isspace (to_uchar (*p))); + + if (*p == '\0' || *p == '#') + return; + + arg_start = p; + + while (*p != '\0' && *p != '#') + ++p; + + for (--p; isspace (to_uchar (*p)); --p) + continue; + ++p; + + *arg = xstrndup (arg_start, p - arg_start); +} + +/* FIXME: Write a string to standard out, while watching for "dangerous" + sequences like unescaped : and = characters. */ + +static void +append_quoted (const char *str) +{ + bool need_backslash = true; + + while (*str != '\0') + { + switch (*str) + { + case '\'': + APPEND_CHAR ('\''); + APPEND_CHAR ('\\'); + APPEND_CHAR ('\''); + need_backslash = true; + break; + + case '\\': + case '^': + need_backslash = !need_backslash; + break; + + case ':': + case '=': + if (need_backslash) + APPEND_CHAR ('\\'); + /* Fall through */ + + default: + need_backslash = true; + break; + } + + APPEND_CHAR (*str); + ++str; + } +} + +/* Read the file open on FP (with name FILENAME). First, look for a + `TERM name' directive where name matches the current terminal type. + Once found, translate and accumulate the associated directives onto + the global obstack LSC_OBSTACK. Give a diagnostic + upon failure (unrecognized keyword is the only way to fail here). + Return true if successful. */ + +static bool +dc_parse_stream (FILE *fp, const char *filename) +{ + size_t line_number = 0; + char const *next_G_line = G_line; + char *input_line = NULL; + size_t input_line_size = 0; + char const *line; + char const *term; + bool ok = true; + + /* State for the parser. */ + enum { ST_TERMNO, ST_TERMYES, ST_TERMSURE, ST_GLOBAL } state = ST_GLOBAL; + + /* Get terminal type */ + term = getenv ("TERM"); + if (term == NULL || *term == '\0') + term = "none"; + + while (1) + { + char *keywd, *arg; + bool unrecognized; + + ++line_number; + + if (fp) + { + if (getline (&input_line, &input_line_size, fp) <= 0) + { + free (input_line); + break; + } + line = input_line; + } + else + { + if (next_G_line == G_line + sizeof G_line) + break; + line = next_G_line; + next_G_line += strlen (next_G_line) + 1; + } + + parse_line (line, &keywd, &arg); + + if (keywd == NULL) + continue; + + if (arg == NULL) + { + error (0, 0, _("%s:%lu: invalid line; missing second token"), + filename, (unsigned long int) line_number); + ok = false; + free (keywd); + continue; + } + + unrecognized = false; + if (c_strcasecmp (keywd, "TERM") == 0) + { + if (STREQ (arg, term)) + state = ST_TERMSURE; + else if (state != ST_TERMSURE) + state = ST_TERMNO; + } + else + { + if (state == ST_TERMSURE) + state = ST_TERMYES; /* Another TERM can cancel */ + + if (state != ST_TERMNO) + { + if (keywd[0] == '.') + { + APPEND_CHAR ('*'); + append_quoted (keywd); + APPEND_CHAR ('='); + append_quoted (arg); + APPEND_CHAR (':'); + } + else if (keywd[0] == '*') + { + append_quoted (keywd); + APPEND_CHAR ('='); + append_quoted (arg); + APPEND_CHAR (':'); + } + else if (c_strcasecmp (keywd, "OPTIONS") == 0 + || c_strcasecmp (keywd, "COLOR") == 0 + || c_strcasecmp (keywd, "EIGHTBIT") == 0) + { + /* Ignore. */ + } + else + { + int i; + + for (i = 0; slack_codes[i] != NULL; ++i) + if (c_strcasecmp (keywd, slack_codes[i]) == 0) + break; + + if (slack_codes[i] != NULL) + { + APPEND_TWO_CHAR_STRING (ls_codes[i]); + APPEND_CHAR ('='); + append_quoted (arg); + APPEND_CHAR (':'); + } + else + { + unrecognized = true; + } + } + } + else + { + unrecognized = true; + } + } + + if (unrecognized && (state == ST_TERMSURE || state == ST_TERMYES)) + { + error (0, 0, _("%s:%lu: unrecognized keyword %s"), + (filename ? quote (filename) : _("")), + (unsigned long int) line_number, keywd); + ok = false; + } + + free (keywd); + free (arg); + } + + return ok; +} + +static bool +dc_parse_file (const char *filename) +{ + bool ok; + + if (! STREQ (filename, "-") && freopen (filename, "r", stdin) == NULL) + { + error (0, errno, "%s", filename); + return false; + } + + ok = dc_parse_stream (stdin, filename); + + if (fclose (stdin) != 0) + { + error (0, errno, "%s", quote (filename)); + return false; + } + + return ok; +} + +int +main (int argc, char **argv) +{ + bool ok = true; + int optc; + enum Shell_syntax syntax = SHELL_SYNTAX_UNKNOWN; + bool print_database = false; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "bcp", long_options, NULL)) != -1) + switch (optc) + { + case 'b': /* Bourne shell syntax. */ + syntax = SHELL_SYNTAX_BOURNE; + break; + + case 'c': /* C shell syntax. */ + syntax = SHELL_SYNTAX_C; + break; + + case 'p': + print_database = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + + argc -= optind; + argv += optind; + + /* It doesn't make sense to use --print with either of + --bourne or --c-shell. */ + if (print_database && syntax != SHELL_SYNTAX_UNKNOWN) + { + error (0, 0, + _("the options to output dircolors' internal database and\n\ +to select a shell syntax are mutually exclusive")); + usage (EXIT_FAILURE); + } + + if (!print_database < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[!print_database])); + if (print_database) + fprintf (stderr, "%s\n", + _("File operands cannot be combined with " + "--print-database (-p).")); + usage (EXIT_FAILURE); + } + + if (print_database) + { + char const *p = G_line; + while (p < G_line + sizeof G_line) + { + puts (p); + p += strlen (p) + 1; + } + } + else + { + /* If shell syntax was not explicitly specified, try to guess it. */ + if (syntax == SHELL_SYNTAX_UNKNOWN) + { + syntax = guess_shell_syntax (); + if (syntax == SHELL_SYNTAX_UNKNOWN) + { + error (EXIT_FAILURE, 0, + _("no SHELL environment variable, and no shell type option given")); + } + } + + obstack_init (&lsc_obstack); + if (argc == 0) + ok = dc_parse_stream (NULL, NULL); + else + ok = dc_parse_file (argv[0]); + + if (ok) + { + size_t len = obstack_object_size (&lsc_obstack); + char *s = obstack_finish (&lsc_obstack); + const char *prefix; + const char *suffix; + + if (syntax == SHELL_SYNTAX_BOURNE) + { + prefix = "LS_COLORS='"; + suffix = "';\nexport LS_COLORS\n"; + } + else + { + prefix = "setenv LS_COLORS '"; + suffix = "'\n"; + } + fputs (prefix, stdout); + fwrite (s, 1, len, stdout); + fputs (suffix, stdout); + } + } + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/dircolors.h b/src/dircolors.h new file mode 100644 index 0000000..c74fb53 --- /dev/null +++ b/src/dircolors.h @@ -0,0 +1,164 @@ +static char const G_line[] = +{ + '#',' ','C','o','n','f','i','g','u','r','a','t','i','o','n',' ','f','i','l','e',' ','f','o','r',' ','d','i','r','c','o','l','o','r','s',',',' ','a',' ','u','t','i','l','i','t','y',' ','t','o',' ','h','e','l','p',' ','y','o','u',' ','s','e','t',' ','t','h','e',0, + '#',' ','L','S','_','C','O','L','O','R','S',' ','e','n','v','i','r','o','n','m','e','n','t',' ','v','a','r','i','a','b','l','e',' ','u','s','e','d',' ','b','y',' ','G','N','U',' ','l','s',' ','w','i','t','h',' ','t','h','e',' ','-','-','c','o','l','o','r',' ','o','p','t','i','o','n','.',0, + '#',' ','C','o','p','y','r','i','g','h','t',' ','(','C',')',' ','1','9','9','6',',',' ','1','9','9','9',',',' ','2','0','0','0',',',' ','2','0','0','1',',',' ','2','0','0','2',',',' ','2','0','0','3',',',' ','2','0','0','4',',',' ','2','0','0','5',',',' ','2','0','0','6',0, + '#',' ','F','r','e','e',' ','S','o','f','t','w','a','r','e',' ','F','o','u','n','d','a','t','i','o','n',',',' ','I','n','c','.',0, + '#',' ','C','o','p','y','i','n','g',' ','a','n','d',' ','d','i','s','t','r','i','b','u','t','i','o','n',' ','o','f',' ','t','h','i','s',' ','f','i','l','e',',',' ','w','i','t','h',' ','o','r',' ','w','i','t','h','o','u','t',' ','m','o','d','i','f','i','c','a','t','i','o','n',',',0, + '#',' ','a','r','e',' ','p','e','r','m','i','t','t','e','d',' ','p','r','o','v','i','d','e','d',' ','t','h','e',' ','c','o','p','y','r','i','g','h','t',' ','n','o','t','i','c','e',' ','a','n','d',' ','t','h','i','s',' ','n','o','t','i','c','e',' ','a','r','e',' ','p','r','e','s','e','r','v','e','d','.',0, + '#',' ','T','h','e',' ','k','e','y','w','o','r','d','s',' ','C','O','L','O','R',',',' ','O','P','T','I','O','N','S',',',' ','a','n','d',' ','E','I','G','H','T','B','I','T',' ','(','h','o','n','o','r','e','d',' ','b','y',' ','t','h','e',0, + '#',' ','s','l','a','c','k','w','a','r','e',' ','v','e','r','s','i','o','n',' ','o','f',' ','d','i','r','c','o','l','o','r','s',')',' ','a','r','e',' ','r','e','c','o','g','n','i','z','e','d',' ','b','u','t',' ','i','g','n','o','r','e','d','.',0, + '#',' ','B','e','l','o','w',',',' ','t','h','e','r','e',' ','s','h','o','u','l','d',' ','b','e',' ','o','n','e',' ','T','E','R','M',' ','e','n','t','r','y',' ','f','o','r',' ','e','a','c','h',' ','t','e','r','m','t','y','p','e',' ','t','h','a','t',' ','i','s',' ','c','o','l','o','r','i','z','a','b','l','e',0, + 'T','E','R','M',' ','E','t','e','r','m',0, + 'T','E','R','M',' ','a','n','s','i',0, + 'T','E','R','M',' ','c','o','l','o','r','-','x','t','e','r','m',0, + 'T','E','R','M',' ','c','o','n','1','3','2','x','2','5',0, + 'T','E','R','M',' ','c','o','n','1','3','2','x','3','0',0, + 'T','E','R','M',' ','c','o','n','1','3','2','x','4','3',0, + 'T','E','R','M',' ','c','o','n','1','3','2','x','6','0',0, + 'T','E','R','M',' ','c','o','n','8','0','x','2','5',0, + 'T','E','R','M',' ','c','o','n','8','0','x','2','8',0, + 'T','E','R','M',' ','c','o','n','8','0','x','3','0',0, + 'T','E','R','M',' ','c','o','n','8','0','x','4','3',0, + 'T','E','R','M',' ','c','o','n','8','0','x','5','0',0, + 'T','E','R','M',' ','c','o','n','8','0','x','6','0',0, + 'T','E','R','M',' ','c','o','n','s','2','5',0, + 'T','E','R','M',' ','c','o','n','s','o','l','e',0, + 'T','E','R','M',' ','c','y','g','w','i','n',0, + 'T','E','R','M',' ','d','t','t','e','r','m',0, + 'T','E','R','M',' ','g','n','o','m','e',0, + 'T','E','R','M',' ','k','o','n','s','o','l','e',0, + 'T','E','R','M',' ','k','t','e','r','m',0, + 'T','E','R','M',' ','l','i','n','u','x',0, + 'T','E','R','M',' ','l','i','n','u','x','-','c',0, + 'T','E','R','M',' ','m','a','c','h','-','c','o','l','o','r',0, + 'T','E','R','M',' ','m','l','t','e','r','m',0, + 'T','E','R','M',' ','p','u','t','t','y',0, + 'T','E','R','M',' ','r','x','v','t',0, + 'T','E','R','M',' ','r','x','v','t','-','c','y','g','w','i','n',0, + 'T','E','R','M',' ','r','x','v','t','-','c','y','g','w','i','n','-','n','a','t','i','v','e',0, + 'T','E','R','M',' ','r','x','v','t','-','u','n','i','c','o','d','e',0, + 'T','E','R','M',' ','s','c','r','e','e','n',0, + 'T','E','R','M',' ','s','c','r','e','e','n','-','b','c','e',0, + 'T','E','R','M',' ','s','c','r','e','e','n','-','w',0, + 'T','E','R','M',' ','s','c','r','e','e','n','.','l','i','n','u','x',0, + 'T','E','R','M',' ','v','t','1','0','0',0, + 'T','E','R','M',' ','x','t','e','r','m',0, + 'T','E','R','M',' ','x','t','e','r','m','-','2','5','6','c','o','l','o','r',0, + 'T','E','R','M',' ','x','t','e','r','m','-','c','o','l','o','r',0, + 'T','E','R','M',' ','x','t','e','r','m','-','d','e','b','i','a','n',0, + '#',' ','B','e','l','o','w',' ','a','r','e',' ','t','h','e',' ','c','o','l','o','r',' ','i','n','i','t',' ','s','t','r','i','n','g','s',' ','f','o','r',' ','t','h','e',' ','b','a','s','i','c',' ','f','i','l','e',' ','t','y','p','e','s','.',' ','A',' ','c','o','l','o','r',' ','i','n','i','t',0, + '#',' ','s','t','r','i','n','g',' ','c','o','n','s','i','s','t','s',' ','o','f',' ','o','n','e',' ','o','r',' ','m','o','r','e',' ','o','f',' ','t','h','e',' ','f','o','l','l','o','w','i','n','g',' ','n','u','m','e','r','i','c',' ','c','o','d','e','s',':',0, + '#',' ','A','t','t','r','i','b','u','t','e',' ','c','o','d','e','s',':',0, + '#',' ','0','0','=','n','o','n','e',' ','0','1','=','b','o','l','d',' ','0','4','=','u','n','d','e','r','s','c','o','r','e',' ','0','5','=','b','l','i','n','k',' ','0','7','=','r','e','v','e','r','s','e',' ','0','8','=','c','o','n','c','e','a','l','e','d',0, + '#',' ','T','e','x','t',' ','c','o','l','o','r',' ','c','o','d','e','s',':',0, + '#',' ','3','0','=','b','l','a','c','k',' ','3','1','=','r','e','d',' ','3','2','=','g','r','e','e','n',' ','3','3','=','y','e','l','l','o','w',' ','3','4','=','b','l','u','e',' ','3','5','=','m','a','g','e','n','t','a',' ','3','6','=','c','y','a','n',' ','3','7','=','w','h','i','t','e',0, + '#',' ','B','a','c','k','g','r','o','u','n','d',' ','c','o','l','o','r',' ','c','o','d','e','s',':',0, + '#',' ','4','0','=','b','l','a','c','k',' ','4','1','=','r','e','d',' ','4','2','=','g','r','e','e','n',' ','4','3','=','y','e','l','l','o','w',' ','4','4','=','b','l','u','e',' ','4','5','=','m','a','g','e','n','t','a',' ','4','6','=','c','y','a','n',' ','4','7','=','w','h','i','t','e',0, + 'N','O','R','M','A','L',' ','0','0',' ','#',' ','g','l','o','b','a','l',' ','d','e','f','a','u','l','t',',',' ','a','l','t','h','o','u','g','h',' ','e','v','e','r','y','t','h','i','n','g',' ','s','h','o','u','l','d',' ','b','e',' ','s','o','m','e','t','h','i','n','g','.',0, + 'F','I','L','E',' ','0','0',' ','#',' ','n','o','r','m','a','l',' ','f','i','l','e',0, + 'D','I','R',' ','0','1',';','3','4',' ','#',' ','d','i','r','e','c','t','o','r','y',0, + 'L','I','N','K',' ','0','1',';','3','6',' ','#',' ','s','y','m','b','o','l','i','c',' ','l','i','n','k','.',' ','(','I','f',' ','y','o','u',' ','s','e','t',' ','t','h','i','s',' ','t','o',' ','\'','t','a','r','g','e','t','\'',' ','i','n','s','t','e','a','d',' ','o','f',' ','a',0, + ' ','#',' ','n','u','m','e','r','i','c','a','l',' ','v','a','l','u','e',',',' ','t','h','e',' ','c','o','l','o','r',' ','i','s',' ','a','s',' ','f','o','r',' ','t','h','e',' ','f','i','l','e',' ','p','o','i','n','t','e','d',' ','t','o','.',')',0, + 'F','I','F','O',' ','4','0',';','3','3',' ','#',' ','p','i','p','e',0, + 'S','O','C','K',' ','0','1',';','3','5',' ','#',' ','s','o','c','k','e','t',0, + 'D','O','O','R',' ','0','1',';','3','5',' ','#',' ','d','o','o','r',0, + 'B','L','K',' ','4','0',';','3','3',';','0','1',' ','#',' ','b','l','o','c','k',' ','d','e','v','i','c','e',' ','d','r','i','v','e','r',0, + 'C','H','R',' ','4','0',';','3','3',';','0','1',' ','#',' ','c','h','a','r','a','c','t','e','r',' ','d','e','v','i','c','e',' ','d','r','i','v','e','r',0, + 'O','R','P','H','A','N',' ','4','0',';','3','1',';','0','1',' ','#',' ','s','y','m','l','i','n','k',' ','t','o',' ','n','o','n','e','x','i','s','t','e','n','t',' ','f','i','l','e',',',' ','o','r',' ','n','o','n','-','s','t','a','t','\'','a','b','l','e',' ','f','i','l','e',0, + 'S','E','T','U','I','D',' ','3','7',';','4','1',' ','#',' ','f','i','l','e',' ','t','h','a','t',' ','i','s',' ','s','e','t','u','i','d',' ','(','u','+','s',')',0, + 'S','E','T','G','I','D',' ','3','0',';','4','3',' ','#',' ','f','i','l','e',' ','t','h','a','t',' ','i','s',' ','s','e','t','g','i','d',' ','(','g','+','s',')',0, + 'S','T','I','C','K','Y','_','O','T','H','E','R','_','W','R','I','T','A','B','L','E',' ','3','0',';','4','2',' ','#',' ','d','i','r',' ','t','h','a','t',' ','i','s',' ','s','t','i','c','k','y',' ','a','n','d',' ','o','t','h','e','r','-','w','r','i','t','a','b','l','e',' ','(','+','t',',','o','+','w',')',0, + 'O','T','H','E','R','_','W','R','I','T','A','B','L','E',' ','3','4',';','4','2',' ','#',' ','d','i','r',' ','t','h','a','t',' ','i','s',' ','o','t','h','e','r','-','w','r','i','t','a','b','l','e',' ','(','o','+','w',')',' ','a','n','d',' ','n','o','t',' ','s','t','i','c','k','y',0, + 'S','T','I','C','K','Y',' ','3','7',';','4','4',' ','#',' ','d','i','r',' ','w','i','t','h',' ','t','h','e',' ','s','t','i','c','k','y',' ','b','i','t',' ','s','e','t',' ','(','+','t',')',' ','a','n','d',' ','n','o','t',' ','o','t','h','e','r','-','w','r','i','t','a','b','l','e',0, + '#',' ','T','h','i','s',' ','i','s',' ','f','o','r',' ','f','i','l','e','s',' ','w','i','t','h',' ','e','x','e','c','u','t','e',' ','p','e','r','m','i','s','s','i','o','n',':',0, + 'E','X','E','C',' ','0','1',';','3','2',0, + '#',' ','L','i','s','t',' ','a','n','y',' ','f','i','l','e',' ','e','x','t','e','n','s','i','o','n','s',' ','l','i','k','e',' ','\'','.','g','z','\'',' ','o','r',' ','\'','.','t','a','r','\'',' ','t','h','a','t',' ','y','o','u',' ','w','o','u','l','d',' ','l','i','k','e',' ','l','s',0, + '#',' ','t','o',' ','c','o','l','o','r','i','z','e',' ','b','e','l','o','w','.',' ','P','u','t',' ','t','h','e',' ','e','x','t','e','n','s','i','o','n',',',' ','a',' ','s','p','a','c','e',',',' ','a','n','d',' ','t','h','e',' ','c','o','l','o','r',' ','i','n','i','t',' ','s','t','r','i','n','g','.',0, + '#',' ','(','a','n','d',' ','a','n','y',' ','c','o','m','m','e','n','t','s',' ','y','o','u',' ','w','a','n','t',' ','t','o',' ','a','d','d',' ','a','f','t','e','r',' ','a',' ','\'','#','\'',')',0, + '#',' ','I','f',' ','y','o','u',' ','u','s','e',' ','D','O','S','-','s','t','y','l','e',' ','s','u','f','f','i','x','e','s',',',' ','y','o','u',' ','m','a','y',' ','w','a','n','t',' ','t','o',' ','u','n','c','o','m','m','e','n','t',' ','t','h','e',' ','f','o','l','l','o','w','i','n','g',':',0, + '#','.','c','m','d',' ','0','1',';','3','2',' ','#',' ','e','x','e','c','u','t','a','b','l','e','s',' ','(','b','r','i','g','h','t',' ','g','r','e','e','n',')',0, + '#','.','e','x','e',' ','0','1',';','3','2',0, + '#','.','c','o','m',' ','0','1',';','3','2',0, + '#','.','b','t','m',' ','0','1',';','3','2',0, + '#','.','b','a','t',' ','0','1',';','3','2',0, + '#',' ','O','r',' ','i','f',' ','y','o','u',' ','w','a','n','t',' ','t','o',' ','c','o','l','o','r','i','z','e',' ','s','c','r','i','p','t','s',' ','e','v','e','n',' ','i','f',' ','t','h','e','y',' ','d','o',' ','n','o','t',' ','h','a','v','e',' ','t','h','e',0, + '#',' ','e','x','e','c','u','t','a','b','l','e',' ','b','i','t',' ','a','c','t','u','a','l','l','y',' ','s','e','t','.',0, + '#','.','s','h',' ','0','1',';','3','2',0, + '#','.','c','s','h',' ','0','1',';','3','2',0, + ' ','#',' ','a','r','c','h','i','v','e','s',' ','o','r',' ','c','o','m','p','r','e','s','s','e','d',' ','(','b','r','i','g','h','t',' ','r','e','d',')',0, + '.','t','a','r',' ','0','1',';','3','1',0, + '.','t','g','z',' ','0','1',';','3','1',0, + '.','a','r','j',' ','0','1',';','3','1',0, + '.','t','a','z',' ','0','1',';','3','1',0, + '.','l','z','h',' ','0','1',';','3','1',0, + '.','z','i','p',' ','0','1',';','3','1',0, + '.','z',' ','0','1',';','3','1',0, + '.','Z',' ','0','1',';','3','1',0, + '.','g','z',' ','0','1',';','3','1',0, + '.','b','z','2',' ','0','1',';','3','1',0, + '.','b','z',' ','0','1',';','3','1',0, + '.','t','b','z','2',' ','0','1',';','3','1',0, + '.','t','z',' ','0','1',';','3','1',0, + '.','d','e','b',' ','0','1',';','3','1',0, + '.','r','p','m',' ','0','1',';','3','1',0, + '.','j','a','r',' ','0','1',';','3','1',0, + '.','r','a','r',' ','0','1',';','3','1',0, + '.','a','c','e',' ','0','1',';','3','1',0, + '.','z','o','o',' ','0','1',';','3','1',0, + '.','c','p','i','o',' ','0','1',';','3','1',0, + '.','7','z',' ','0','1',';','3','1',0, + '.','r','z',' ','0','1',';','3','1',0, + '#',' ','i','m','a','g','e',' ','f','o','r','m','a','t','s',0, + '.','j','p','g',' ','0','1',';','3','5',0, + '.','j','p','e','g',' ','0','1',';','3','5',0, + '.','g','i','f',' ','0','1',';','3','5',0, + '.','b','m','p',' ','0','1',';','3','5',0, + '.','p','b','m',' ','0','1',';','3','5',0, + '.','p','g','m',' ','0','1',';','3','5',0, + '.','p','p','m',' ','0','1',';','3','5',0, + '.','t','g','a',' ','0','1',';','3','5',0, + '.','x','b','m',' ','0','1',';','3','5',0, + '.','x','p','m',' ','0','1',';','3','5',0, + '.','t','i','f',' ','0','1',';','3','5',0, + '.','t','i','f','f',' ','0','1',';','3','5',0, + '.','p','n','g',' ','0','1',';','3','5',0, + '.','m','n','g',' ','0','1',';','3','5',0, + '.','p','c','x',' ','0','1',';','3','5',0, + '.','m','o','v',' ','0','1',';','3','5',0, + '.','m','p','g',' ','0','1',';','3','5',0, + '.','m','p','e','g',' ','0','1',';','3','5',0, + '.','m','2','v',' ','0','1',';','3','5',0, + '.','m','k','v',' ','0','1',';','3','5',0, + '.','o','g','m',' ','0','1',';','3','5',0, + '.','m','p','4',' ','0','1',';','3','5',0, + '.','m','4','v',' ','0','1',';','3','5',0, + '.','m','p','4','v',' ','0','1',';','3','5',0, + '.','v','o','b',' ','0','1',';','3','5',0, + '.','q','t',' ','0','1',';','3','5',0, + '.','n','u','v',' ','0','1',';','3','5',0, + '.','w','m','v',' ','0','1',';','3','5',0, + '.','a','s','f',' ','0','1',';','3','5',0, + '.','r','m',' ','0','1',';','3','5',0, + '.','r','m','v','b',' ','0','1',';','3','5',0, + '.','f','l','c',' ','0','1',';','3','5',0, + '.','a','v','i',' ','0','1',';','3','5',0, + '.','f','l','i',' ','0','1',';','3','5',0, + '.','g','l',' ','0','1',';','3','5',0, + '.','d','l',' ','0','1',';','3','5',0, + '.','x','c','f',' ','0','1',';','3','5',0, + '.','x','w','d',' ','0','1',';','3','5',0, + '.','y','u','v',' ','0','1',';','3','5',0, + '#',' ','a','u','d','i','o',' ','f','o','r','m','a','t','s',0, + '.','a','a','c',' ','0','0',';','3','6',0, + '.','a','u',' ','0','0',';','3','6',0, + '.','f','l','a','c',' ','0','0',';','3','6',0, + '.','m','i','d',' ','0','0',';','3','6',0, + '.','m','i','d','i',' ','0','0',';','3','6',0, + '.','m','k','a',' ','0','0',';','3','6',0, + '.','m','p','3',' ','0','0',';','3','6',0, + '.','m','p','c',' ','0','0',';','3','6',0, + '.','o','g','g',' ','0','0',';','3','6',0, + '.','r','a',' ','0','0',';','3','6',0, + '.','w','a','v',' ','0','0',';','3','6',0, +}; diff --git a/src/dircolors.hin b/src/dircolors.hin new file mode 100644 index 0000000..8d550d1 --- /dev/null +++ b/src/dircolors.hin @@ -0,0 +1,171 @@ +# Configuration file for dircolors, a utility to help you set the +# LS_COLORS environment variable used by GNU ls with the --color option. + +# Copyright (C) 1996, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 +# Free Software Foundation, Inc. +# Copying and distribution of this file, with or without modification, +# are permitted provided the copyright notice and this notice are preserved. + +# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the +# slackware version of dircolors) are recognized but ignored. + +# Below, there should be one TERM entry for each termtype that is colorizable +TERM Eterm +TERM ansi +TERM color-xterm +TERM con132x25 +TERM con132x30 +TERM con132x43 +TERM con132x60 +TERM con80x25 +TERM con80x28 +TERM con80x30 +TERM con80x43 +TERM con80x50 +TERM con80x60 +TERM cons25 +TERM console +TERM cygwin +TERM dtterm +TERM gnome +TERM konsole +TERM kterm +TERM linux +TERM linux-c +TERM mach-color +TERM mlterm +TERM putty +TERM rxvt +TERM rxvt-cygwin +TERM rxvt-cygwin-native +TERM rxvt-unicode +TERM screen +TERM screen-bce +TERM screen-w +TERM screen.linux +TERM vt100 +TERM xterm +TERM xterm-256color +TERM xterm-color +TERM xterm-debian + +# Below are the color init strings for the basic file types. A color init +# string consists of one or more of the following numeric codes: +# Attribute codes: +# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed +# Text color codes: +# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white +# Background color codes: +# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white +NORMAL 00 # global default, although everything should be something. +FILE 00 # normal file +DIR 01;34 # directory +LINK 01;36 # symbolic link. (If you set this to 'target' instead of a + # numerical value, the color is as for the file pointed to.) +FIFO 40;33 # pipe +SOCK 01;35 # socket +DOOR 01;35 # door +BLK 40;33;01 # block device driver +CHR 40;33;01 # character device driver +ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file +SETUID 37;41 # file that is setuid (u+s) +SETGID 30;43 # file that is setgid (g+s) +STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w) +OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky +STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable + +# This is for files with execute permission: +EXEC 01;32 + +# List any file extensions like '.gz' or '.tar' that you would like ls +# to colorize below. Put the extension, a space, and the color init string. +# (and any comments you want to add after a '#') + +# If you use DOS-style suffixes, you may want to uncomment the following: +#.cmd 01;32 # executables (bright green) +#.exe 01;32 +#.com 01;32 +#.btm 01;32 +#.bat 01;32 +# Or if you want to colorize scripts even if they do not have the +# executable bit actually set. +#.sh 01;32 +#.csh 01;32 + + # archives or compressed (bright red) +.tar 01;31 +.tgz 01;31 +.arj 01;31 +.taz 01;31 +.lzh 01;31 +.zip 01;31 +.z 01;31 +.Z 01;31 +.gz 01;31 +.bz2 01;31 +.bz 01;31 +.tbz2 01;31 +.tz 01;31 +.deb 01;31 +.rpm 01;31 +.jar 01;31 +.rar 01;31 +.ace 01;31 +.zoo 01;31 +.cpio 01;31 +.7z 01;31 +.rz 01;31 + +# image formats +.jpg 01;35 +.jpeg 01;35 +.gif 01;35 +.bmp 01;35 +.pbm 01;35 +.pgm 01;35 +.ppm 01;35 +.tga 01;35 +.xbm 01;35 +.xpm 01;35 +.tif 01;35 +.tiff 01;35 +.png 01;35 +.mng 01;35 +.pcx 01;35 +.mov 01;35 +.mpg 01;35 +.mpeg 01;35 +.m2v 01;35 +.mkv 01;35 +.ogm 01;35 +.mp4 01;35 +.m4v 01;35 +.mp4v 01;35 +.vob 01;35 +.qt 01;35 +.nuv 01;35 +.wmv 01;35 +.asf 01;35 +.rm 01;35 +.rmvb 01;35 +.flc 01;35 +.avi 01;35 +.fli 01;35 +.gl 01;35 +.dl 01;35 +.xcf 01;35 +.xwd 01;35 +.yuv 01;35 + +# audio formats +.aac 00;36 +.au 00;36 +.flac 00;36 +.mid 00;36 +.midi 00;36 +.mka 00;36 +.mp3 00;36 +.mpc 00;36 +.ogg 00;36 +.ra 00;36 +.wav 00;36 diff --git a/src/dirname.c b/src/dirname.c new file mode 100644 index 0000000..2253391 --- /dev/null +++ b/src/dirname.c @@ -0,0 +1,117 @@ +/* dirname -- strip suffix from file name + + Copyright (C) 1990-1997, 1999-2002, 2004, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by David MacKenzie and Jim Meyering. */ + +#include +#include +#include +#include + +#include "system.h" +#include "long-options.h" +#include "error.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "dirname" + +#define AUTHORS "David MacKenzie", "Jim Meyering" + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s NAME\n\ + or: %s OPTION\n\ +"), + program_name, program_name); + fputs (_("\ +Print NAME with its trailing /component removed; if NAME contains no /'s,\n\ +output `.' (meaning the current directory).\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\ +\n\ +Examples:\n\ + %s /usr/bin/sort Output \"/usr/bin\".\n\ + %s stdio.h Output \".\".\n\ +"), + program_name, program_name); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + static char const dot = '.'; + char const *result; + size_t len; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "+", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (argc < optind + 1) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + if (optind + 1 < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); + usage (EXIT_FAILURE); + } + + result = argv[optind]; + len = dir_len (result); + + if (! len) + { + result = ˙ + len = 1; + } + + fwrite (result, 1, len, stdout); + putchar ('\n'); + + exit (EXIT_SUCCESS); +} diff --git a/src/du.c b/src/du.c new file mode 100644 index 0000000..206d318 --- /dev/null +++ b/src/du.c @@ -0,0 +1,1021 @@ +/* du -- summarize disk usage + Copyright (C) 1988-1991, 1995-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Differences from the Unix du: + * Doesn't simply ignore the names of regular files given as arguments + when -a is given. + + By tege@sics.se, Torbjorn Granlund, + and djm@ai.mit.edu, David MacKenzie. + Variable blocks added by lm@sgi.com and eggert@twinsun.com. + Rewritten to use nftw, then to use fts by Jim Meyering. */ + +#include +#include +#include +#include +#include +#include "system.h" +#include "argmatch.h" +#include "error.h" +#include "exclude.h" +#include "fprintftime.h" +#include "hash.h" +#include "human.h" +#include "inttostr.h" +#include "quote.h" +#include "quotearg.h" +#include "readtokens0.h" +#include "same.h" +#include "stat-time.h" +#include "xfts.h" +#include "xstrtol.h" + +extern bool fts_debug; + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "du" + +#define AUTHORS \ + "Torbjorn Granlund", "David MacKenzie, Paul Eggert", "Jim Meyering" + +#if DU_DEBUG +# define FTS_CROSS_CHECK(Fts) fts_cross_check (Fts) +# define DEBUG_OPT "d" +#else +# define FTS_CROSS_CHECK(Fts) +# define DEBUG_OPT +#endif + +/* Initial size of the hash table. */ +#define INITIAL_TABLE_SIZE 103 + +/* Hash structure for inode and device numbers. The separate entry + structure makes it easier to rehash "in place". */ + +struct entry +{ + ino_t st_ino; + dev_t st_dev; +}; + +/* A set of dev/ino pairs. */ +static Hash_table *htab; + +/* Define a class for collecting directory information. */ + +struct duinfo +{ + /* Size of files in directory. */ + uintmax_t size; + + /* Latest time stamp found. If tmax.tv_sec == TYPE_MINIMUM (time_t) + && tmax.tv_nsec < 0, no time stamp has been found. */ + struct timespec tmax; +}; + +/* Initialize directory data. */ +static inline void +duinfo_init (struct duinfo *a) +{ + a->size = 0; + a->tmax.tv_sec = TYPE_MINIMUM (time_t); + a->tmax.tv_nsec = -1; +} + +/* Set directory data. */ +static inline void +duinfo_set (struct duinfo *a, uintmax_t size, struct timespec tmax) +{ + a->size = size; + a->tmax = tmax; +} + +/* Accumulate directory data. */ +static inline void +duinfo_add (struct duinfo *a, struct duinfo const *b) +{ + a->size += b->size; + if (timespec_cmp (a->tmax, b->tmax) < 0) + a->tmax = b->tmax; +} + +/* A structure for per-directory level information. */ +struct dulevel +{ + /* Entries in this directory. */ + struct duinfo ent; + + /* Total for subdirectories. */ + struct duinfo subdir; +}; + +/* Name under which this program was invoked. */ +char *program_name; + +/* If true, display counts for all files, not just directories. */ +static bool opt_all = false; + +/* If true, rather than using the disk usage of each file, + use the apparent size (a la stat.st_size). */ +static bool apparent_size = false; + +/* If true, count each hard link of files with multiple links. */ +static bool opt_count_all = false; + +/* If true, output the NUL byte instead of a newline at the end of each line. */ +static bool opt_nul_terminate_output = false; + +/* If true, print a grand total at the end. */ +static bool print_grand_total = false; + +/* If nonzero, do not add sizes of subdirectories. */ +static bool opt_separate_dirs = false; + +/* Show the total for each directory (and file if --all) that is at + most MAX_DEPTH levels down from the root of the hierarchy. The root + is at level 0, so `du --max-depth=0' is equivalent to `du -s'. */ +static size_t max_depth = SIZE_MAX; + +/* Human-readable options for output. */ +static int human_output_opts; + +/* If true, print most recently modified date, using the specified format. */ +static bool opt_time = false; + +/* Type of time to display. controlled by --time. */ + +enum time_type + { + time_mtime, /* default */ + time_ctime, + time_atime + }; + +static enum time_type time_type = time_mtime; + +/* User specified date / time style */ +static char const *time_style = NULL; + +/* Format used to display date / time. Controlled by --time-style */ +static char const *time_format = NULL; + +/* The units to use when printing sizes. */ +static uintmax_t output_block_size; + +/* File name patterns to exclude. */ +static struct exclude *exclude; + +/* Grand total size of all args, in bytes. Also latest modified date. */ +static struct duinfo tot_dui; + +#define IS_DIR_TYPE(Type) \ + ((Type) == FTS_DP \ + || (Type) == FTS_DNR) + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + APPARENT_SIZE_OPTION = CHAR_MAX + 1, + EXCLUDE_OPTION, + FILES0_FROM_OPTION, + HUMAN_SI_OPTION, + + /* FIXME: --kilobytes is deprecated (but not -k); remove in late 2006 */ + KILOBYTES_LONG_OPTION, + + MAX_DEPTH_OPTION, + + /* FIXME: --megabytes is deprecated (but not -m); remove in late 2006 */ + MEGABYTES_LONG_OPTION, + + TIME_OPTION, + TIME_STYLE_OPTION +}; + +static struct option const long_options[] = +{ + {"all", no_argument, NULL, 'a'}, + {"apparent-size", no_argument, NULL, APPARENT_SIZE_OPTION}, + {"block-size", required_argument, NULL, 'B'}, + {"bytes", no_argument, NULL, 'b'}, + {"count-links", no_argument, NULL, 'l'}, + {"dereference", no_argument, NULL, 'L'}, + {"dereference-args", no_argument, NULL, 'D'}, + {"exclude", required_argument, NULL, EXCLUDE_OPTION}, + {"exclude-from", required_argument, NULL, 'X'}, + {"files0-from", required_argument, NULL, FILES0_FROM_OPTION}, + {"human-readable", no_argument, NULL, 'h'}, + {"si", no_argument, NULL, HUMAN_SI_OPTION}, + {"kilobytes", no_argument, NULL, KILOBYTES_LONG_OPTION}, + {"max-depth", required_argument, NULL, MAX_DEPTH_OPTION}, + {"null", no_argument, NULL, '0'}, + {"megabytes", no_argument, NULL, MEGABYTES_LONG_OPTION}, + {"no-dereference", no_argument, NULL, 'P'}, + {"one-file-system", no_argument, NULL, 'x'}, + {"separate-dirs", no_argument, NULL, 'S'}, + {"summarize", no_argument, NULL, 's'}, + {"total", no_argument, NULL, 'c'}, + {"time", optional_argument, NULL, TIME_OPTION}, + {"time-style", required_argument, NULL, TIME_STYLE_OPTION}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +static char const *const time_args[] = +{ + "atime", "access", "use", "ctime", "status", NULL +}; +static enum time_type const time_types[] = +{ + time_atime, time_atime, time_atime, time_ctime, time_ctime +}; +ARGMATCH_VERIFY (time_args, time_types); + +/* `full-iso' uses full ISO-style dates and times. `long-iso' uses longer + ISO-style time stamps, though shorter than `full-iso'. `iso' uses shorter + ISO-style time stamps. */ +enum time_style + { + full_iso_time_style, /* --time-style=full-iso */ + long_iso_time_style, /* --time-style=long-iso */ + iso_time_style /* --time-style=iso */ + }; + +static char const *const time_style_args[] = +{ + "full-iso", "long-iso", "iso", NULL +}; +static enum time_style const time_style_types[] = +{ + full_iso_time_style, long_iso_time_style, iso_time_style +}; +ARGMATCH_VERIFY (time_style_args, time_style_types); + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ + or: %s [OPTION]... --files0-from=F\n\ +"), program_name, program_name); + fputs (_("\ +Summarize disk usage of each FILE, recursively for directories.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -a, --all write counts for all files, not just directories\n\ + --apparent-size print apparent sizes, rather than disk usage; although\n\ + the apparent size is usually smaller, it may be\n\ + larger due to holes in (`sparse') files, internal\n\ + fragmentation, indirect blocks, and the like\n\ +"), stdout); + fputs (_("\ + -B, --block-size=SIZE use SIZE-byte blocks\n\ + -b, --bytes equivalent to `--apparent-size --block-size=1'\n\ + -c, --total produce a grand total\n\ + -D, --dereference-args dereference FILEs that are symbolic links\n\ +"), stdout); + fputs (_("\ + --files0-from=F summarize disk usage of the NUL-terminated file\n\ + names specified in file F\n\ + -H like --si, but also evokes a warning; will soon\n\ + change to be equivalent to --dereference-args (-D)\n\ + -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)\n\ + --si like -h, but use powers of 1000 not 1024\n\ +"), stdout); + fputs (_("\ + -k like --block-size=1K\n\ + -l, --count-links count sizes many times if hard linked\n\ + -m like --block-size=1M\n\ +"), stdout); + fputs (_("\ + -L, --dereference dereference all symbolic links\n\ + -P, --no-dereference don't follow any symbolic links (this is the default)\n\ + -0, --null end each output line with 0 byte rather than newline\n\ + -S, --separate-dirs do not include size of subdirectories\n\ + -s, --summarize display only a total for each argument\n\ +"), stdout); + fputs (_("\ + -x, --one-file-system skip directories on different file systems\n\ + -X FILE, --exclude-from=FILE Exclude files that match any pattern in FILE.\n\ + --exclude=PATTERN Exclude files that match PATTERN.\n\ + --max-depth=N print the total for a directory (or file, with --all)\n\ + only if it is N or fewer levels below the command\n\ + line argument; --max-depth=0 is the same as\n\ + --summarize\n\ +"), stdout); + fputs (_("\ + --time show time of the last modification of any file in the\n\ + directory, or any of its subdirectories\n\ + --time=WORD show time as WORD instead of modification time:\n\ + atime, access, use, ctime or status\n\ + --time-style=STYLE show times using style STYLE:\n\ + full-iso, long-iso, iso, +FORMAT\n\ + FORMAT is interpreted like `date'\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\n\ +SIZE may be (or may be an integer optionally followed by) one of following:\n\ +kB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +static size_t +entry_hash (void const *x, size_t table_size) +{ + struct entry const *p = x; + + /* Ignoring the device number here should be fine. */ + /* The cast to uintmax_t prevents negative remainders + if st_ino is negative. */ + return (uintmax_t) p->st_ino % table_size; +} + +/* Compare two dev/ino pairs. Return true if they are the same. */ +static bool +entry_compare (void const *x, void const *y) +{ + struct entry const *a = x; + struct entry const *b = y; + return SAME_INODE (*a, *b) ? true : false; +} + +/* Try to insert the INO/DEV pair into the global table, HTAB. + Return true if the pair is successfully inserted, + false if the pair is already in the table. */ +static bool +hash_ins (ino_t ino, dev_t dev) +{ + struct entry *ent; + struct entry *ent_from_table; + + ent = xmalloc (sizeof *ent); + ent->st_ino = ino; + ent->st_dev = dev; + + ent_from_table = hash_insert (htab, ent); + if (ent_from_table == NULL) + { + /* Insertion failed due to lack of memory. */ + xalloc_die (); + } + + if (ent_from_table == ent) + { + /* Insertion succeeded. */ + return true; + } + + /* That pair is already in the table, so ENT was not inserted. Free it. */ + free (ent); + + return false; +} + +/* Initialize the hash table. */ +static void +hash_init (void) +{ + htab = hash_initialize (INITIAL_TABLE_SIZE, NULL, + entry_hash, entry_compare, free); + if (htab == NULL) + xalloc_die (); +} + +/* FIXME: this code is nearly identical to code in date.c */ +/* Display the date and time in WHEN according to the format specified + in FORMAT. */ + +static void +show_date (const char *format, struct timespec when) +{ + struct tm *tm = localtime (&when.tv_sec); + if (! tm) + { + char buf[INT_BUFSIZE_BOUND (intmax_t)]; + error (0, 0, _("time %s is out of range"), + (TYPE_SIGNED (time_t) + ? imaxtostr (when.tv_sec, buf) + : umaxtostr (when.tv_sec, buf))); + fputs (buf, stdout); + return; + } + + fprintftime (stdout, format, tm, 0, when.tv_nsec); +} + +/* Print N_BYTES. Convert it to a readable value before printing. */ + +static void +print_only_size (uintmax_t n_bytes) +{ + char buf[LONGEST_HUMAN_READABLE + 1]; + fputs (human_readable (n_bytes, buf, human_output_opts, + 1, output_block_size), stdout); +} + +/* Print size (and optionally time) indicated by *PDUI, followed by STRING. */ + +static void +print_size (const struct duinfo *pdui, const char *string) +{ + print_only_size (pdui->size); + if (opt_time) + { + putchar ('\t'); + show_date (time_format, pdui->tmax); + } + printf ("\t%s%c", string, opt_nul_terminate_output ? '\0' : '\n'); + fflush (stdout); +} + +/* This function is called once for every file system object that fts + encounters. fts does a depth-first traversal. This function knows + that and accumulates per-directory totals based on changes in + the depth of the current entry. It returns true on success. */ + +static bool +process_file (FTS *fts, FTSENT *ent) +{ + bool ok; + struct duinfo dui; + struct duinfo dui_to_print; + size_t level; + static size_t prev_level; + static size_t n_alloc; + /* First element of the structure contains: + The sum of the st_size values of all entries in the single directory + at the corresponding level. Although this does include the st_size + corresponding to each subdirectory, it does not include the size of + any file in a subdirectory. Also corresponding last modified date. + Second element of the structure contains: + The sum of the sizes of all entries in the hierarchy at or below the + directory at the specified level. */ + static struct dulevel *dulvl; + bool print = true; + + const char *file = ent->fts_path; + const struct stat *sb = ent->fts_statp; + bool skip; + + /* If necessary, set FTS_SKIP before returning. */ + skip = excluded_file_name (exclude, ent->fts_path); + if (skip) + fts_set (fts, ent, FTS_SKIP); + + switch (ent->fts_info) + { + case FTS_NS: + error (0, ent->fts_errno, _("cannot access %s"), quote (file)); + return false; + + case FTS_ERR: + /* if (S_ISDIR (ent->fts_statp->st_mode) && FIXME */ + error (0, ent->fts_errno, _("%s"), quote (file)); + return false; + + case FTS_DNR: + /* Don't return just yet, since although the directory is not readable, + we were able to stat it, so we do have a size. */ + error (0, ent->fts_errno, _("cannot read directory %s"), quote (file)); + ok = false; + break; + + default: + ok = true; + break; + } + + /* If this is the first (pre-order) encounter with a directory, + or if it's the second encounter for a skipped directory, then + return right away. */ + if (ent->fts_info == FTS_D || skip) + return ok; + + /* If the file is being excluded or if it has already been counted + via a hard link, then don't let it contribute to the sums. */ + if (skip + || (!opt_count_all + && ! S_ISDIR (sb->st_mode) + && 1 < sb->st_nlink + && ! hash_ins (sb->st_ino, sb->st_dev))) + { + /* Note that we must not simply return here. + We still have to update prev_level and maybe propagate + some sums up the hierarchy. */ + duinfo_init (&dui); + print = false; + } + else + { + duinfo_set (&dui, + (apparent_size + ? sb->st_size + : (uintmax_t) ST_NBLOCKS (*sb) * ST_NBLOCKSIZE), + (time_type == time_mtime ? get_stat_mtime (sb) + : time_type == time_atime ? get_stat_atime (sb) + : get_stat_ctime (sb))); + } + + level = ent->fts_level; + dui_to_print = dui; + + if (n_alloc == 0) + { + n_alloc = level + 10; + dulvl = xcalloc (n_alloc, sizeof *dulvl); + } + else + { + if (level == prev_level) + { + /* This is usually the most common case. Do nothing. */ + } + else if (level > prev_level) + { + /* Descending the hierarchy. + Clear the accumulators for *all* levels between prev_level + and the current one. The depth may change dramatically, + e.g., from 1 to 10. */ + size_t i; + + if (n_alloc <= level) + { + dulvl = xnrealloc (dulvl, level, 2 * sizeof *dulvl); + n_alloc = level * 2; + } + + for (i = prev_level + 1; i <= level; i++) + { + duinfo_init (&dulvl[i].ent); + duinfo_init (&dulvl[i].subdir); + } + } + else /* level < prev_level */ + { + /* Ascending the hierarchy. + Process a directory only after all entries in that + directory have been processed. When the depth decreases, + propagate sums from the children (prev_level) to the parent. + Here, the current level is always one smaller than the + previous one. */ + assert (level == prev_level - 1); + duinfo_add (&dui_to_print, &dulvl[prev_level].ent); + if (!opt_separate_dirs) + duinfo_add (&dui_to_print, &dulvl[prev_level].subdir); + duinfo_add (&dulvl[level].subdir, &dulvl[prev_level].ent); + duinfo_add (&dulvl[level].subdir, &dulvl[prev_level].subdir); + } + } + + prev_level = level; + + /* Let the size of a directory entry contribute to the total for the + containing directory, unless --separate-dirs (-S) is specified. */ + if ( ! (opt_separate_dirs && IS_DIR_TYPE (ent->fts_info))) + duinfo_add (&dulvl[level].ent, &dui); + + /* Even if this directory is unreadable or we can't chdir into it, + do let its size contribute to the total, ... */ + duinfo_add (&tot_dui, &dui); + + /* ... but don't print out a total for it, since without the size(s) + of any potential entries, it could be very misleading. */ + if (ent->fts_info == FTS_DNR) + return ok; + + /* If we're not counting an entry, e.g., because it's a hard link + to a file we've already counted (and --count-links), then don't + print a line for it. */ + if (!print) + return ok; + + if ((IS_DIR_TYPE (ent->fts_info) && level <= max_depth) + || ((opt_all && level <= max_depth) || level == 0)) + print_size (&dui_to_print, file); + + return ok; +} + +/* Recursively print the sizes of the directories (and, if selected, files) + named in FILES, the last entry of which is NULL. + BIT_FLAGS controls how fts works. + Return true if successful. */ + +static bool +du_files (char **files, int bit_flags) +{ + bool ok = true; + + if (*files) + { + FTS *fts = xfts_open (files, bit_flags, NULL); + + while (1) + { + FTSENT *ent; + + ent = fts_read (fts); + if (ent == NULL) + { + if (errno != 0) + { + /* FIXME: try to give a better message */ + error (0, errno, _("fts_read failed")); + ok = false; + } + break; + } + FTS_CROSS_CHECK (fts); + + ok &= process_file (fts, ent); + } + + /* Ignore failure, since the only way it can do so is in failing to + return to the original directory, and since we're about to exit, + that doesn't matter. */ + fts_close (fts); + } + + if (print_grand_total) + print_size (&tot_dui, _("total")); + + return ok; +} + +int +main (int argc, char **argv) +{ + int c; + char *cwd_only[2]; + bool max_depth_specified = false; + char **files; + bool ok = true; + char *files_from = NULL; + struct Tokens tok; + + /* Bit flags that control how fts works. */ + int bit_flags = FTS_TIGHT_CYCLE_CHECK; + + /* Select one of the three FTS_ options that control if/when + to follow a symlink. */ + int symlink_deref_bits = FTS_PHYSICAL; + + /* If true, display only a total for each argument. */ + bool opt_summarize_only = false; + + cwd_only[0] = "."; + cwd_only[1] = NULL; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + exclude = new_exclude (); + + human_output_opts = human_options (getenv ("DU_BLOCK_SIZE"), false, + &output_block_size); + + while ((c = getopt_long (argc, argv, DEBUG_OPT "0abchHklmsxB:DLPSX:", + long_options, NULL)) != -1) + { + switch (c) + { +#if DU_DEBUG + case 'd': + fts_debug = true; + break; +#endif + + case '0': + opt_nul_terminate_output = true; + break; + + case 'a': + opt_all = true; + break; + + case APPARENT_SIZE_OPTION: + apparent_size = true; + break; + + case 'b': + apparent_size = true; + human_output_opts = 0; + output_block_size = 1; + break; + + case 'c': + print_grand_total = true; + break; + + case 'h': + human_output_opts = human_autoscale | human_SI | human_base_1024; + output_block_size = 1; + break; + + case 'H': /* FIXME: remove warning and move this "case 'H'" to + precede --dereference-args in late 2006. */ + error (0, 0, _("WARNING: use --si, not -H; the meaning of the -H\ + option will soon\nchange to be the same as that of --dereference-args (-D)")); + /* fall through */ + case HUMAN_SI_OPTION: + human_output_opts = human_autoscale | human_SI; + output_block_size = 1; + break; + + case KILOBYTES_LONG_OPTION: + error (0, 0, + _("the --kilobytes option is deprecated; use -k instead")); + /* fall through */ + case 'k': + human_output_opts = 0; + output_block_size = 1024; + break; + + case MAX_DEPTH_OPTION: /* --max-depth=N */ + { + unsigned long int tmp_ulong; + if (xstrtoul (optarg, NULL, 0, &tmp_ulong, NULL) == LONGINT_OK + && tmp_ulong <= SIZE_MAX) + { + max_depth_specified = true; + max_depth = tmp_ulong; + } + else + { + error (0, 0, _("invalid maximum depth %s"), + quote (optarg)); + ok = false; + } + } + break; + + case MEGABYTES_LONG_OPTION: + error (0, 0, + _("the --megabytes option is deprecated; use -m instead")); + /* fall through */ + case 'm': + human_output_opts = 0; + output_block_size = 1024 * 1024; + break; + + case 'l': + opt_count_all = true; + break; + + case 's': + opt_summarize_only = true; + break; + + case 'x': + bit_flags |= FTS_XDEV; + break; + + case 'B': + human_output_opts = human_options (optarg, true, &output_block_size); + break; + + case 'D': /* This will eventually be 'H' (-H), too. */ + symlink_deref_bits = FTS_COMFOLLOW | FTS_PHYSICAL; + break; + + case 'L': /* --dereference */ + symlink_deref_bits = FTS_LOGICAL; + break; + + case 'P': /* --no-dereference */ + symlink_deref_bits = FTS_PHYSICAL; + break; + + case 'S': + opt_separate_dirs = true; + break; + + case 'X': + if (add_exclude_file (add_exclude, exclude, optarg, + EXCLUDE_WILDCARDS, '\n')) + { + error (0, errno, "%s", quotearg_colon (optarg)); + ok = false; + } + break; + + case FILES0_FROM_OPTION: + files_from = optarg; + break; + + case EXCLUDE_OPTION: + add_exclude (exclude, optarg, EXCLUDE_WILDCARDS); + break; + + case TIME_OPTION: + opt_time = true; + time_type = + (optarg + ? XARGMATCH ("--time", optarg, time_args, time_types) + : time_mtime); + break; + + case TIME_STYLE_OPTION: + time_style = optarg; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + ok = false; + } + } + + if (!ok) + usage (EXIT_FAILURE); + + if (opt_all & opt_summarize_only) + { + error (0, 0, _("cannot both summarize and show all entries")); + usage (EXIT_FAILURE); + } + + if (opt_summarize_only && max_depth_specified && max_depth == 0) + { + error (0, 0, + _("warning: summarizing is the same as using --max-depth=0")); + } + + if (opt_summarize_only && max_depth_specified && max_depth != 0) + { + unsigned long int d = max_depth; + error (0, 0, _("warning: summarizing conflicts with --max-depth=%lu"), d); + usage (EXIT_FAILURE); + } + + if (opt_summarize_only) + max_depth = 0; + + /* Process time style if printing last times. */ + if (opt_time) + { + if (! time_style) + { + time_style = getenv ("TIME_STYLE"); + + /* Ignore TIMESTYLE="locale", for compatibility with ls. */ + if (! time_style || STREQ (time_style, "locale")) + time_style = "long-iso"; + else if (*time_style == '+') + { + /* Ignore anything after a newline, for compatibility + with ls. */ + char *p = strchr (time_style, '\n'); + if (p) + *p = '\0'; + } + else + { + /* Ignore "posix-" prefix, for compatibility with ls. */ + static char const posix_prefix[] = "posix-"; + while (strncmp (time_style, posix_prefix, sizeof posix_prefix - 1) + == 0) + time_style += sizeof posix_prefix - 1; + } + } + + if (*time_style == '+') + time_format = time_style + 1; + else + { + switch (XARGMATCH ("time style", time_style, + time_style_args, time_style_types)) + { + case full_iso_time_style: + time_format = "%Y-%m-%d %H:%M:%S.%N %z"; + break; + + case long_iso_time_style: + time_format = "%Y-%m-%d %H:%M"; + break; + + case iso_time_style: + time_format = "%Y-%m-%d"; + break; + } + } + } + + if (files_from) + { + /* When using --files0-from=F, you may not specify any files + on the command-line. */ + if (optind < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind])); + fprintf (stderr, "%s\n", + _("File operands cannot be combined with --files0-from.")); + usage (EXIT_FAILURE); + } + + if (! (STREQ (files_from, "-") || freopen (files_from, "r", stdin))) + error (EXIT_FAILURE, errno, _("cannot open %s for reading"), + quote (files_from)); + + readtokens0_init (&tok); + + if (! readtokens0 (stdin, &tok) || fclose (stdin) != 0) + error (EXIT_FAILURE, 0, _("cannot read file names from %s"), + quote (files_from)); + + files = tok.tok; + } + else + { + files = (optind < argc ? argv + optind : cwd_only); + } + + /* Initialize the hash structure for inode numbers. */ + hash_init (); + + /* Report and filter out any empty file names before invoking fts. + This works around a glitch in fts, which fails immediately + (without looking at the other file names) when given an empty + file name. */ + { + size_t i = 0; + size_t j; + + for (j = 0; ; j++) + { + if (i != j) + files[i] = files[j]; + + if ( ! files[i]) + break; + + if (files[i][0]) + i++; + else + { + if (files_from) + { + /* Using the standard `filename:line-number:' prefix here is + not totally appropriate, since NUL is the separator, not NL, + but it might be better than nothing. */ + unsigned long int file_number = j + 1; + error (0, 0, "%s:%lu: %s", quotearg_colon (files_from), + file_number, _("invalid zero-length file name")); + } + else + error (0, 0, "%s", _("invalid zero-length file name")); + } + } + + ok = (i == j); + } + + bit_flags |= symlink_deref_bits; + ok &= du_files (files, bit_flags); + + /* This isn't really necessary, but it does ensure we + exercise this function. */ + if (files_from) + readtokens0_free (&tok); + + hash_free (htab); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/echo.c b/src/echo.c new file mode 100644 index 0000000..5f8582c --- /dev/null +++ b/src/echo.c @@ -0,0 +1,276 @@ +/* echo.c, derived from code echo.c in Bash. + Copyright (C) 87,89, 1991-1997, 1999-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include +#include +#include +#include "system.h" +#include "long-options.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "echo" + +#define AUTHORS "FIXME unknown" + +/* echo [-neE] [arg ...] +Output the ARGs. If -n is specified, the trailing newline is +suppressed. If the -e option is given, interpretation of the +following backslash-escaped characters is turned on: + \a alert (bell) + \b backspace + \c suppress trailing newline + \f form feed + \n new line + \r carriage return + \t horizontal tab + \v vertical tab + \\ backslash + \0NNN the character whose ASCII code is NNN (octal). + +You can explicitly turn off the interpretation of the above characters +on System V systems with the -E option. +*/ + +/* If true, interpret backslash escapes by default. */ +#ifndef DEFAULT_ECHO_TO_XPG +enum { DEFAULT_ECHO_TO_XPG = false }; +#endif + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... [STRING]...\n"), program_name); + fputs (_("\ +Echo the STRING(s) to standard output.\n\ +\n\ + -n do not output the trailing newline\n\ +"), stdout); + fputs (_(DEFAULT_ECHO_TO_XPG + ? "\ + -e enable interpretation of backslash escapes (default)\n\ + -E disable interpretation of backslash escapes\n" + : "\ + -e enable interpretation of backslash escapes\n\ + -E disable interpretation of backslash escapes (default)\n"), + stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +If -e is in effect, the following sequences are recognized:\n\ +\n\ + \\0NNN the character whose ASCII code is NNN (octal)\n\ + \\\\ backslash\n\ + \\a alert (BEL)\n\ + \\b backspace\n\ +"), stdout); + fputs (_("\ + \\c suppress trailing newline\n\ + \\f form feed\n\ + \\n new line\n\ + \\r carriage return\n\ + \\t horizontal tab\n\ + \\v vertical tab\n\ +"), stdout); + printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Convert C from hexadecimal character to integer. */ +static int +hextobin (unsigned char c) +{ + switch (c) + { + default: return c - '0'; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + } +} + +/* Print the words in LIST to standard output. If the first word is + `-n', then don't print a trailing newline. We also support the + echo syntax from Version 9 unix systems. */ + +int +main (int argc, char **argv) +{ + bool display_return = true; + bool allow_options = + (! getenv ("POSIXLY_CORRECT") + || (! DEFAULT_ECHO_TO_XPG && 1 < argc && STREQ (argv[1], "-n"))); + + /* System V machines already have a /bin/sh with a v9 behavior. + Use the identical behavior for these machines so that the + existing system shell scripts won't barf. */ + bool do_v9 = DEFAULT_ECHO_TO_XPG; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + if (allow_options) + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + + --argc; + ++argv; + + if (allow_options) + while (argc > 0 && *argv[0] == '-') + { + char const *temp = argv[0] + 1; + size_t i; + + /* If it appears that we are handling options, then make sure that + all of the options specified are actually valid. Otherwise, the + string should just be echoed. */ + + for (i = 0; temp[i]; i++) + switch (temp[i]) + { + case 'e': case 'E': case 'n': + break; + default: + goto just_echo; + } + + if (i == 0) + goto just_echo; + + /* All of the options in TEMP are valid options to ECHO. + Handle them. */ + while (*temp) + switch (*temp++) + { + case 'e': + do_v9 = true; + break; + + case 'E': + do_v9 = false; + break; + + case 'n': + display_return = false; + break; + } + + argc--; + argv++; + } + +just_echo: + + if (do_v9) + { + while (argc > 0) + { + char const *s = argv[0]; + unsigned char c; + + while ((c = *s++)) + { + if (c == '\\' && *s) + { + switch (c = *s++) + { + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'c': exit (EXIT_SUCCESS); + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'x': + { + unsigned char ch = *s; + if (! isxdigit (ch)) + goto not_an_escape; + s++; + c = hextobin (ch); + ch = *s; + if (isxdigit (ch)) + { + s++; + c = c * 16 + hextobin (ch); + } + } + break; + case '0': + c = 0; + if (! ('0' <= *s && *s <= '7')) + break; + c = *s++; + /* Fall through. */ + case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c -= '0'; + if ('0' <= *s && *s <= '7') + c = c * 8 + (*s++ - '0'); + if ('0' <= *s && *s <= '7') + c = c * 8 + (*s++ - '0'); + break; + case '\\': break; + + not_an_escape: + default: putchar ('\\'); break; + } + } + putchar (c); + } + argc--; + argv++; + if (argc > 0) + putchar (' '); + } + } + else + { + while (argc > 0) + { + fputs (argv[0], stdout); + argc--; + argv++; + if (argc > 0) + putchar (' '); + } + } + + if (display_return) + putchar ('\n'); + exit (EXIT_SUCCESS); +} diff --git a/src/env.c b/src/env.c new file mode 100644 index 0000000..d95ebfd --- /dev/null +++ b/src/env.c @@ -0,0 +1,205 @@ +/* env - run a program in a modified environment + Copyright (C) 1986, 1991-2005, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Richard Mlynarik and David MacKenzie */ + +/* Options: + - + -i + --ignore-environment + Construct a new environment from scratch; normally the + environment is inherited from the parent process, except as + modified by other options. + + -u variable + --unset=variable + Unset variable VARIABLE (remove it from the environment). + If VARIABLE was not set, does nothing. + + variable=value (an arg containing a "=" character) + Set the environment variable VARIABLE to value VALUE. VALUE + may be of zero length ("variable="). Setting a variable to a + zero-length value is different from unsetting it. + + -- + Indicate that the following argument is the program + to invoke. This is necessary when the program's name + begins with "-" or contains a "=". + + The first remaining argument specifies a program to invoke; + it is searched for according to the specification of the PATH + environment variable. Any arguments following that are + passed as arguments to that program. + + If no command name is specified following the environment + specifications, the resulting environment is printed. + This is like specifying a command name of "printenv". + + Examples: + + If the environment passed to "env" is + { LOGNAME=rms EDITOR=emacs PATH=.:/gnubin:/hacks } + + env - foo + runs "foo" in a null environment. + + env foo + runs "foo" in the environment + { LOGNAME=rms EDITOR=emacs PATH=.:/gnubin:/hacks } + + env DISPLAY=gnu:0 nemacs + runs "nemacs" in the environment + { LOGNAME=rms EDITOR=emacs PATH=.:/gnubin:/hacks DISPLAY=gnu:0 } + + env - LOGNAME=foo /hacks/hack bar baz + runs the "hack" program on arguments "bar" and "baz" in an + environment in which the only variable is "LOGNAME". Note that + the "-" option clears out the PATH variable, so one should be + careful to specify in which directory to find the program to + call. + + env -u EDITOR LOGNAME=foo PATH=/energy -- e=mc2 bar baz + runs the program "/energy/e=mc2" with environment + { LOGNAME=foo PATH=/energy } +*/ + +#include +#include +#include +#include +#include + +#include "system.h" +#include "error.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "env" + +#define AUTHORS "Richard Mlynarik", "David MacKenzie" + +int putenv (); + +extern char **environ; + +/* The name by which this program was run. */ +char *program_name; + +static struct option const longopts[] = +{ + {"ignore-environment", no_argument, NULL, 'i'}, + {"unset", required_argument, NULL, 'u'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n"), + program_name); + fputs (_("\ +Set each NAME to VALUE in the environment and run COMMAND.\n\ +\n\ + -i, --ignore-environment start with an empty environment\n\ + -u, --unset=NAME remove variable from the environment\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +A mere - implies -i. If no COMMAND, print the resulting environment.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int optc; + bool ignore_environment = false; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (EXIT_FAIL); + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "+iu:", longopts, NULL)) != -1) + { + switch (optc) + { + case 'i': + ignore_environment = true; + break; + case 'u': + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAIL); + } + } + + if (optind < argc && STREQ (argv[optind], "-")) + ignore_environment = true; + + if (ignore_environment) + { + static char *dummy_environ[] = { NULL }; + environ = dummy_environ; + } + + optind = 0; /* Force GNU getopt to re-initialize. */ + while ((optc = getopt_long (argc, argv, "+iu:", longopts, NULL)) != -1) + if (optc == 'u') + putenv (optarg); /* Requires GNU putenv. */ + + if (optind < argc && STREQ (argv[optind], "-")) + ++optind; + + while (optind < argc && strchr (argv[optind], '=')) + putenv (argv[optind++]); + + /* If no program is specified, print the environment and exit. */ + if (argc <= optind) + { + char *const *e = environ; + while (*e) + puts (*e++); + exit (EXIT_SUCCESS); + } + + execvp (argv[optind], &argv[optind]); + + { + int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE); + error (0, errno, "%s", argv[optind]); + exit (exit_status); + } +} diff --git a/src/expand.c b/src/expand.c new file mode 100644 index 0000000..5645203 --- /dev/null +++ b/src/expand.c @@ -0,0 +1,438 @@ +/* expand - convert tabs to spaces + Copyright (C) 89, 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* By default, convert all tabs to spaces. + Preserves backspace characters in the output; they decrement the + column count for tab calculations. + The default action is equivalent to -8. + + Options: + --tabs=tab1[,tab2[,...]] + -t tab1[,tab2[,...]] + -tab1[,tab2[,...]] If only one tab stop is given, set the tabs tab1 + columns apart instead of the default 8. Otherwise, + set the tabs at columns tab1, tab2, etc. (numbered from + 0); replace any tabs beyond the tab stops given with + single spaces. + --initial + -i Only convert initial tabs on each line to spaces. + + David MacKenzie */ + +#include + +#include +#include +#include +#include "system.h" +#include "error.h" +#include "quote.h" +#include "xstrndup.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "expand" + +#define AUTHORS "David MacKenzie" + +/* The number of bytes added at a time to the amount of memory + allocated for the output line. */ +#define OUTPUT_BLOCK 256 + +/* The name this program was run with. */ +char *program_name; + +/* If true, convert blanks even after nonblank characters have been + read on the line. */ +static bool convert_entire_line; + +/* If nonzero, the size of all tab stops. If zero, use `tab_list' instead. */ +static uintmax_t tab_size; + +/* Array of the explicit column numbers of the tab stops; + after `tab_list' is exhausted, each additional tab is replaced + by a space. The first column is column 0. */ +static uintmax_t *tab_list; + +/* The number of allocated entries in `tab_list'. */ +static size_t n_tabs_allocated; + +/* The index of the first invalid element of `tab_list', + where the next element can be added. */ +static size_t first_free_tab; + +/* Null-terminated array of input filenames. */ +static char **file_list; + +/* Default for `file_list' if no files are given on the command line. */ +static char *stdin_argv[] = +{ + "-", NULL +}; + +/* True if we have ever read standard input. */ +static bool have_read_stdin; + +/* The desired exit status. */ +static int exit_status; + +static char const shortopts[] = "it:0::1::2::3::4::5::6::7::8::9::"; + +static struct option const longopts[] = +{ + {"tabs", required_argument, NULL, 't'}, + {"initial", no_argument, NULL, 'i'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + fputs (_("\ +Convert tabs in each FILE to spaces, writing to standard output.\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -i, --initial do not convert tabs after non blanks\n\ + -t, --tabs=NUMBER have tabs NUMBER characters apart, not 8\n\ +"), stdout); + fputs (_("\ + -t, --tabs=LIST use comma separated list of explicit tab positions\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Add tab stop TABVAL to the end of `tab_list'. */ + +static void +add_tab_stop (uintmax_t tabval) +{ + if (first_free_tab == n_tabs_allocated) + tab_list = X2NREALLOC (tab_list, &n_tabs_allocated); + tab_list[first_free_tab++] = tabval; +} + +/* Add the comma or blank separated list of tab stops STOPS + to the list of tab stops. */ + +static void +parse_tab_stops (char const *stops) +{ + bool have_tabval = false; + uintmax_t tabval IF_LINT (= 0); + char const *num_start IF_LINT (= NULL); + bool ok = true; + + for (; *stops; stops++) + { + if (*stops == ',' || isblank (to_uchar (*stops))) + { + if (have_tabval) + add_tab_stop (tabval); + have_tabval = false; + } + else if (ISDIGIT (*stops)) + { + if (!have_tabval) + { + tabval = 0; + have_tabval = true; + num_start = stops; + } + + /* Detect overflow. */ + if (!DECIMAL_DIGIT_ACCUMULATE (tabval, *stops - '0', uintmax_t)) + { + size_t len = strspn (num_start, "0123456789"); + char *bad_num = xstrndup (num_start, len); + error (0, 0, _("tab stop is too large %s"), quote (bad_num)); + free (bad_num); + ok = false; + stops = num_start + len - 1; + } + } + else + { + error (0, 0, _("tab size contains invalid character(s): %s"), + quote (stops)); + ok = false; + break; + } + } + + if (!ok) + exit (EXIT_FAILURE); + + if (have_tabval) + add_tab_stop (tabval); +} + +/* Check that the list of tab stops TABS, with ENTRIES entries, + contains only nonzero, ascending values. */ + +static void +validate_tab_stops (uintmax_t const *tabs, size_t entries) +{ + uintmax_t prev_tab = 0; + size_t i; + + for (i = 0; i < entries; i++) + { + if (tabs[i] == 0) + error (EXIT_FAILURE, 0, _("tab size cannot be 0")); + if (tabs[i] <= prev_tab) + error (EXIT_FAILURE, 0, _("tab sizes must be ascending")); + prev_tab = tabs[i]; + } +} + +/* Close the old stream pointer FP if it is non-NULL, + and return a new one opened to read the next input file. + Open a filename of `-' as the standard input. + Return NULL if there are no more input files. */ + +static FILE * +next_file (FILE *fp) +{ + static char *prev_file; + char *file; + + if (fp) + { + if (ferror (fp)) + { + error (0, errno, "%s", prev_file); + exit_status = EXIT_FAILURE; + } + if (STREQ (prev_file, "-")) + clearerr (fp); /* Also clear EOF. */ + else if (fclose (fp) != 0) + { + error (0, errno, "%s", prev_file); + exit_status = EXIT_FAILURE; + } + } + + while ((file = *file_list++) != NULL) + { + if (STREQ (file, "-")) + { + have_read_stdin = true; + prev_file = file; + return stdin; + } + fp = fopen (file, "r"); + if (fp) + { + prev_file = file; + return fp; + } + error (0, errno, "%s", file); + exit_status = EXIT_FAILURE; + } + return NULL; +} + +/* Change tabs to spaces, writing to stdout. + Read each file in `file_list', in order. */ + +static void +expand (void) +{ + /* Input stream. */ + FILE *fp = next_file (NULL); + + if (!fp) + return; + + for (;;) + { + /* Input character, or EOF. */ + int c; + + /* If true, perform translations. */ + bool convert = true; + + + /* The following variables have valid values only when CONVERT + is true: */ + + /* Column of next input character. */ + uintmax_t column = 0; + + /* Index in TAB_LIST of next tab stop to examine. */ + size_t tab_index = 0; + + + /* Convert a line of text. */ + + do + { + while ((c = getc (fp)) < 0 && (fp = next_file (fp))) + continue; + + if (convert) + { + if (c == '\t') + { + /* Column the next input tab stop is on. */ + uintmax_t next_tab_column; + + if (tab_size) + next_tab_column = column + (tab_size - column % tab_size); + else + for (;;) + if (tab_index == first_free_tab) + { + next_tab_column = column + 1; + break; + } + else + { + uintmax_t tab = tab_list[tab_index++]; + if (column < tab) + { + next_tab_column = tab; + break; + } + } + + if (next_tab_column < column) + error (EXIT_FAILURE, 0, _("input line is too long")); + + while (++column < next_tab_column) + if (putchar (' ') < 0) + error (EXIT_FAILURE, errno, _("write error")); + + c = ' '; + } + else if (c == '\b') + { + /* Go back one column, and force recalculation of the + next tab stop. */ + column -= !!column; + tab_index -= !!tab_index; + } + else + { + column++; + if (!column) + error (EXIT_FAILURE, 0, _("input line is too long")); + } + + convert &= convert_entire_line | !! isblank (c); + } + + if (c < 0) + return; + + if (putchar (c) < 0) + error (EXIT_FAILURE, errno, _("write error")); + } + while (c != '\n'); + } +} + +int +main (int argc, char **argv) +{ + int c; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + have_read_stdin = false; + exit_status = EXIT_SUCCESS; + convert_entire_line = true; + tab_list = NULL; + first_free_tab = 0; + + while ((c = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1) + { + switch (c) + { + case 'i': + convert_entire_line = false; + break; + + case 't': + parse_tab_stops (optarg); + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (optarg) + parse_tab_stops (optarg - 1); + else + { + char tab_stop[2]; + tab_stop[0] = c; + tab_stop[1] = '\0'; + parse_tab_stops (tab_stop); + } + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + validate_tab_stops (tab_list, first_free_tab); + + if (first_free_tab == 0) + tab_size = 8; + else if (first_free_tab == 1) + tab_size = tab_list[0]; + else + tab_size = 0; + + file_list = (optind < argc ? &argv[optind] : stdin_argv); + + expand (); + + if (have_read_stdin && fclose (stdin) != 0) + error (EXIT_FAILURE, errno, "-"); + + exit (exit_status); +} diff --git a/src/expr.c b/src/expr.c new file mode 100644 index 0000000..352c80c --- /dev/null +++ b/src/expr.c @@ -0,0 +1,873 @@ +/* expr -- evaluate expressions. + Copyright (C) 86, 1991-1997, 1999-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Author: Mike Parker. + + This program evaluates expressions. Each token (operator, operand, + parenthesis) of the expression must be a seperate argument. The + parser used is a reasonably general one, though any incarnation of + it is language-specific. It is especially nice for expressions. + + No parse tree is needed; a new node is evaluated immediately. + One function can handle multiple operators all of equal precedence, + provided they all associate ((x op x) op x). + + Define EVAL_TRACE to print an evaluation trace. */ + +#include +#include +#include +#include "system.h" + +#include +#include "long-options.h" +#include "error.h" +#include "inttostr.h" +#include "quote.h" +#include "quotearg.h" +#include "strnumcmp.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "expr" + +#define AUTHORS "Mike Parker" + +/* Exit statuses. */ +enum + { + /* Invalid expression: e.g., its form does not conform to the + grammar for expressions. Our grammar is an extension of the + POSIX grammar. */ + EXPR_INVALID = 2, + + /* An internal error occurred, e.g., arithmetic overflow, storage + exhaustion. */ + EXPR_FAILURE + }; + +/* The kinds of value we can have. */ +enum valtype +{ + integer, + string +}; +typedef enum valtype TYPE; + +/* A value is.... */ +struct valinfo +{ + TYPE type; /* Which kind. */ + union + { /* The value itself. */ + intmax_t i; + char *s; + } u; +}; +typedef struct valinfo VALUE; + +/* The arguments given to the program, minus the program name. */ +static char **args; + +/* The name this program was run with. */ +char *program_name; + +static VALUE *eval (bool); +static bool nomoreargs (void); +static bool null (VALUE *v); +static void printv (VALUE *v); + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s EXPRESSION\n\ + or: %s OPTION\n\ +"), + program_name, program_name); + putchar ('\n'); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Print the value of EXPRESSION to standard output. A blank line below\n\ +separates increasing precedence groups. EXPRESSION may be:\n\ +\n\ + ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2\n\ +\n\ + ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0\n\ +"), stdout); + fputs (_("\ +\n\ + ARG1 < ARG2 ARG1 is less than ARG2\n\ + ARG1 <= ARG2 ARG1 is less than or equal to ARG2\n\ + ARG1 = ARG2 ARG1 is equal to ARG2\n\ + ARG1 != ARG2 ARG1 is unequal to ARG2\n\ + ARG1 >= ARG2 ARG1 is greater than or equal to ARG2\n\ + ARG1 > ARG2 ARG1 is greater than ARG2\n\ +"), stdout); + fputs (_("\ +\n\ + ARG1 + ARG2 arithmetic sum of ARG1 and ARG2\n\ + ARG1 - ARG2 arithmetic difference of ARG1 and ARG2\n\ +"), stdout); + fputs (_("\ +\n\ + ARG1 * ARG2 arithmetic product of ARG1 and ARG2\n\ + ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2\n\ + ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2\n\ +"), stdout); + fputs (_("\ +\n\ + STRING : REGEXP anchored pattern match of REGEXP in STRING\n\ +\n\ + match STRING REGEXP same as STRING : REGEXP\n\ + substr STRING POS LENGTH substring of STRING, POS counted from 1\n\ + index STRING CHARS index in STRING where any CHARS is found, or 0\n\ + length STRING length of STRING\n\ +"), stdout); + fputs (_("\ + + TOKEN interpret TOKEN as a string, even if it is a\n\ + keyword like `match' or an operator like `/'\n\ +\n\ + ( EXPRESSION ) value of EXPRESSION\n\ +"), stdout); + fputs (_("\ +\n\ +Beware that many operators need to be escaped or quoted for shells.\n\ +Comparisons are arithmetic if both ARGs are numbers, else lexicographical.\n\ +Pattern matches return the string matched between \\( and \\) or null; if\n\ +\\( and \\) are not used, they return the number of characters matched or 0.\n\ +"), stdout); + fputs (_("\ +\n\ +Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null\n\ +or 0, 2 if EXPRESSION is syntactically invalid, and 3 if an error occurred.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Report a syntax error and exit. */ +static void +syntax_error (void) +{ + error (EXPR_INVALID, 0, _("syntax error")); +} + +/* Report an integer overflow for operation OP and exit. */ +static void +integer_overflow (char op) +{ + error (EXPR_FAILURE, ERANGE, "%c", op); +} + +int +main (int argc, char **argv) +{ + VALUE *v; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (EXPR_FAILURE); + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + /* The above handles --help and --version. + Since there is no other invocation of getopt, handle `--' here. */ + if (argc > 1 && STREQ (argv[1], "--")) + { + --argc; + ++argv; + } + + if (argc <= 1) + { + error (0, 0, _("missing operand")); + usage (EXPR_INVALID); + } + + args = argv + 1; + + v = eval (true); + if (!nomoreargs ()) + syntax_error (); + printv (v); + + exit (null (v)); +} + +/* Return a VALUE for I. */ + +static VALUE * +int_value (intmax_t i) +{ + VALUE *v = xmalloc (sizeof *v); + v->type = integer; + v->u.i = i; + return v; +} + +/* Return a VALUE for S. */ + +static VALUE * +str_value (char const *s) +{ + VALUE *v = xmalloc (sizeof *v); + v->type = string; + v->u.s = xstrdup (s); + return v; +} + +/* Free VALUE V, including structure components. */ + +static void +freev (VALUE *v) +{ + if (v->type == string) + free (v->u.s); + free (v); +} + +/* Print VALUE V. */ + +static void +printv (VALUE *v) +{ + char *p; + char buf[INT_BUFSIZE_BOUND (intmax_t)]; + + switch (v->type) + { + case integer: + p = imaxtostr (v->u.i, buf); + break; + case string: + p = v->u.s; + break; + default: + abort (); + } + + puts (p); +} + +/* Return true if V is a null-string or zero-number. */ + +static bool +null (VALUE *v) +{ + switch (v->type) + { + case integer: + return v->u.i == 0; + case string: + { + char const *cp = v->u.s; + if (*cp == '\0') + return true; + + cp += (*cp == '-'); + + do + { + if (*cp != '0') + return false; + } + while (*++cp); + + return true; + } + default: + abort (); + } +} + +/* Return true if CP takes the form of an integer. */ + +static bool +looks_like_integer (char const *cp) +{ + cp += (*cp == '-'); + + do + if (! ISDIGIT (*cp)) + return false; + while (*++cp); + + return true; +} + +/* Coerce V to a string value (can't fail). */ + +static void +tostring (VALUE *v) +{ + char buf[INT_BUFSIZE_BOUND (intmax_t)]; + + switch (v->type) + { + case integer: + v->u.s = xstrdup (imaxtostr (v->u.i, buf)); + v->type = string; + break; + case string: + break; + default: + abort (); + } +} + +/* Coerce V to an integer value. Return true on success, false on failure. */ + +static bool +toarith (VALUE *v) +{ + switch (v->type) + { + case integer: + return true; + case string: + { + intmax_t value; + + if (! looks_like_integer (v->u.s)) + return false; + if (xstrtoimax (v->u.s, NULL, 10, &value, NULL) != LONGINT_OK) + error (EXPR_FAILURE, ERANGE, "%s", v->u.s); + free (v->u.s); + v->u.i = value; + v->type = integer; + return true; + } + default: + abort (); + } +} + +/* Return true and advance if the next token matches STR exactly. + STR must not be NULL. */ + +static bool +nextarg (char const *str) +{ + if (*args == NULL) + return false; + else + { + bool r = STREQ (*args, str); + args += r; + return r; + } +} + +/* Return true if there no more tokens. */ + +static bool +nomoreargs (void) +{ + return *args == 0; +} + +#ifdef EVAL_TRACE +/* Print evaluation trace and args remaining. */ + +static void +trace (fxn) + char *fxn; +{ + char **a; + + printf ("%s:", fxn); + for (a = args; *a; a++) + printf (" %s", *a); + putchar ('\n'); +} +#endif + +/* Do the : operator. + SV is the VALUE for the lhs (the string), + PV is the VALUE for the rhs (the pattern). */ + +static VALUE * +docolon (VALUE *sv, VALUE *pv) +{ + VALUE *v IF_LINT (= NULL); + const char *errmsg; + struct re_pattern_buffer re_buffer; + char fastmap[UCHAR_MAX + 1]; + struct re_registers re_regs; + regoff_t matchlen; + + tostring (sv); + tostring (pv); + + re_regs.num_regs = 0; + re_regs.start = NULL; + re_regs.end = NULL; + + re_buffer.buffer = NULL; + re_buffer.allocated = 0; + re_buffer.fastmap = fastmap; + re_buffer.translate = NULL; + re_syntax_options = + RE_SYNTAX_POSIX_BASIC & ~RE_CONTEXT_INVALID_DUP & ~RE_NO_EMPTY_RANGES; + errmsg = re_compile_pattern (pv->u.s, strlen (pv->u.s), &re_buffer); + if (errmsg) + error (EXPR_INVALID, 0, "%s", errmsg); + re_buffer.newline_anchor = 0; + + matchlen = re_match (&re_buffer, sv->u.s, strlen (sv->u.s), 0, &re_regs); + if (0 <= matchlen) + { + /* Were \(...\) used? */ + if (re_buffer.re_nsub > 0) + { + sv->u.s[re_regs.end[1]] = '\0'; + v = str_value (sv->u.s + re_regs.start[1]); + } + else + v = int_value (matchlen); + } + else if (matchlen == -1) + { + /* Match failed -- return the right kind of null. */ + if (re_buffer.re_nsub > 0) + v = str_value (""); + else + v = int_value (0); + } + else + error (EXPR_FAILURE, + (matchlen == -2 ? errno : EOVERFLOW), + _("error in regular expression matcher")); + + if (0 < re_regs.num_regs) + { + free (re_regs.start); + free (re_regs.end); + } + re_buffer.fastmap = NULL; + regfree (&re_buffer); + return v; +} + +/* Handle bare operands and ( expr ) syntax. */ + +static VALUE * +eval7 (bool evaluate) +{ + VALUE *v; + +#ifdef EVAL_TRACE + trace ("eval7"); +#endif + if (nomoreargs ()) + syntax_error (); + + if (nextarg ("(")) + { + v = eval (evaluate); + if (!nextarg (")")) + syntax_error (); + return v; + } + + if (nextarg (")")) + syntax_error (); + + return str_value (*args++); +} + +/* Handle match, substr, index, and length keywords, and quoting "+". */ + +static VALUE * +eval6 (bool evaluate) +{ + VALUE *l; + VALUE *r; + VALUE *v; + VALUE *i1; + VALUE *i2; + +#ifdef EVAL_TRACE + trace ("eval6"); +#endif + if (nextarg ("+")) + { + if (nomoreargs ()) + syntax_error (); + return str_value (*args++); + } + else if (nextarg ("length")) + { + r = eval6 (evaluate); + tostring (r); + v = int_value (strlen (r->u.s)); + freev (r); + return v; + } + else if (nextarg ("match")) + { + l = eval6 (evaluate); + r = eval6 (evaluate); + if (evaluate) + { + v = docolon (l, r); + freev (l); + } + else + v = l; + freev (r); + return v; + } + else if (nextarg ("index")) + { + l = eval6 (evaluate); + r = eval6 (evaluate); + tostring (l); + tostring (r); + v = int_value (strcspn (l->u.s, r->u.s) + 1); + if (v->u.i == strlen (l->u.s) + 1) + v->u.i = 0; + freev (l); + freev (r); + return v; + } + else if (nextarg ("substr")) + { + size_t llen; + l = eval6 (evaluate); + i1 = eval6 (evaluate); + i2 = eval6 (evaluate); + tostring (l); + llen = strlen (l->u.s); + if (!toarith (i1) || !toarith (i2) + || llen < i1->u.i + || i1->u.i <= 0 || i2->u.i <= 0) + v = str_value (""); + else + { + size_t vlen = MIN (i2->u.i, llen - i1->u.i + 1); + char *vlim; + v = xmalloc (sizeof *v); + v->type = string; + v->u.s = xmalloc (vlen + 1); + vlim = mempcpy (v->u.s, l->u.s + i1->u.i - 1, vlen); + *vlim = '\0'; + } + freev (l); + freev (i1); + freev (i2); + return v; + } + else + return eval7 (evaluate); +} + +/* Handle : operator (pattern matching). + Calls docolon to do the real work. */ + +static VALUE * +eval5 (bool evaluate) +{ + VALUE *l; + VALUE *r; + VALUE *v; + +#ifdef EVAL_TRACE + trace ("eval5"); +#endif + l = eval6 (evaluate); + while (1) + { + if (nextarg (":")) + { + r = eval6 (evaluate); + if (evaluate) + { + v = docolon (l, r); + freev (l); + l = v; + } + freev (r); + } + else + return l; + } +} + +/* Handle *, /, % operators. */ + +static VALUE * +eval4 (bool evaluate) +{ + VALUE *l; + VALUE *r; + enum { multiply, divide, mod } fxn; + intmax_t val = 0; + +#ifdef EVAL_TRACE + trace ("eval4"); +#endif + l = eval5 (evaluate); + while (1) + { + if (nextarg ("*")) + fxn = multiply; + else if (nextarg ("/")) + fxn = divide; + else if (nextarg ("%")) + fxn = mod; + else + return l; + r = eval5 (evaluate); + if (evaluate) + { + if (!toarith (l) || !toarith (r)) + error (EXPR_INVALID, 0, _("non-numeric argument")); + if (fxn == multiply) + { + val = l->u.i * r->u.i; + if (! (l->u.i == 0 || r->u.i == 0 + || ((val < 0) == ((l->u.i < 0) ^ (r->u.i < 0)) + && val / l->u.i == r->u.i))) + integer_overflow ('*'); + } + else + { + if (r->u.i == 0) + error (EXPR_INVALID, 0, _("division by zero")); + if (l->u.i < - INTMAX_MAX && r->u.i == -1) + { + /* Some x86-style hosts raise an exception for + INT_MIN / -1 and INT_MIN % -1, so handle these + problematic cases specially. */ + if (fxn == divide) + integer_overflow ('/'); + val = 0; + } + else + val = fxn == divide ? l->u.i / r->u.i : l->u.i % r->u.i; + } + } + freev (l); + freev (r); + l = int_value (val); + } +} + +/* Handle +, - operators. */ + +static VALUE * +eval3 (bool evaluate) +{ + VALUE *l; + VALUE *r; + enum { plus, minus } fxn; + intmax_t val = 0; + +#ifdef EVAL_TRACE + trace ("eval3"); +#endif + l = eval4 (evaluate); + while (1) + { + if (nextarg ("+")) + fxn = plus; + else if (nextarg ("-")) + fxn = minus; + else + return l; + r = eval4 (evaluate); + if (evaluate) + { + if (!toarith (l) || !toarith (r)) + error (EXPR_INVALID, 0, _("non-numeric argument")); + if (fxn == plus) + { + val = l->u.i + r->u.i; + if ((val < l->u.i) != (r->u.i < 0)) + integer_overflow ('+'); + } + else + { + val = l->u.i - r->u.i; + if ((l->u.i < val) != (r->u.i < 0)) + integer_overflow ('-'); + } + } + freev (l); + freev (r); + l = int_value (val); + } +} + +/* Handle comparisons. */ + +static VALUE * +eval2 (bool evaluate) +{ + VALUE *l; + +#ifdef EVAL_TRACE + trace ("eval2"); +#endif + l = eval3 (evaluate); + while (1) + { + VALUE *r; + enum + { + less_than, less_equal, equal, not_equal, greater_equal, greater_than + } fxn; + bool val = false; + + if (nextarg ("<")) + fxn = less_than; + else if (nextarg ("<=")) + fxn = less_equal; + else if (nextarg ("=") || nextarg ("==")) + fxn = equal; + else if (nextarg ("!=")) + fxn = not_equal; + else if (nextarg (">=")) + fxn = greater_equal; + else if (nextarg (">")) + fxn = greater_than; + else + return l; + r = eval3 (evaluate); + + if (evaluate) + { + int cmp; + tostring (l); + tostring (r); + + if (looks_like_integer (l->u.s) && looks_like_integer (r->u.s)) + cmp = strintcmp (l->u.s, r->u.s); + else + { + errno = 0; + cmp = strcoll (l->u.s, r->u.s); + + if (errno) + { + error (0, errno, _("string comparison failed")); + error (0, 0, _("Set LC_ALL='C' to work around the problem.")); + error (EXPR_INVALID, 0, + _("The strings compared were %s and %s."), + quotearg_n_style (0, locale_quoting_style, l->u.s), + quotearg_n_style (1, locale_quoting_style, r->u.s)); + } + } + + switch (fxn) + { + case less_than: val = (cmp < 0); break; + case less_equal: val = (cmp <= 0); break; + case equal: val = (cmp == 0); break; + case not_equal: val = (cmp != 0); break; + case greater_equal: val = (cmp >= 0); break; + case greater_than: val = (cmp > 0); break; + default: abort (); + } + } + + freev (l); + freev (r); + l = int_value (val); + } +} + +/* Handle &. */ + +static VALUE * +eval1 (bool evaluate) +{ + VALUE *l; + VALUE *r; + +#ifdef EVAL_TRACE + trace ("eval1"); +#endif + l = eval2 (evaluate); + while (1) + { + if (nextarg ("&")) + { + r = eval2 (evaluate & ~ null (l)); + if (null (l) || null (r)) + { + freev (l); + freev (r); + l = int_value (0); + } + else + freev (r); + } + else + return l; + } +} + +/* Handle |. */ + +static VALUE * +eval (bool evaluate) +{ + VALUE *l; + VALUE *r; + +#ifdef EVAL_TRACE + trace ("eval"); +#endif + l = eval1 (evaluate); + while (1) + { + if (nextarg ("|")) + { + r = eval1 (evaluate & null (l)); + if (null (l)) + { + freev (l); + l = r; + if (null (l)) + { + freev (l); + l = int_value (0); + } + } + else + freev (r); + } + else + return l; + } +} diff --git a/src/extract-magic b/src/extract-magic new file mode 100644 index 0000000..2e9e871 --- /dev/null +++ b/src/extract-magic @@ -0,0 +1,135 @@ +#!/usr/bin/perl -w +# Derive #define directives from specially formatted `case ...:' statements. + +# Copyright (C) 2003, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +use strict; + +use Getopt::Long; + +(my $VERSION = '$Revision: 1.5 $ ') =~ tr/[0-9].//cd; +(my $ME = $0) =~ s|.*/||; + +END +{ + # Nobody ever checks the status of print()s. That's okay, because + # if any do fail, we're guaranteed to get an indicator when we close() + # the filehandle. + # + # Close stdout now, and if there were no errors, return happy status. + # If stdout has already been closed by the script, though, do nothing. + defined fileno STDOUT + or return; + close STDOUT + and return; + + # Errors closing stdout. Indicate that, and hope stderr is OK. + warn "$ME: closing standard output: $!\n"; + + # Don't be so arrogant as to assume that we're the first END handler + # defined, and thus the last one invoked. There may be others yet + # to come. $? will be passed on to them, and to the final _exit(). + # + # If it isn't already an error, make it one (and if it _is_ an error, + # preserve the value: it might be important). + $? ||= 1; +} + +sub usage ($) +{ + my ($exit_code) = @_; + my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR); + if ($exit_code != 0) + { + print $STREAM "Try `$ME --help' for more information.\n"; + } + else + { + print $STREAM < sub { usage 0 }, + version => sub { print "$ME version $VERSION\n"; exit }, + ) or usage 1; + + my $fail = 0; + + @ARGV < 1 + and (warn "$ME: missing FILE arguments\n"), $fail = 1; + 1 < @ARGV + and (warn "$ME: too many arguments\n"), $fail = 1; + $fail + and usage 1; + + my $file = $ARGV[0]; + + open FH, $file + or die "$ME: can't open `$file' for reading: $!\n"; + + # For each line like this: + # case S_MAGIC_ROMFS: /* 0x7275 */ + # emit one like this: + # # define S_MAGIC_ROMFS 0x7275 + # Fail if there is a `case S_MAGIC_.*' line without + # a properly formed comment. + + print <)) + { + $line =~ /^[ \t]+case S_MAGIC_/ + or next; + $line =~ m!^[ \t]+case (S_MAGIC_\w+): /\* (0x[0-9A-Fa-f]+) \*/$! + or (warn "$ME:$file:$.: malformed case S_MAGIC_... line"), + $fail = 1, next; + my $name = $1; + my $value = $2; + print "# define $name $value\n"; + } + + print <<\EOF; +#elif defined __GNU__ +# include +#endif +EOF + close FH; + + exit $fail; +} diff --git a/src/factor.c b/src/factor.c new file mode 100644 index 0000000..dc8f1cc --- /dev/null +++ b/src/factor.c @@ -0,0 +1,220 @@ +/* factor -- print prime factors of n. + Copyright (C) 86, 1995-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Paul Rubin . + Adapted for GNU, fixed to factor UINT_MAX by Jim Meyering. */ + +#include +#include +#include +#include + +#include +#define NDEBUG 1 + +#include "system.h" +#include "error.h" +#include "inttostr.h" +#include "long-options.h" +#include "quote.h" +#include "readtokens.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "factor" + +#define AUTHORS "Paul Rubin" + +/* Token delimiters when reading from a file. */ +#define DELIM "\n\t " + +/* The maximum number of factors, including -1, for negative numbers. */ +#define MAX_N_FACTORS (sizeof (uintmax_t) * CHAR_BIT) + +/* The trial divisor increment wheel. Use it to skip over divisors that + are composites of 2, 3, 5, 7, or 11. The part from WHEEL_START up to + WHEEL_END is reused periodically, while the "lead in" is used to test + for those primes and to jump onto the wheel. For more information, see + http://www.utm.edu/research/primes/glossary/WheelFactorization.html */ + +#include "wheel-size.h" /* For the definition of WHEEL_SIZE. */ +static const unsigned char wheel_tab[] = + { +#include "wheel.h" + }; + +#define WHEEL_START (wheel_tab + WHEEL_SIZE) +#define WHEEL_END (wheel_tab + (sizeof wheel_tab / sizeof wheel_tab[0])) + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [NUMBER]...\n\ + or: %s OPTION\n\ +"), + program_name, program_name); + fputs (_("\ +Print the prime factors of each NUMBER.\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Print the prime factors of all specified integer NUMBERs. If no arguments\n\ +are specified on the command line, they are read from standard input.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* FIXME: comment */ + +static size_t +factor (uintmax_t n0, size_t max_n_factors, uintmax_t *factors) +{ + uintmax_t n = n0, d, q; + size_t n_factors = 0; + unsigned char const *w = wheel_tab; + + if (n <= 1) + return n_factors; + + /* The exit condition in the following loop is correct because + any time it is tested one of these 3 conditions holds: + (1) d divides n + (2) n is prime + (3) n is composite but has no factors less than d. + If (1) or (2) obviously the right thing happens. + If (3), then since n is composite it is >= d^2. */ + + d = 2; + do + { + q = n / d; + while (n == q * d) + { + assert (n_factors < max_n_factors); + factors[n_factors++] = d; + n = q; + q = n / d; + } + d += *(w++); + if (w == WHEEL_END) + w = WHEEL_START; + } + while (d <= q); + + if (n != 1 || n0 == 1) + { + assert (n_factors < max_n_factors); + factors[n_factors++] = n; + } + + return n_factors; +} + +/* FIXME: comment */ + +static bool +print_factors (const char *s) +{ + uintmax_t factors[MAX_N_FACTORS]; + uintmax_t n; + size_t n_factors; + size_t i; + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + strtol_error err; + + if ((err = xstrtoumax (s, NULL, 10, &n, "")) != LONGINT_OK) + { + if (err == LONGINT_OVERFLOW) + error (0, 0, _("%s is too large"), quote (s)); + else + error (0, 0, _("%s is not a valid positive integer"), quote (s)); + return false; + } + n_factors = factor (n, MAX_N_FACTORS, factors); + printf ("%s:", umaxtostr (n, buf)); + for (i = 0; i < n_factors; i++) + printf (" %s", umaxtostr (factors[i], buf)); + putchar ('\n'); + return true; +} + +static bool +do_stdin (void) +{ + bool ok = true; + token_buffer tokenbuffer; + + init_tokenbuffer (&tokenbuffer); + + for (;;) + { + size_t token_length = readtoken (stdin, DELIM, sizeof (DELIM) - 1, + &tokenbuffer); + if (token_length == (size_t) -1) + break; + ok &= print_factors (tokenbuffer.buffer); + } + free (tokenbuffer.buffer); + + return ok; +} + +int +main (int argc, char **argv) +{ + bool ok; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (argc <= optind) + ok = do_stdin (); + else + { + int i; + ok = true; + for (i = optind; i < argc; i++) + if (! print_factors (argv[i])) + ok = false; + } + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/false.c b/src/false.c new file mode 100644 index 0000000..bc9c703 --- /dev/null +++ b/src/false.c @@ -0,0 +1,2 @@ +#define EXIT_STATUS EXIT_FAILURE +#include "true.c" diff --git a/src/fmt.c b/src/fmt.c new file mode 100644 index 0000000..5ccc8c4 --- /dev/null +++ b/src/fmt.c @@ -0,0 +1,1015 @@ +/* GNU fmt -- simple text formatter. + Copyright (C) 1994-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Ross Paterson . */ + +#include +#include +#include +#include + +/* Redefine. Otherwise, systems (Unicos for one) with headers that define + it to be a type get syntax errors for the variable declaration below. */ +#define word unused_word_type + +#include "system.h" +#include "error.h" +#include "quote.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "fmt" + +#define AUTHORS "Ross Paterson" + +/* The following parameters represent the program's idea of what is + "best". Adjust to taste, subject to the caveats given. */ + +/* Default longest permitted line length (max_width). */ +#define WIDTH 75 + +/* Prefer lines to be LEEWAY % shorter than the maximum width, giving + room for optimization. */ +#define LEEWAY 7 + +/* The default secondary indent of tagged paragraph used for unindented + one-line paragraphs not preceded by any multi-line paragraphs. */ +#define DEF_INDENT 3 + +/* Costs and bonuses are expressed as the equivalent departure from the + optimal line length, multiplied by 10. e.g. assigning something a + cost of 50 means that it is as bad as a line 5 characters too short + or too long. The definition of SHORT_COST(n) should not be changed. + However, EQUIV(n) may need tuning. */ + +/* FIXME: "fmt" misbehaves given large inputs or options. One + possible workaround for part of the problem is to change COST to be + a floating-point type. There are other problems besides COST, + though; see MAXWORDS below. */ + +typedef long int COST; + +#define MAXCOST TYPE_MAXIMUM (COST) + +#define SQR(n) ((n) * (n)) +#define EQUIV(n) SQR ((COST) (n)) + +/* Cost of a filled line n chars longer or shorter than best_width. */ +#define SHORT_COST(n) EQUIV ((n) * 10) + +/* Cost of the difference between adjacent filled lines. */ +#define RAGGED_COST(n) (SHORT_COST (n) / 2) + +/* Basic cost per line. */ +#define LINE_COST EQUIV (70) + +/* Cost of breaking a line after the first word of a sentence, where + the length of the word is N. */ +#define WIDOW_COST(n) (EQUIV (200) / ((n) + 2)) + +/* Cost of breaking a line before the last word of a sentence, where + the length of the word is N. */ +#define ORPHAN_COST(n) (EQUIV (150) / ((n) + 2)) + +/* Bonus for breaking a line at the end of a sentence. */ +#define SENTENCE_BONUS EQUIV (50) + +/* Cost of breaking a line after a period not marking end of a sentence. + With the definition of sentence we are using (borrowed from emacs, see + get_line()) such a break would then look like a sentence break. Hence + we assign a very high cost -- it should be avoided unless things are + really bad. */ +#define NOBREAK_COST EQUIV (600) + +/* Bonus for breaking a line before open parenthesis. */ +#define PAREN_BONUS EQUIV (40) + +/* Bonus for breaking a line after other punctuation. */ +#define PUNCT_BONUS EQUIV(40) + +/* Credit for breaking a long paragraph one line later. */ +#define LINE_CREDIT EQUIV(3) + +/* Size of paragraph buffer, in words and characters. Longer paragraphs + are handled neatly (cf. flush_paragraph()), so long as these values + are considerably greater than required by the width. These values + cannot be extended indefinitely: doing so would run into size limits + and/or cause more overflows in cost calculations. FIXME: Remove these + arbitrary limits. */ + +#define MAXWORDS 1000 +#define MAXCHARS 5000 + +/* Extra ctype(3)-style macros. */ + +#define isopen(c) (strchr ("([`'\"", c) != NULL) +#define isclose(c) (strchr (")]'\"", c) != NULL) +#define isperiod(c) (strchr (".?!", c) != NULL) + +/* Size of a tab stop, for expansion on input and re-introduction on + output. */ +#define TABWIDTH 8 + +/* Word descriptor structure. */ + +typedef struct Word WORD; + +struct Word + { + + /* Static attributes determined during input. */ + + const char *text; /* the text of the word */ + int length; /* length of this word */ + int space; /* the size of the following space */ + unsigned int paren:1; /* starts with open paren */ + unsigned int period:1; /* ends in [.?!])* */ + unsigned int punct:1; /* ends in punctuation */ + unsigned int final:1; /* end of sentence */ + + /* The remaining fields are computed during the optimization. */ + + int line_length; /* length of the best line starting here */ + COST best_cost; /* cost of best paragraph starting here */ + WORD *next_break; /* break which achieves best_cost */ + }; + +/* Forward declarations. */ + +static void set_prefix (char *p); +static void fmt (FILE *f); +static bool get_paragraph (FILE *f); +static int get_line (FILE *f, int c); +static int get_prefix (FILE *f); +static int get_space (FILE *f, int c); +static int copy_rest (FILE *f, int c); +static bool same_para (int c); +static void flush_paragraph (void); +static void fmt_paragraph (void); +static void check_punctuation (WORD *w); +static COST base_cost (WORD *this); +static COST line_cost (WORD *next, int len); +static void put_paragraph (WORD *finish); +static void put_line (WORD *w, int indent); +static void put_word (WORD *w); +static void put_space (int space); + +/* The name this program was run with. */ +const char *program_name; + +/* Option values. */ + +/* If true, first 2 lines may have different indent (default false). */ +static bool crown; + +/* If true, first 2 lines _must_ have different indent (default false). */ +static bool tagged; + +/* If true, each line is a paragraph on its own (default false). */ +static bool split; + +/* If true, don't preserve inter-word spacing (default false). */ +static bool uniform; + +/* Prefix minus leading and trailing spaces (default ""). */ +static const char *prefix; + +/* User-supplied maximum line width (default WIDTH). The only output + lines longer than this will each comprise a single word. */ +static int max_width; + +/* Values derived from the option values. */ + +/* The length of prefix minus leading space. */ +static int prefix_full_length; + +/* The length of the leading space trimmed from the prefix. */ +static int prefix_lead_space; + +/* The length of prefix minus leading and trailing space. */ +static int prefix_length; + +/* The preferred width of text lines, set to LEEWAY % less than max_width. */ +static int best_width; + +/* Dynamic variables. */ + +/* Start column of the character most recently read from the input file. */ +static int in_column; + +/* Start column of the next character to be written to stdout. */ +static int out_column; + +/* Space for the paragraph text -- longer paragraphs are handled neatly + (cf. flush_paragraph()). */ +static char parabuf[MAXCHARS]; + +/* A pointer into parabuf, indicating the first unused character position. */ +static char *wptr; + +/* The words of a paragraph -- longer paragraphs are handled neatly + (cf. flush_paragraph()). */ +static WORD word[MAXWORDS]; + +/* A pointer into the above word array, indicating the first position + after the last complete word. Sometimes it will point at an incomplete + word. */ +static WORD *word_limit; + +/* If true, current input file contains tab characters, and so tabs can be + used for white space on output. */ +static bool tabs; + +/* Space before trimmed prefix on each line of the current paragraph. */ +static int prefix_indent; + +/* Indentation of the first line of the current paragraph. */ +static int first_indent; + +/* Indentation of other lines of the current paragraph */ +static int other_indent; + +/* To detect the end of a paragraph, we need to look ahead to the first + non-blank character after the prefix on the next line, or the first + character on the following line that failed to match the prefix. + We can reconstruct the lookahead from that character (next_char), its + position on the line (in_column) and the amount of space before the + prefix (next_prefix_indent). See get_paragraph() and copy_rest(). */ + +/* The last character read from the input file. */ +static int next_char; + +/* The space before the trimmed prefix (or part of it) on the next line + after the current paragraph. */ +static int next_prefix_indent; + +/* If nonzero, the length of the last line output in the current + paragraph, used to charge for raggedness at the split point for long + paragraphs chosen by fmt_paragraph(). */ +static int last_line_length; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [-DIGITS] [OPTION]... [FILE]...\n"), program_name); + fputs (_("\ +Reformat each paragraph in the FILE(s), writing to standard output.\n\ +If no FILE or if FILE is `-', read standard input.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -c, --crown-margin preserve indentation of first two lines\n\ + -p, --prefix=STRING reformat only lines beginning with STRING,\n\ + reattaching the prefix to reformatted lines\n\ + -s, --split-only split long lines, but do not refill\n\ +"), + stdout); + fputs (_("\ + -t, --tagged-paragraph indentation of first line different from second\n\ + -u, --uniform-spacing one space between words, two after sentences\n\ + -w, --width=WIDTH maximum line width (default of 75 columns)\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +With no FILE, or when FILE is -, read standard input.\n"), + stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Decode options and launch execution. */ + +static const struct option long_options[] = +{ + {"crown-margin", no_argument, NULL, 'c'}, + {"prefix", required_argument, NULL, 'p'}, + {"split-only", no_argument, NULL, 's'}, + {"tagged-paragraph", no_argument, NULL, 't'}, + {"uniform-spacing", no_argument, NULL, 'u'}, + {"width", required_argument, NULL, 'w'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0}, +}; + +int +main (int argc, char **argv) +{ + int optchar; + bool ok = true; + char const *max_width_option = NULL; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + crown = tagged = split = uniform = false; + max_width = WIDTH; + prefix = ""; + prefix_length = prefix_lead_space = prefix_full_length = 0; + + if (argc > 1 && argv[1][0] == '-' && ISDIGIT (argv[1][1])) + { + /* Old option syntax; a dash followed by one or more digits. */ + max_width_option = argv[1] + 1; + + /* Make the option we just parsed invisible to getopt. */ + argv[1] = argv[0]; + argv++; + argc--; + } + + while ((optchar = getopt_long (argc, argv, "0123456789cstuw:p:", + long_options, NULL)) + != -1) + switch (optchar) + { + default: + if (ISDIGIT (optchar)) + error (0, 0, _("invalid option -- %c; -WIDTH is recognized\ + only when it is the first\noption; use -w N instead"), + optchar); + usage (EXIT_FAILURE); + + case 'c': + crown = true; + break; + + case 's': + split = true; + break; + + case 't': + tagged = true; + break; + + case 'u': + uniform = true; + break; + + case 'w': + max_width_option = optarg; + break; + + case 'p': + set_prefix (optarg); + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + } + + if (max_width_option) + { + /* Limit max_width to MAXCHARS / 2; otherwise, the resulting + output can be quite ugly. */ + unsigned long int tmp; + if (! (xstrtoul (max_width_option, NULL, 10, &tmp, "") == LONGINT_OK + && tmp <= MAXCHARS / 2)) + error (EXIT_FAILURE, 0, _("invalid width: %s"), + quote (max_width_option)); + max_width = tmp; + } + + best_width = max_width * (2 * (100 - LEEWAY) + 1) / 200; + + if (optind == argc) + fmt (stdin); + else + { + for (; optind < argc; optind++) + { + char *file = argv[optind]; + if (STREQ (file, "-")) + fmt (stdin); + else + { + FILE *in_stream; + in_stream = fopen (file, "r"); + if (in_stream != NULL) + { + fmt (in_stream); + if (fclose (in_stream) == EOF) + { + error (0, errno, "%s", file); + ok = false; + } + } + else + { + error (0, errno, _("cannot open %s for reading"), + quote (file)); + ok = false; + } + } + } + } + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} + +/* Trim space from the front and back of the string P, yielding the prefix, + and record the lengths of the prefix and the space trimmed. */ + +static void +set_prefix (char *p) +{ + char *s; + + prefix_lead_space = 0; + while (*p == ' ') + { + prefix_lead_space++; + p++; + } + prefix = p; + prefix_full_length = strlen (p); + s = p + prefix_full_length; + while (s > p && s[-1] == ' ') + s--; + *s = '\0'; + prefix_length = s - p; +} + +/* read file F and send formatted output to stdout. */ + +static void +fmt (FILE *f) +{ + tabs = false; + other_indent = 0; + next_char = get_prefix (f); + while (get_paragraph (f)) + { + fmt_paragraph (); + put_paragraph (word_limit); + } +} + +/* Set the global variable `other_indent' according to SAME_PARAGRAPH + and other global variables. */ + +static void +set_other_indent (bool same_paragraph) +{ + if (split) + other_indent = first_indent; + else if (crown) + { + other_indent = (same_paragraph ? in_column : first_indent); + } + else if (tagged) + { + if (same_paragraph && in_column != first_indent) + { + other_indent = in_column; + } + + /* Only one line: use the secondary indent from last time if it + splits, or 0 if there have been no multi-line paragraphs in the + input so far. But if these rules make the two indents the same, + pick a new secondary indent. */ + + else if (other_indent == first_indent) + other_indent = first_indent == 0 ? DEF_INDENT : 0; + } + else + { + other_indent = first_indent; + } +} + +/* Read a paragraph from input file F. A paragraph consists of a + maximal number of non-blank (excluding any prefix) lines subject to: + * In split mode, a paragraph is a single non-blank line. + * In crown mode, the second and subsequent lines must have the + same indentation, but possibly different from the indent of the + first line. + * Tagged mode is similar, but the first and second lines must have + different indentations. + * Otherwise, all lines of a paragraph must have the same indent. + If a prefix is in effect, it must be present at the same indent for + each line in the paragraph. + + Return false if end-of-file was encountered before the start of a + paragraph, else true. */ + +static bool +get_paragraph (FILE *f) +{ + int c; + + last_line_length = 0; + c = next_char; + + /* Scan (and copy) blank lines, and lines not introduced by the prefix. */ + + while (c == '\n' || c == EOF + || next_prefix_indent < prefix_lead_space + || in_column < next_prefix_indent + prefix_full_length) + { + c = copy_rest (f, c); + if (c == EOF) + { + next_char = EOF; + return false; + } + putchar ('\n'); + c = get_prefix (f); + } + + /* Got a suitable first line for a paragraph. */ + + prefix_indent = next_prefix_indent; + first_indent = in_column; + wptr = parabuf; + word_limit = word; + c = get_line (f, c); + set_other_indent (same_para (c)); + + /* Read rest of paragraph (unless split is specified). */ + + if (split) + { + /* empty */ + } + else if (crown) + { + if (same_para (c)) + { + do + { /* for each line till the end of the para */ + c = get_line (f, c); + } + while (same_para (c) && in_column == other_indent); + } + } + else if (tagged) + { + if (same_para (c) && in_column != first_indent) + { + do + { /* for each line till the end of the para */ + c = get_line (f, c); + } + while (same_para (c) && in_column == other_indent); + } + } + else + { + while (same_para (c) && in_column == other_indent) + c = get_line (f, c); + } + (word_limit - 1)->period = (word_limit - 1)->final = true; + next_char = c; + return true; +} + +/* Copy to the output a line that failed to match the prefix, or that + was blank after the prefix. In the former case, C is the character + that failed to match the prefix. In the latter, C is \n or EOF. + Return the character (\n or EOF) ending the line. */ + +static int +copy_rest (FILE *f, int c) +{ + const char *s; + + out_column = 0; + if (in_column > next_prefix_indent || (c != '\n' && c != EOF)) + { + put_space (next_prefix_indent); + for (s = prefix; out_column != in_column && *s; out_column++) + putchar (*s++); + if (c != EOF && c != '\n') + put_space (in_column - out_column); + if (c == EOF && in_column >= next_prefix_indent + prefix_length) + putchar ('\n'); + } + while (c != '\n' && c != EOF) + { + putchar (c); + c = getc (f); + } + return c; +} + +/* Return true if a line whose first non-blank character after the + prefix (if any) is C could belong to the current paragraph, + otherwise false. */ + +static bool +same_para (int c) +{ + return (next_prefix_indent == prefix_indent + && in_column >= next_prefix_indent + prefix_full_length + && c != '\n' && c != EOF); +} + +/* Read a line from input file F, given first non-blank character C + after the prefix, and the following indent, and break it into words. + A word is a maximal non-empty string of non-white characters. A word + ending in [.?!]["')\]]* and followed by end-of-line or at least two + spaces ends a sentence, as in emacs. + + Return the first non-blank character of the next line. */ + +static int +get_line (FILE *f, int c) +{ + int start; + char *end_of_parabuf; + WORD *end_of_word; + + end_of_parabuf = ¶buf[MAXCHARS]; + end_of_word = &word[MAXWORDS - 2]; + + do + { /* for each word in a line */ + + /* Scan word. */ + + word_limit->text = wptr; + do + { + if (wptr == end_of_parabuf) + { + set_other_indent (true); + flush_paragraph (); + } + *wptr++ = c; + c = getc (f); + } + while (c != EOF && !isspace (c)); + in_column += word_limit->length = wptr - word_limit->text; + check_punctuation (word_limit); + + /* Scan inter-word space. */ + + start = in_column; + c = get_space (f, c); + word_limit->space = in_column - start; + word_limit->final = (c == EOF + || (word_limit->period + && (c == '\n' || word_limit->space > 1))); + if (c == '\n' || c == EOF || uniform) + word_limit->space = word_limit->final ? 2 : 1; + if (word_limit == end_of_word) + { + set_other_indent (true); + flush_paragraph (); + } + word_limit++; + } + while (c != '\n' && c != EOF); + return get_prefix (f); +} + +/* Read a prefix from input file F. Return either first non-matching + character, or first non-blank character after the prefix. */ + +static int +get_prefix (FILE *f) +{ + int c; + + in_column = 0; + c = get_space (f, getc (f)); + if (prefix_length == 0) + next_prefix_indent = prefix_lead_space < in_column ? + prefix_lead_space : in_column; + else + { + const char *p; + next_prefix_indent = in_column; + for (p = prefix; *p != '\0'; p++) + { + unsigned char pc = *p; + if (c != pc) + return c; + in_column++; + c = getc (f); + } + c = get_space (f, c); + } + return c; +} + +/* Read blank characters from input file F, starting with C, and keeping + in_column up-to-date. Return first non-blank character. */ + +static int +get_space (FILE *f, int c) +{ + for (;;) + { + if (c == ' ') + in_column++; + else if (c == '\t') + { + tabs = true; + in_column = (in_column / TABWIDTH + 1) * TABWIDTH; + } + else + return c; + c = getc (f); + } +} + +/* Set extra fields in word W describing any attached punctuation. */ + +static void +check_punctuation (WORD *w) +{ + char const *start = w->text; + char const *finish = start + (w->length - 1); + unsigned char fin = *finish; + + w->paren = isopen (*start); + w->punct = !! ispunct (fin); + while (start < finish && isclose (*finish)) + finish--; + w->period = isperiod (*finish); +} + +/* Flush part of the paragraph to make room. This function is called on + hitting the limit on the number of words or characters. */ + +static void +flush_paragraph (void) +{ + WORD *split_point; + WORD *w; + int shift; + COST best_break; + + /* In the special case where it's all one word, just flush it. */ + + if (word_limit == word) + { + fwrite (parabuf, sizeof *parabuf, wptr - parabuf, stdout); + wptr = parabuf; + return; + } + + /* Otherwise: + - format what you have so far as a paragraph, + - find a low-cost line break near the end, + - output to there, + - make that the start of the paragraph. */ + + fmt_paragraph (); + + /* Choose a good split point. */ + + split_point = word_limit; + best_break = MAXCOST; + for (w = word->next_break; w != word_limit; w = w->next_break) + { + if (w->best_cost - w->next_break->best_cost < best_break) + { + split_point = w; + best_break = w->best_cost - w->next_break->best_cost; + } + if (best_break <= MAXCOST - LINE_CREDIT) + best_break += LINE_CREDIT; + } + put_paragraph (split_point); + + /* Copy text of words down to start of parabuf -- we use memmove because + the source and target may overlap. */ + + memmove (parabuf, split_point->text, wptr - split_point->text); + shift = split_point->text - parabuf; + wptr -= shift; + + /* Adjust text pointers. */ + + for (w = split_point; w <= word_limit; w++) + w->text -= shift; + + /* Copy words from split_point down to word -- we use memmove because + the source and target may overlap. */ + + memmove (word, split_point, (word_limit - split_point + 1) * sizeof *word); + word_limit -= split_point - word; +} + +/* Compute the optimal formatting for the whole paragraph by computing + and remembering the optimal formatting for each suffix from the empty + one to the whole paragraph. */ + +static void +fmt_paragraph (void) +{ + WORD *start, *w; + int len; + COST wcost, best; + int saved_length; + + word_limit->best_cost = 0; + saved_length = word_limit->length; + word_limit->length = max_width; /* sentinel */ + + for (start = word_limit - 1; start >= word; start--) + { + best = MAXCOST; + len = start == word ? first_indent : other_indent; + + /* At least one word, however long, in the line. */ + + w = start; + len += w->length; + do + { + w++; + + /* Consider breaking before w. */ + + wcost = line_cost (w, len) + w->best_cost; + if (start == word && last_line_length > 0) + wcost += RAGGED_COST (len - last_line_length); + if (wcost < best) + { + best = wcost; + start->next_break = w; + start->line_length = len; + } + + /* This is a kludge to keep us from computing `len' as the + sum of the sentinel length and some non-zero number. + Since the sentinel w->length may be INT_MAX, adding + to that would give a negative result. */ + if (w == word_limit) + break; + + len += (w - 1)->space + w->length; /* w > start >= word */ + } + while (len < max_width); + start->best_cost = best + base_cost (start); + } + + word_limit->length = saved_length; +} + +/* Return the constant component of the cost of breaking before the + word THIS. */ + +static COST +base_cost (WORD *this) +{ + COST cost; + + cost = LINE_COST; + + if (this > word) + { + if ((this - 1)->period) + { + if ((this - 1)->final) + cost -= SENTENCE_BONUS; + else + cost += NOBREAK_COST; + } + else if ((this - 1)->punct) + cost -= PUNCT_BONUS; + else if (this > word + 1 && (this - 2)->final) + cost += WIDOW_COST ((this - 1)->length); + } + + if (this->paren) + cost -= PAREN_BONUS; + else if (this->final) + cost += ORPHAN_COST (this->length); + + return cost; +} + +/* Return the component of the cost of breaking before word NEXT that + depends on LEN, the length of the line beginning there. */ + +static COST +line_cost (WORD *next, int len) +{ + int n; + COST cost; + + if (next == word_limit) + return 0; + n = best_width - len; + cost = SHORT_COST (n); + if (next->next_break != word_limit) + { + n = len - next->line_length; + cost += RAGGED_COST (n); + } + return cost; +} + +/* Output to stdout a paragraph from word up to (but not including) + FINISH, which must be in the next_break chain from word. */ + +static void +put_paragraph (WORD *finish) +{ + WORD *w; + + put_line (word, first_indent); + for (w = word->next_break; w != finish; w = w->next_break) + put_line (w, other_indent); +} + +/* Output to stdout the line beginning with word W, beginning in column + INDENT, including the prefix (if any). */ + +static void +put_line (WORD *w, int indent) +{ + WORD *endline; + + out_column = 0; + put_space (prefix_indent); + fputs (prefix, stdout); + out_column += prefix_length; + put_space (indent - out_column); + + endline = w->next_break - 1; + for (; w != endline; w++) + { + put_word (w); + put_space (w->space); + } + put_word (w); + last_line_length = out_column; + putchar ('\n'); +} + +/* Output to stdout the word W. */ + +static void +put_word (WORD *w) +{ + const char *s; + int n; + + s = w->text; + for (n = w->length; n != 0; n--) + putchar (*s++); + out_column += w->length; +} + +/* Output to stdout SPACE spaces, or equivalent tabs. */ + +static void +put_space (int space) +{ + int space_target, tab_target; + + space_target = out_column + space; + if (tabs) + { + tab_target = space_target / TABWIDTH * TABWIDTH; + if (out_column + 1 < tab_target) + while (out_column < tab_target) + { + putchar ('\t'); + out_column = (out_column / TABWIDTH + 1) * TABWIDTH; + } + } + while (out_column < space_target) + { + putchar (' '); + out_column++; + } +} diff --git a/src/fold.c b/src/fold.c new file mode 100644 index 0000000..0d4ea58 --- /dev/null +++ b/src/fold.c @@ -0,0 +1,318 @@ +/* fold -- wrap each input line to fit in specified width. + Copyright (C) 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by David MacKenzie, djm@gnu.ai.mit.edu. */ + +#include + +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "quote.h" +#include "xstrtol.h" + +#define TAB_WIDTH 8 + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "fold" + +#define AUTHORS "David MacKenzie" + +/* The name this program was run with. */ +char *program_name; + +/* If nonzero, try to break on whitespace. */ +static bool break_spaces; + +/* If nonzero, count bytes, not column positions. */ +static bool count_bytes; + +/* If nonzero, at least one of the files we read was standard input. */ +static bool have_read_stdin; + +static char const shortopts[] = "bsw:0::1::2::3::4::5::6::7::8::9::"; + +static struct option const longopts[] = +{ + {"bytes", no_argument, NULL, 'b'}, + {"spaces", no_argument, NULL, 's'}, + {"width", required_argument, NULL, 'w'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + fputs (_("\ +Wrap input lines in each FILE (standard input by default), writing to\n\ +standard output.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -b, --bytes count bytes rather than columns\n\ + -s, --spaces break at spaces\n\ + -w, --width=WIDTH use WIDTH columns instead of 80\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Assuming the current column is COLUMN, return the column that + printing C will move the cursor to. + The first column is 0. */ + +static size_t +adjust_column (size_t column, char c) +{ + if (!count_bytes) + { + if (c == '\b') + { + if (column > 0) + column--; + } + else if (c == '\r') + column = 0; + else if (c == '\t') + column += TAB_WIDTH - column % TAB_WIDTH; + else /* if (isprint (c)) */ + column++; + } + else + column++; + return column; +} + +/* Fold file FILENAME, or standard input if FILENAME is "-", + to stdout, with maximum line length WIDTH. + Return true if successful. */ + +static bool +fold_file (char const *filename, size_t width) +{ + FILE *istream; + int c; + size_t column = 0; /* Screen column where next char will go. */ + size_t offset_out = 0; /* Index in `line_out' for next char. */ + static char *line_out = NULL; + static size_t allocated_out = 0; + int saved_errno; + + if (STREQ (filename, "-")) + { + istream = stdin; + have_read_stdin = true; + } + else + istream = fopen (filename, "r"); + + if (istream == NULL) + { + error (0, errno, "%s", filename); + return false; + } + + while ((c = getc (istream)) != EOF) + { + if (offset_out + 1 >= allocated_out) + line_out = X2REALLOC (line_out, &allocated_out); + + if (c == '\n') + { + line_out[offset_out++] = c; + fwrite (line_out, sizeof (char), offset_out, stdout); + column = offset_out = 0; + continue; + } + + rescan: + column = adjust_column (column, c); + + if (column > width) + { + /* This character would make the line too long. + Print the line plus a newline, and make this character + start the next line. */ + if (break_spaces) + { + bool found_blank = false; + size_t logical_end = offset_out; + + /* Look for the last blank. */ + while (logical_end) + { + --logical_end; + if (isblank (to_uchar (line_out[logical_end]))) + { + found_blank = true; + break; + } + } + + if (found_blank) + { + size_t i; + + /* Found a blank. Don't output the part after it. */ + logical_end++; + fwrite (line_out, sizeof (char), (size_t) logical_end, + stdout); + putchar ('\n'); + /* Move the remainder to the beginning of the next line. + The areas being copied here might overlap. */ + memmove (line_out, line_out + logical_end, + offset_out - logical_end); + offset_out -= logical_end; + for (column = i = 0; i < offset_out; i++) + column = adjust_column (column, line_out[i]); + goto rescan; + } + } + + if (offset_out == 0) + { + line_out[offset_out++] = c; + continue; + } + + line_out[offset_out++] = '\n'; + fwrite (line_out, sizeof (char), (size_t) offset_out, stdout); + column = offset_out = 0; + goto rescan; + } + + line_out[offset_out++] = c; + } + + saved_errno = errno; + + if (offset_out) + fwrite (line_out, sizeof (char), (size_t) offset_out, stdout); + + if (ferror (istream)) + { + error (0, saved_errno, "%s", filename); + if (!STREQ (filename, "-")) + fclose (istream); + return false; + } + if (!STREQ (filename, "-") && fclose (istream) == EOF) + { + error (0, errno, "%s", filename); + return false; + } + + return true; +} + +int +main (int argc, char **argv) +{ + size_t width = 80; + int i; + int optc; + bool ok; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + break_spaces = count_bytes = have_read_stdin = false; + + while ((optc = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1) + { + char optargbuf[2]; + + switch (optc) + { + case 'b': /* Count bytes rather than columns. */ + count_bytes = true; + break; + + case 's': /* Break at word boundaries. */ + break_spaces = true; + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (optarg) + optarg--; + else + { + optargbuf[0] = optc; + optargbuf[1] = '\0'; + optarg = optargbuf; + } + /* Fall through. */ + case 'w': /* Line width. */ + { + unsigned long int tmp_ulong; + if (! (xstrtoul (optarg, NULL, 10, &tmp_ulong, "") == LONGINT_OK + && 0 < tmp_ulong && tmp_ulong < SIZE_MAX - TAB_WIDTH)) + error (EXIT_FAILURE, 0, + _("invalid number of columns: %s"), quote (optarg)); + width = tmp_ulong; + } + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (argc == optind) + ok = fold_file ("-", width); + else + { + ok = true; + for (i = optind; i < argc; i++) + ok &= fold_file (argv[i], width); + } + + if (have_read_stdin && fclose (stdin) == EOF) + error (EXIT_FAILURE, errno, "-"); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/fs.h b/src/fs.h new file mode 100644 index 0000000..5ceea5b --- /dev/null +++ b/src/fs.h @@ -0,0 +1,43 @@ +/* Define the magic numbers as given by statfs(2). + Please send additions to bug-coreutils@gnu.org and meskes@debian.org. + This file is generated automatically from ./stat.c. */ + +#if defined __linux__ +# define S_MAGIC_AFFS 0xADFF +# define S_MAGIC_DEVPTS 0x1CD1 +# define S_MAGIC_EXT 0x137D +# define S_MAGIC_EXT2_OLD 0xEF51 +# define S_MAGIC_EXT2 0xEF53 +# define S_MAGIC_JFS 0x3153464a +# define S_MAGIC_XFS 0x58465342 +# define S_MAGIC_HPFS 0xF995E849 +# define S_MAGIC_ISOFS 0x9660 +# define S_MAGIC_ISOFS_WIN 0x4000 +# define S_MAGIC_ISOFS_R_WIN 0x4004 +# define S_MAGIC_MINIX 0x137F +# define S_MAGIC_MINIX_30 0x138F +# define S_MAGIC_MINIX_V2 0x2468 +# define S_MAGIC_MINIX_V2_30 0x2478 +# define S_MAGIC_MSDOS 0x4d44 +# define S_MAGIC_FAT 0x4006 +# define S_MAGIC_NCP 0x564c +# define S_MAGIC_NFS 0x6969 +# define S_MAGIC_PROC 0x9fa0 +# define S_MAGIC_SMB 0x517B +# define S_MAGIC_XENIX 0x012FF7B4 +# define S_MAGIC_SYSV4 0x012FF7B5 +# define S_MAGIC_SYSV2 0x012FF7B6 +# define S_MAGIC_COH 0x012FF7B7 +# define S_MAGIC_UFS 0x00011954 +# define S_MAGIC_XIAFS 0x012FD16D +# define S_MAGIC_NTFS 0x5346544e +# define S_MAGIC_TMPFS 0x1021994 +# define S_MAGIC_REISERFS 0x52654973 +# define S_MAGIC_CRAMFS 0x28cd3d45 +# define S_MAGIC_ROMFS 0x7275 +# define S_MAGIC_RAMFS 0x858458f6 +# define S_MAGIC_SQUASHFS 0x73717368 +# define S_MAGIC_SYSFS 0x62656572 +#elif defined __GNU__ +# include +#endif diff --git a/src/groups.sh b/src/groups.sh new file mode 100755 index 0000000..dd32c63 --- /dev/null +++ b/src/groups.sh @@ -0,0 +1,83 @@ +#!/bin/sh +# groups -- print the groups a user is in +# Copyright (C) 1991, 1997, 2000, 2002, 2004-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +# Written by David MacKenzie . + +# Make sure we get GNU id, if possible; also allow +# it to be somewhere else in PATH if not installed yet. +PATH=@bindir@:$PATH + +usage="Usage: $0 [OPTION]... [USERNAME]... + + --help display this help and exit + --version output version information and exit + +Same as id -Gn. If no USERNAME, use current process. + +Report bugs to <@PACKAGE_BUGREPORT@>." + +version='groups (@GNU_PACKAGE@) @VERSION@ +Copyright (C) @RELEASE_YEAR@ Free Software Foundation, Inc. +This is free software. You may redistribute copies of it under the terms of +the GNU General Public License . +There is NO WARRANTY, to the extent permitted by law. + +Written by David MacKenzie.' + + +for arg +do + case $arg in + --help | --hel | --he | --h) + exec echo "$usage" ;; + --version | --versio | --versi | --vers | --ver | --ve | --v) + exec echo "$version" ;; + --) + shift + break ;; + -*) + echo "$0: invalid option: $arg" >&2 + exit 1 ;; + esac +done + +# With fewer than two arguments, simply exec "id". +case $# in + 0|1) exec id -Gn -- "$@" ;; +esac + +# With more, we need a loop, and be sure to exit nonzero upon failure. +status=0 +write_error=0 + +for name +do + if groups=`id -Gn -- "$name"`; then + echo "$name : $groups" || { + status=$? + if test $write_error = 0; then + echo "$0: write error" >&2 + write_error=1 + fi + } + else + status=$? + fi +done + +exit $status diff --git a/src/head.c b/src/head.c new file mode 100644 index 0000000..4038722 --- /dev/null +++ b/src/head.c @@ -0,0 +1,1064 @@ +/* head -- output first part of file(s) + Copyright (C) 89, 90, 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Options: (see usage) + Reads from standard input if no files are given or when a filename of + ``-'' is encountered. + By default, filename headers are printed only if more than one file + is given. + By default, prints the first 10 lines (head -n 10). + + David MacKenzie */ + +#include + +#include +#include +#include + +#include "system.h" + +#include "error.h" +#include "full-write.h" +#include "full-read.h" +#include "inttostr.h" +#include "quote.h" +#include "safe-read.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "head" + +#define AUTHORS "David MacKenzie", "Jim Meyering" + +/* Number of lines/chars/blocks to head. */ +#define DEFAULT_NUMBER 10 + +/* Useful only when eliding tail bytes or lines. + If true, skip the is-regular-file test used to determine whether + to use the lseek optimization. Instead, use the more general (and + more expensive) code unconditionally. Intended solely for testing. */ +static bool presume_input_pipe; + +/* If true, print filename headers. */ +static bool print_headers; + +/* When to print the filename banners. */ +enum header_mode +{ + multiple_files, always, never +}; + +/* The name this program was run with. */ +char *program_name; + +/* Have we ever read standard input? */ +static bool have_read_stdin; + +enum Copy_fd_status + { + COPY_FD_OK = 0, + COPY_FD_READ_ERROR, + COPY_FD_WRITE_ERROR, + COPY_FD_UNEXPECTED_EOF + }; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + PRESUME_INPUT_PIPE_OPTION = CHAR_MAX + 1 +}; + +static struct option const long_options[] = +{ + {"bytes", required_argument, NULL, 'c'}, + {"lines", required_argument, NULL, 'n'}, + {"-presume-input-pipe", no_argument, NULL, + PRESUME_INPUT_PIPE_OPTION}, /* do not document */ + {"quiet", no_argument, NULL, 'q'}, + {"silent", no_argument, NULL, 'q'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + fputs (_("\ +Print the first 10 lines of each FILE to standard output.\n\ +With more than one FILE, precede each with a header giving the file name.\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -c, --bytes=[-]N print the first N bytes of each file;\n\ + with the leading `-', print all but the last\n\ + N bytes of each file\n\ + -n, --lines=[-]N print the first N lines instead of the first 10;\n\ + with the leading `-', print all but the last\n\ + N lines of each file\n\ +"), stdout); + fputs (_("\ + -q, --quiet, --silent never print headers giving file names\n\ + -v, --verbose always print headers giving file names\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +N may have a multiplier suffix: b 512, k 1024, m 1024*1024.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +static void +diagnose_copy_fd_failure (enum Copy_fd_status err, char const *filename) +{ + switch (err) + { + case COPY_FD_READ_ERROR: + error (0, errno, _("error reading %s"), quote (filename)); + break; + case COPY_FD_WRITE_ERROR: + error (0, errno, _("error writing %s"), quote (filename)); + break; + case COPY_FD_UNEXPECTED_EOF: + error (0, errno, _("%s: file has shrunk too much"), quote (filename)); + break; + default: + abort (); + } +} + +static void +write_header (const char *filename) +{ + static bool first_file = true; + + printf ("%s==> %s <==\n", (first_file ? "" : "\n"), filename); + first_file = false; +} + +/* Copy no more than N_BYTES from file descriptor SRC_FD to O_STREAM. + Return an appropriate indication of success or failure. */ + +static enum Copy_fd_status +copy_fd (int src_fd, FILE *o_stream, uintmax_t n_bytes) +{ + char buf[BUFSIZ]; + const size_t buf_size = sizeof (buf); + + /* Copy the file contents. */ + while (0 < n_bytes) + { + size_t n_to_read = MIN (buf_size, n_bytes); + size_t n_read = safe_read (src_fd, buf, n_to_read); + if (n_read == SAFE_READ_ERROR) + return COPY_FD_READ_ERROR; + + n_bytes -= n_read; + + if (n_read == 0 && n_bytes != 0) + return COPY_FD_UNEXPECTED_EOF; + + if (fwrite (buf, 1, n_read, o_stream) < n_read) + return COPY_FD_WRITE_ERROR; + } + + return COPY_FD_OK; +} + +/* Print all but the last N_ELIDE lines from the input available via + the non-seekable file descriptor FD. Return true upon success. + Give a diagnostic and return false upon error. */ +static bool +elide_tail_bytes_pipe (const char *filename, int fd, uintmax_t n_elide_0) +{ + size_t n_elide = n_elide_0; + +#ifndef HEAD_TAIL_PIPE_READ_BUFSIZE +# define HEAD_TAIL_PIPE_READ_BUFSIZE BUFSIZ +#endif +#define READ_BUFSIZE HEAD_TAIL_PIPE_READ_BUFSIZE + + /* If we're eliding no more than this many bytes, then it's ok to allocate + more memory in order to use a more time-efficient algorithm. + FIXME: use a fraction of available memory instead, as in sort. + FIXME: is this even worthwhile? */ +#ifndef HEAD_TAIL_PIPE_BYTECOUNT_THRESHOLD +# define HEAD_TAIL_PIPE_BYTECOUNT_THRESHOLD 1024 * 1024 +#endif + +#if HEAD_TAIL_PIPE_BYTECOUNT_THRESHOLD < 2 * READ_BUFSIZE + "HEAD_TAIL_PIPE_BYTECOUNT_THRESHOLD must be at least 2 * READ_BUFSIZE" +#endif + + if (SIZE_MAX < n_elide_0 + READ_BUFSIZE) + { + char umax_buf[INT_BUFSIZE_BOUND (uintmax_t)]; + error (EXIT_FAILURE, 0, _("%s: number of bytes is too large"), + umaxtostr (n_elide_0, umax_buf)); + } + + /* Two cases to consider... + 1) n_elide is small enough that we can afford to double-buffer: + allocate 2 * (READ_BUFSIZE + n_elide) bytes + 2) n_elide is too big for that, so we allocate only + (READ_BUFSIZE + n_elide) bytes + + FIXME: profile, to see if double-buffering is worthwhile + + CAUTION: do not fail (out of memory) when asked to elide + a ridiculous amount, but when given only a small input. */ + + if (n_elide <= HEAD_TAIL_PIPE_BYTECOUNT_THRESHOLD) + { + bool ok = true; + bool first = true; + bool eof = false; + size_t n_to_read = READ_BUFSIZE + n_elide; + bool i; + char *b[2]; + b[0] = xnmalloc (2, n_to_read); + b[1] = b[0] + n_to_read; + + for (i = false; ! eof ; i = !i) + { + size_t n_read = full_read (fd, b[i], n_to_read); + size_t delta = 0; + if (n_read < n_to_read) + { + if (errno != 0) + { + error (0, errno, _("error reading %s"), quote (filename)); + ok = false; + break; + } + + /* reached EOF */ + if (n_read <= n_elide) + { + if (first) + { + /* The input is no larger than the number of bytes + to elide. So there's nothing to output, and + we're done. */ + } + else + { + delta = n_elide - n_read; + } + } + eof = true; + } + + /* Output any (but maybe just part of the) elided data from + the previous round. */ + if ( ! first) + { + /* Don't bother checking for errors here. + If there's a failure, the test of the following + fwrite or in close_stdout will catch it. */ + fwrite (b[!i] + READ_BUFSIZE, 1, n_elide - delta, stdout); + } + first = false; + + if (n_elide < n_read + && fwrite (b[i], 1, n_read - n_elide, stdout) < n_read - n_elide) + { + error (0, errno, _("write error")); + ok = false; + break; + } + } + + free (b[0]); + return ok; + } + else + { + /* Read blocks of size READ_BUFSIZE, until we've read at least n_elide + bytes. Then, for each new buffer we read, also write an old one. */ + + bool ok = true; + bool eof = false; + size_t n_read; + bool buffered_enough; + size_t i, i_next; + char **b; + /* Round n_elide up to a multiple of READ_BUFSIZE. */ + size_t rem = READ_BUFSIZE - (n_elide % READ_BUFSIZE); + size_t n_elide_round = n_elide + rem; + size_t n_bufs = n_elide_round / READ_BUFSIZE + 1; + b = xcalloc (n_bufs, sizeof *b); + + buffered_enough = false; + for (i = 0, i_next = 1; !eof; i = i_next, i_next = (i_next + 1) % n_bufs) + { + if (b[i] == NULL) + b[i] = xmalloc (READ_BUFSIZE); + n_read = full_read (fd, b[i], READ_BUFSIZE); + if (n_read < READ_BUFSIZE) + { + if (errno != 0) + { + error (0, errno, _("error reading %s"), quote (filename)); + ok = false; + goto free_mem; + } + eof = true; + } + + if (i + 1 == n_bufs) + buffered_enough = true; + + if (buffered_enough) + { + if (fwrite (b[i_next], 1, n_read, stdout) < n_read) + { + error (0, errno, _("write error")); + ok = false; + goto free_mem; + } + } + } + + /* Output any remainder: rem bytes from b[i] + n_read. */ + if (rem) + { + if (buffered_enough) + { + size_t n_bytes_left_in_b_i = READ_BUFSIZE - n_read; + if (rem < n_bytes_left_in_b_i) + { + fwrite (b[i] + n_read, 1, rem, stdout); + } + else + { + fwrite (b[i] + n_read, 1, n_bytes_left_in_b_i, stdout); + fwrite (b[i_next], 1, rem - n_bytes_left_in_b_i, stdout); + } + } + else if (i + 1 == n_bufs) + { + /* This happens when n_elide < file_size < n_elide_round. + + |READ_BUF.| + | | rem | + |---------!---------!---------!---------| + |---- n_elide ---------| + | | x | + | |y | + |---- file size -----------| + | |n_read| + |---- n_elide_round ----------| + */ + size_t y = READ_BUFSIZE - rem; + size_t x = n_read - y; + fwrite (b[i_next], 1, x, stdout); + } + } + + free_mem:; + for (i = 0; i < n_bufs; i++) + free (b[i]); + free (b); + + return ok; + } +} + +/* Print all but the last N_ELIDE lines from the input available + via file descriptor FD. Return true upon success. + Give a diagnostic and return false upon error. */ + +/* NOTE: if the input file shrinks by more than N_ELIDE bytes between + the length determination and the actual reading, then head fails. */ + +static bool +elide_tail_bytes_file (const char *filename, int fd, uintmax_t n_elide) +{ + struct stat stats; + + if (presume_input_pipe || fstat (fd, &stats) || ! S_ISREG (stats.st_mode)) + { + return elide_tail_bytes_pipe (filename, fd, n_elide); + } + else + { + off_t current_pos, end_pos; + uintmax_t bytes_remaining; + off_t diff; + enum Copy_fd_status err; + + if ((current_pos = lseek (fd, (off_t) 0, SEEK_CUR)) == -1 + || (end_pos = lseek (fd, (off_t) 0, SEEK_END)) == -1) + { + error (0, errno, _("cannot lseek %s"), quote (filename)); + return false; + } + + /* Be careful here. The current position may actually be + beyond the end of the file. */ + bytes_remaining = (diff = end_pos - current_pos) < 0 ? 0 : diff; + + if (bytes_remaining <= n_elide) + return true; + + /* Seek back to `current' position, then copy the required + number of bytes from fd. */ + if (lseek (fd, (off_t) 0, current_pos) == -1) + { + error (0, errno, _("%s: cannot lseek back to original position"), + quote (filename)); + return false; + } + + err = copy_fd (fd, stdout, bytes_remaining - n_elide); + if (err == COPY_FD_OK) + return true; + + diagnose_copy_fd_failure (err, filename); + return false; + } +} + +/* Print all but the last N_ELIDE lines from the input stream + open for reading via file descriptor FD. + Buffer the specified number of lines as a linked list of LBUFFERs, + adding them as needed. Return true if successful. */ + +static bool +elide_tail_lines_pipe (const char *filename, int fd, uintmax_t n_elide) +{ + struct linebuffer + { + char buffer[BUFSIZ]; + size_t nbytes; + size_t nlines; + struct linebuffer *next; + }; + typedef struct linebuffer LBUFFER; + LBUFFER *first, *last, *tmp; + size_t total_lines = 0; /* Total number of newlines in all buffers. */ + bool ok = true; + size_t n_read; /* Size in bytes of most recent read */ + + first = last = xmalloc (sizeof (LBUFFER)); + first->nbytes = first->nlines = 0; + first->next = NULL; + tmp = xmalloc (sizeof (LBUFFER)); + + /* Always read into a fresh buffer. + Read, (producing no output) until we've accumulated at least + n_elide newlines, or until EOF, whichever comes first. */ + while (1) + { + n_read = safe_read (fd, tmp->buffer, BUFSIZ); + if (n_read == 0 || n_read == SAFE_READ_ERROR) + break; + tmp->nbytes = n_read; + tmp->nlines = 0; + tmp->next = NULL; + + /* Count the number of newlines just read. */ + { + char const *buffer_end = tmp->buffer + n_read; + char const *p = tmp->buffer; + while ((p = memchr (p, '\n', buffer_end - p))) + { + ++p; + ++tmp->nlines; + } + } + total_lines += tmp->nlines; + + /* If there is enough room in the last buffer read, just append the new + one to it. This is because when reading from a pipe, `n_read' can + often be very small. */ + if (tmp->nbytes + last->nbytes < BUFSIZ) + { + memcpy (&last->buffer[last->nbytes], tmp->buffer, tmp->nbytes); + last->nbytes += tmp->nbytes; + last->nlines += tmp->nlines; + } + else + { + /* If there's not enough room, link the new buffer onto the end of + the list, then either free up the oldest buffer for the next + read if that would leave enough lines, or else malloc a new one. + Some compaction mechanism is possible but probably not + worthwhile. */ + last = last->next = tmp; + if (n_elide < total_lines - first->nlines) + { + fwrite (first->buffer, 1, first->nbytes, stdout); + tmp = first; + total_lines -= first->nlines; + first = first->next; + } + else + tmp = xmalloc (sizeof (LBUFFER)); + } + } + + free (tmp); + + if (n_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (filename)); + ok = false; + goto free_lbuffers; + } + + /* If we read any bytes at all, count the incomplete line + on files that don't end with a newline. */ + if (last->nbytes && last->buffer[last->nbytes - 1] != '\n') + { + ++last->nlines; + ++total_lines; + } + + for (tmp = first; n_elide < total_lines - tmp->nlines; tmp = tmp->next) + { + fwrite (tmp->buffer, 1, tmp->nbytes, stdout); + total_lines -= tmp->nlines; + } + + /* Print the first `total_lines - n_elide' lines of tmp->buffer. */ + if (n_elide < total_lines) + { + size_t n = total_lines - n_elide; + char const *buffer_end = tmp->buffer + tmp->nbytes; + char const *p = tmp->buffer; + while (n && (p = memchr (p, '\n', buffer_end - p))) + { + ++p; + ++tmp->nlines; + --n; + } + fwrite (tmp->buffer, 1, p - tmp->buffer, stdout); + } + +free_lbuffers: + while (first) + { + tmp = first->next; + free (first); + first = tmp; + } + return ok; +} + +/* Output all but the last N_LINES lines of the input stream defined by + FD, START_POS, and END_POS. + START_POS is the starting position of the read pointer for the file + associated with FD (may be nonzero). + END_POS is the file offset of EOF (one larger than offset of last byte). + Return true upon success. + Give a diagnostic and return false upon error. + + NOTE: this code is very similar to that of tail.c's file_lines function. + Unfortunately, factoring out some common core looks like it'd result + in a less efficient implementation or a messy interface. */ +static bool +elide_tail_lines_seekable (const char *pretty_filename, int fd, + uintmax_t n_lines, + off_t start_pos, off_t end_pos) +{ + char buffer[BUFSIZ]; + size_t bytes_read; + off_t pos = end_pos; + + /* Set `bytes_read' to the size of the last, probably partial, buffer; + 0 < `bytes_read' <= `BUFSIZ'. */ + bytes_read = (pos - start_pos) % BUFSIZ; + if (bytes_read == 0) + bytes_read = BUFSIZ; + /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that all + reads will be on block boundaries, which might increase efficiency. */ + pos -= bytes_read; + if (lseek (fd, pos, SEEK_SET) < 0) + { + char offset_buf[INT_BUFSIZE_BOUND (off_t)]; + error (0, errno, _("%s: cannot seek to offset %s"), + pretty_filename, offtostr (pos, offset_buf)); + return false; + } + bytes_read = safe_read (fd, buffer, bytes_read); + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (pretty_filename)); + return false; + } + + /* Count the incomplete line on files that don't end with a newline. */ + if (bytes_read && buffer[bytes_read - 1] != '\n') + --n_lines; + + while (1) + { + /* Scan backward, counting the newlines in this bufferfull. */ + + size_t n = bytes_read; + while (n) + { + char const *nl; + nl = memrchr (buffer, '\n', n); + if (nl == NULL) + break; + n = nl - buffer; + if (n_lines-- == 0) + { + /* Found it. */ + /* If necessary, restore the file pointer and copy + input to output up to position, POS. */ + if (start_pos < pos) + { + enum Copy_fd_status err; + if (lseek (fd, start_pos, SEEK_SET) < 0) + { + /* Failed to reposition file pointer. */ + error (0, errno, + "%s: unable to restore file pointer to initial offset", + quote (pretty_filename)); + return false; + } + + err = copy_fd (fd, stdout, pos - start_pos); + if (err != COPY_FD_OK) + { + diagnose_copy_fd_failure (err, pretty_filename); + return false; + } + } + + /* Output the initial portion of the buffer + in which we found the desired newline byte. + Don't bother testing for failure for such a small amount. + Any failure will be detected upon close. */ + fwrite (buffer, 1, n + 1, stdout); + return true; + } + } + + /* Not enough newlines in that bufferfull. */ + if (pos == start_pos) + { + /* Not enough lines in the file. */ + return true; + } + pos -= BUFSIZ; + if (lseek (fd, pos, SEEK_SET) < 0) + { + char offset_buf[INT_BUFSIZE_BOUND (off_t)]; + error (0, errno, _("%s: cannot seek to offset %s"), + pretty_filename, offtostr (pos, offset_buf)); + return false; + } + + bytes_read = safe_read (fd, buffer, BUFSIZ); + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (pretty_filename)); + return false; + } + + /* FIXME: is this dead code? + Consider the test, pos == start_pos, above. */ + if (bytes_read == 0) + return true; + } +} + +/* Print all but the last N_ELIDE lines from the input available + via file descriptor FD. Return true upon success. + Give a diagnostic and return nonzero upon error. */ + +static bool +elide_tail_lines_file (const char *filename, int fd, uintmax_t n_elide) +{ + if (!presume_input_pipe) + { + /* Find the offset, OFF, of the Nth newline from the end, + but not counting the last byte of the file. + If found, write from current position to OFF, inclusive. + Otherwise, just return true. */ + + off_t start_pos = lseek (fd, (off_t) 0, SEEK_CUR); + off_t end_pos = lseek (fd, (off_t) 0, SEEK_END); + if (0 <= start_pos && start_pos < end_pos) + { + /* If the file is empty, we're done. */ + if (end_pos == 0) + return true; + + return elide_tail_lines_seekable (filename, fd, n_elide, + start_pos, end_pos); + } + + /* lseek failed or the end offset precedes start. + Fall through. */ + } + + return elide_tail_lines_pipe (filename, fd, n_elide); +} + +static bool +head_bytes (const char *filename, int fd, uintmax_t bytes_to_write) +{ + char buffer[BUFSIZ]; + size_t bytes_to_read = BUFSIZ; + + while (bytes_to_write) + { + size_t bytes_read; + if (bytes_to_write < bytes_to_read) + bytes_to_read = bytes_to_write; + bytes_read = safe_read (fd, buffer, bytes_to_read); + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (filename)); + return false; + } + if (bytes_read == 0) + break; + if (fwrite (buffer, 1, bytes_read, stdout) < bytes_read) + error (EXIT_FAILURE, errno, _("write error")); + bytes_to_write -= bytes_read; + } + return true; +} + +static bool +head_lines (const char *filename, int fd, uintmax_t lines_to_write) +{ + char buffer[BUFSIZ]; + + while (lines_to_write) + { + size_t bytes_read = safe_read (fd, buffer, BUFSIZ); + size_t bytes_to_write = 0; + + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (filename)); + return false; + } + if (bytes_read == 0) + break; + while (bytes_to_write < bytes_read) + if (buffer[bytes_to_write++] == '\n' && --lines_to_write == 0) + { + off_t n_bytes_past_EOL = bytes_read - bytes_to_write; + /* If we have read more data than that on the specified number + of lines, try to seek back to the position we would have + gotten to had we been reading one byte at a time. */ + if (lseek (fd, -n_bytes_past_EOL, SEEK_CUR) < 0) + { + int e = errno; + struct stat st; + if (fstat (fd, &st) != 0 || S_ISREG (st.st_mode)) + error (0, e, _("cannot reposition file pointer for %s"), + quote (filename)); + } + break; + } + if (fwrite (buffer, 1, bytes_to_write, stdout) < bytes_to_write) + error (EXIT_FAILURE, errno, _("write error")); + } + return true; +} + +static bool +head (const char *filename, int fd, uintmax_t n_units, bool count_lines, + bool elide_from_end) +{ + if (print_headers) + write_header (filename); + + if (elide_from_end) + { + if (count_lines) + { + return elide_tail_lines_file (filename, fd, n_units); + } + else + { + return elide_tail_bytes_file (filename, fd, n_units); + } + } + if (count_lines) + return head_lines (filename, fd, n_units); + else + return head_bytes (filename, fd, n_units); +} + +static bool +head_file (const char *filename, uintmax_t n_units, bool count_lines, + bool elide_from_end) +{ + int fd; + bool ok; + bool is_stdin = STREQ (filename, "-"); + + if (is_stdin) + { + have_read_stdin = true; + fd = STDIN_FILENO; + filename = _("standard input"); + if (O_BINARY && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + } + else + { + fd = open (filename, O_RDONLY | O_BINARY); + if (fd < 0) + { + error (0, errno, _("cannot open %s for reading"), quote (filename)); + return false; + } + } + + ok = head (filename, fd, n_units, count_lines, elide_from_end); + if (!is_stdin && close (fd) != 0) + { + error (0, errno, _("closing %s"), quote (filename)); + return false; + } + return ok; +} + +/* Convert a string of decimal digits, N_STRING, with a single, optional suffix + character (b, k, or m) to an integral value. Upon successful conversion, + return that value. If it cannot be converted, give a diagnostic and exit. + COUNT_LINES indicates whether N_STRING is a number of bytes or a number + of lines. It is used solely to give a more specific diagnostic. */ + +static uintmax_t +string_to_integer (bool count_lines, const char *n_string) +{ + strtol_error s_err; + uintmax_t n; + + s_err = xstrtoumax (n_string, NULL, 10, &n, "bkm"); + + if (s_err == LONGINT_OVERFLOW) + { + error (EXIT_FAILURE, 0, + _("%s: %s is so large that it is not representable"), n_string, + count_lines ? _("number of lines") : _("number of bytes")); + } + + if (s_err != LONGINT_OK) + { + error (EXIT_FAILURE, 0, "%s: %s", n_string, + (count_lines + ? _("invalid number of lines") + : _("invalid number of bytes"))); + } + + return n; +} + +int +main (int argc, char **argv) +{ + enum header_mode header_mode = multiple_files; + bool ok = true; + int c; + size_t i; + + /* Number of items to print. */ + uintmax_t n_units = DEFAULT_NUMBER; + + /* If true, interpret the numeric argument as the number of lines. + Otherwise, interpret it as the number of bytes. */ + bool count_lines = true; + + /* Elide the specified number of lines or bytes, counting from + the end of the file. */ + bool elide_from_end = false; + + /* Initializer for file_list if no file-arguments + were specified on the command line. */ + static char const *const default_file_list[] = {"-", NULL}; + char const *const *file_list; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + have_read_stdin = false; + + print_headers = false; + + if (1 < argc && argv[1][0] == '-' && ISDIGIT (argv[1][1])) + { + char *a = argv[1]; + char *n_string = ++a; + char *end_n_string; + char multiplier_char = 0; + + /* Old option syntax; a dash, one or more digits, and one or + more option letters. Move past the number. */ + do ++a; + while (ISDIGIT (*a)); + + /* Pointer to the byte after the last digit. */ + end_n_string = a; + + /* Parse any appended option letters. */ + for (; *a; a++) + { + switch (*a) + { + case 'c': + count_lines = false; + multiplier_char = 0; + break; + + case 'b': + case 'k': + case 'm': + count_lines = false; + multiplier_char = *a; + break; + + case 'l': + count_lines = true; + break; + + case 'q': + header_mode = never; + break; + + case 'v': + header_mode = always; + break; + + default: + error (0, 0, _("invalid trailing option -- %c"), *a); + usage (EXIT_FAILURE); + } + } + + /* Append the multiplier character (if any) onto the end of + the digit string. Then add NUL byte if necessary. */ + *end_n_string = multiplier_char; + if (multiplier_char) + *(++end_n_string) = 0; + + n_units = string_to_integer (count_lines, n_string); + + /* Make the options we just parsed invisible to getopt. */ + argv[1] = argv[0]; + argv++; + argc--; + } + + while ((c = getopt_long (argc, argv, "c:n:qv0123456789", long_options, NULL)) + != -1) + { + switch (c) + { + case PRESUME_INPUT_PIPE_OPTION: + presume_input_pipe = true; + break; + + case 'c': + count_lines = false; + elide_from_end = (*optarg == '-'); + if (elide_from_end) + ++optarg; + n_units = string_to_integer (count_lines, optarg); + break; + + case 'n': + count_lines = true; + elide_from_end = (*optarg == '-'); + if (elide_from_end) + ++optarg; + n_units = string_to_integer (count_lines, optarg); + break; + + case 'q': + header_mode = never; + break; + + case 'v': + header_mode = always; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + if (ISDIGIT (c)) + error (0, 0, _("invalid trailing option -- %c"), c); + usage (EXIT_FAILURE); + } + } + + if (header_mode == always + || (header_mode == multiple_files && optind < argc - 1)) + print_headers = true; + + if ( ! count_lines && elide_from_end && OFF_T_MAX < n_units) + { + char umax_buf[INT_BUFSIZE_BOUND (uintmax_t)]; + error (EXIT_FAILURE, 0, _("%s: number of bytes is too large"), + umaxtostr (n_units, umax_buf)); + } + + file_list = (optind < argc + ? (char const *const *) &argv[optind] + : default_file_list); + + if (O_BINARY && ! isatty (STDOUT_FILENO)) + freopen (NULL, "wb", stdout); + + for (i = 0; file_list[i]; ++i) + ok &= head_file (file_list[i], n_units, count_lines, elide_from_end); + + if (have_read_stdin && close (STDIN_FILENO) < 0) + error (EXIT_FAILURE, errno, "-"); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/hostid.c b/src/hostid.c new file mode 100644 index 0000000..090b8a0 --- /dev/null +++ b/src/hostid.c @@ -0,0 +1,96 @@ +/* print the hexadecimal identifier for the current host + + Copyright (C) 1997, 1999, 2000, 2001, 2002, 2003, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Jim Meyering. */ + +#include +#include +#include +#include + +#include "system.h" +#include "long-options.h" +#include "error.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "hostid" + +#define AUTHORS "Jim Meyering" + +/* The name this program was run with, for error messages. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s\n\ + or: %s OPTION\n\ +Print the numeric identifier (in hexadecimal) for the current host.\n\ +\n\ +"), + program_name, program_name); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + unsigned int id; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (optind < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind])); + usage (EXIT_FAILURE); + } + + id = gethostid (); + + /* POSIX says gethostid returns a "32-bit identifier" but is silent + whether it's sign-extended. Turn off any sign-extension. This + is a no-op unless unsigned int is wider than 32 bits. */ + id &= 0xffffffff; + + printf ("%08x\n", id); + + exit (EXIT_SUCCESS); +} diff --git a/src/hostname.c b/src/hostname.c new file mode 100644 index 0000000..f6748a6 --- /dev/null +++ b/src/hostname.c @@ -0,0 +1,125 @@ +/* hostname - set or print the name of current host system + Copyright (C) 1994-1997, 1999-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Jim Meyering. */ + +#include +#include +#include +#include + +#include "system.h" +#include "long-options.h" +#include "error.h" +#include "quote.h" +#include "xgethostname.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "hostname" + +#define AUTHORS "Jim Meyering" + +#if HAVE_SETHOSTNAME && !defined sethostname +int sethostname (); +#endif + +#if !defined HAVE_SETHOSTNAME && defined HAVE_SYSINFO && \ + defined HAVE_SYS_SYSTEMINFO_H +# include + +int +sethostname (char *name, size_t namelen) +{ + /* Using sysinfo() is the SVR4 mechanism to set a hostname. */ + return (sysinfo (SI_SET_HOSTNAME, name, namelen) < 0 ? -1 : 0); +} + +# define HAVE_SETHOSTNAME 1 /* Now we have it... */ +#endif + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [NAME]\n\ + or: %s OPTION\n\ +Print or set the hostname of the current system.\n\ +\n\ +"), + program_name, program_name); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + char *hostname; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (argc == optind + 1) + { +#ifdef HAVE_SETHOSTNAME + /* Set hostname to operand. */ + char const *name = argv[optind]; + if (sethostname (name, strlen (name)) != 0) + error (EXIT_FAILURE, errno, _("cannot set name to %s"), quote (name)); +#else + error (EXIT_FAILURE, 0, + _("cannot set hostname; this system lacks the functionality")); +#endif + } + + if (argc <= optind) + { + hostname = xgethostname (); + if (hostname == NULL) + error (EXIT_FAILURE, errno, _("cannot determine hostname")); + printf ("%s\n", hostname); + } + + if (optind + 1 < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); + usage (EXIT_FAILURE); + } + + exit (EXIT_SUCCESS); +} diff --git a/src/id.c b/src/id.c new file mode 100644 index 0000000..7abb3e5 --- /dev/null +++ b/src/id.c @@ -0,0 +1,388 @@ +/* id -- print real and effective UIDs and GIDs + Copyright (C) 1989-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Arnold Robbins. + Major rewrite by David MacKenzie, djm@gnu.ai.mit.edu. */ + +#include +#include +#include +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "id" + +#define AUTHORS "Arnold Robbins", "David MacKenzie" + +int getugroups (); + +static void print_user (uid_t uid); +static void print_group (gid_t gid); +static void print_group_list (const char *username); +static void print_full_info (const char *username); + +/* The name this program was run with. */ +char *program_name; + +/* If true, output user/group name instead of ID number. -n */ +static bool use_name = false; + +/* The real and effective IDs of the user to print. */ +static uid_t ruid, euid; +static gid_t rgid, egid; + +/* True unless errors have been encountered. */ +static bool ok = true; + +static struct option const longopts[] = +{ + {"group", no_argument, NULL, 'g'}, + {"groups", no_argument, NULL, 'G'}, + {"name", no_argument, NULL, 'n'}, + {"real", no_argument, NULL, 'r'}, + {"user", no_argument, NULL, 'u'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... [USERNAME]\n"), program_name); + fputs (_("\ +Print information for USERNAME, or the current user.\n\ +\n\ + -a ignore, for compatibility with other versions\n\ + -g, --group print only the effective group ID\n\ + -G, --groups print all group IDs\n\ + -n, --name print a name instead of a number, for -ugG\n\ + -r, --real print the real ID instead of the effective ID, with -ugG\n\ + -u, --user print only the effective user ID\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Without any OPTION, print some useful set of identified information.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int optc; + + /* If true, output the list of all group IDs. -G */ + bool just_group_list = false; + /* If true, output only the group ID(s). -g */ + bool just_group = false; + /* If true, output real UID/GID instead of default effective UID/GID. -r */ + bool use_real = false; + /* If true, output only the user ID(s). -u */ + bool just_user = false; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "agnruG", longopts, NULL)) != -1) + { + switch (optc) + { + case 'a': + /* Ignore -a, for compatibility with SVR4. */ + break; + case 'g': + just_group = true; + break; + case 'n': + use_name = true; + break; + case 'r': + use_real = true; + break; + case 'u': + just_user = true; + break; + case 'G': + just_group_list = true; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + if (just_user + just_group + just_group_list > 1) + error (EXIT_FAILURE, 0, _("cannot print only user and only group")); + + if (just_user + just_group + just_group_list == 0 && (use_real | use_name)) + error (EXIT_FAILURE, 0, + _("cannot print only names or real IDs in default format")); + + if (argc - optind > 1) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); + usage (EXIT_FAILURE); + } + + if (argc - optind == 1) + { + struct passwd *pwd = getpwnam (argv[optind]); + if (pwd == NULL) + error (EXIT_FAILURE, 0, _("%s: No such user"), argv[optind]); + ruid = euid = pwd->pw_uid; + rgid = egid = pwd->pw_gid; + } + else + { + euid = geteuid (); + ruid = getuid (); + egid = getegid (); + rgid = getgid (); + } + + if (just_user) + print_user (use_real ? ruid : euid); + else if (just_group) + print_group (use_real ? rgid : egid); + else if (just_group_list) + print_group_list (argv[optind]); + else + print_full_info (argv[optind]); + putchar ('\n'); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} + +/* Print the name or value of user ID UID. */ + +static void +print_user (uid_t uid) +{ + struct passwd *pwd = NULL; + + if (use_name) + { + pwd = getpwuid (uid); + if (pwd == NULL) + { + error (0, 0, _("cannot find name for user ID %lu"), + (unsigned long int) uid); + ok = false; + } + } + + if (pwd == NULL) + printf ("%lu", (unsigned long int) uid); + else + printf ("%s", pwd->pw_name); +} + +/* Print the name or value of group ID GID. */ + +static void +print_group (gid_t gid) +{ + struct group *grp = NULL; + + if (use_name) + { + grp = getgrgid (gid); + if (grp == NULL) + { + error (0, 0, _("cannot find name for group ID %lu"), + (unsigned long int) gid); + ok = false; + } + } + + if (grp == NULL) + printf ("%lu", (unsigned long int) gid); + else + printf ("%s", grp->gr_name); +} + +#if HAVE_GETGROUPS + +/* FIXME: document */ + +static bool +xgetgroups (const char *username, gid_t gid, int *n_groups, + GETGROUPS_T **groups) +{ + int max_n_groups; + int ng; + GETGROUPS_T *g = NULL; + + if (!username) + max_n_groups = getgroups (0, NULL); + else + max_n_groups = getugroups (0, NULL, username, gid); + + if (max_n_groups < 0) + ng = -1; + else + { + g = xnmalloc (max_n_groups, sizeof *g); + if (!username) + ng = getgroups (max_n_groups, g); + else + ng = getugroups (max_n_groups, g, username, gid); + } + + if (ng < 0) + { + error (0, errno, _("cannot get supplemental group list")); + free (g); + return false; + } + else + { + *n_groups = ng; + *groups = g; + return true; + } +} + +#endif /* HAVE_GETGROUPS */ + +/* Print all of the distinct groups the user is in. */ + +static void +print_group_list (const char *username) +{ + struct passwd *pwd; + + pwd = getpwuid (ruid); + if (pwd == NULL) + ok = false; + + print_group (rgid); + if (egid != rgid) + { + putchar (' '); + print_group (egid); + } + +#if HAVE_GETGROUPS + { + int n_groups; + GETGROUPS_T *groups; + int i; + + if (! xgetgroups (username, (pwd ? pwd->pw_gid : (gid_t) -1), + &n_groups, &groups)) + { + ok = false; + return; + } + + for (i = 0; i < n_groups; i++) + if (groups[i] != rgid && groups[i] != egid) + { + putchar (' '); + print_group (groups[i]); + } + free (groups); + } +#endif /* HAVE_GETGROUPS */ +} + +/* Print all of the info about the user's user and group IDs. */ + +static void +print_full_info (const char *username) +{ + struct passwd *pwd; + struct group *grp; + + printf ("uid=%lu", (unsigned long int) ruid); + pwd = getpwuid (ruid); + if (pwd) + printf ("(%s)", pwd->pw_name); + + printf (" gid=%lu", (unsigned long int) rgid); + grp = getgrgid (rgid); + if (grp) + printf ("(%s)", grp->gr_name); + + if (euid != ruid) + { + printf (" euid=%lu", (unsigned long int) euid); + pwd = getpwuid (euid); + if (pwd) + printf ("(%s)", pwd->pw_name); + } + + if (egid != rgid) + { + printf (" egid=%lu", (unsigned long int) egid); + grp = getgrgid (egid); + if (grp) + printf ("(%s)", grp->gr_name); + } + +#if HAVE_GETGROUPS + { + int n_groups; + GETGROUPS_T *groups; + int i; + + if (! xgetgroups (username, (pwd ? pwd->pw_gid : (gid_t) -1), + &n_groups, &groups)) + { + ok = false; + return; + } + + if (n_groups > 0) + fputs (_(" groups="), stdout); + for (i = 0; i < n_groups; i++) + { + if (i > 0) + putchar (','); + printf ("%lu", (unsigned long int) groups[i]); + grp = getgrgid (groups[i]); + if (grp) + printf ("(%s)", grp->gr_name); + } + free (groups); + } +#endif /* HAVE_GETGROUPS */ +} diff --git a/src/install.c b/src/install.c new file mode 100644 index 0000000..0457751 --- /dev/null +++ b/src/install.c @@ -0,0 +1,708 @@ +/* install - copy files and set attributes + Copyright (C) 89, 90, 91, 1995-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by David MacKenzie */ + +#include +#include +#include +#include +#include +#include +#include + +#include "system.h" +#include "backupfile.h" +#include "error.h" +#include "cp-hash.h" +#include "copy.h" +#include "filenamecat.h" +#include "mkancesdirs.h" +#include "mkdir-p.h" +#include "modechange.h" +#include "quote.h" +#include "savewd.h" +#include "stat-time.h" +#include "utimens.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "install" + +#define AUTHORS "David MacKenzie" + +#if HAVE_SYS_WAIT_H +# include +#endif + +#if ! HAVE_ENDGRENT +# define endgrent() ((void) 0) +#endif + +#if ! HAVE_ENDPWENT +# define endpwent() ((void) 0) +#endif + +/* 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 + +/* Number of bytes of a file to copy at a time. */ +#define READ_SIZE (32 * 1024) + +static bool change_timestamps (struct stat const *from_sb, char const *to); +static bool change_attributes (char const *name); +static bool copy_file (const char *from, const char *to, + const struct cp_options *x); +static bool install_file_in_file_parents (char const *from, char *to, + struct cp_options *x); +static bool install_file_in_dir (const char *from, const char *to_dir, + const struct cp_options *x); +static bool install_file_in_file (const char *from, const char *to, + const struct cp_options *x); +static void get_ids (void); +static void strip (char const *name); +static void announce_mkdir (char const *dir, void *options); +static int make_ancestor (char const *dir, char const *component, + void *options); +void usage (int status); + +/* 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. */ +static char *owner_name; + +/* The user ID corresponding to `owner_name'. */ +static uid_t owner_id; + +/* The group name that will own the files, or NULL to make the group + the current group ID. */ +static char *group_name; + +/* The group ID corresponding to `group_name'. */ +static gid_t group_id; + +#define DEFAULT_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) + +/* The file mode bits to which non-directory files will be set. The umask has + no effect. */ +static mode_t mode = DEFAULT_MODE; + +/* Similar, but for directories. */ +static mode_t dir_mode = DEFAULT_MODE; + +/* The file mode bits that the user cares about. This should be a + superset of DIR_MODE and a subset of CHMOD_MODE_BITS. This matters + for directories, since otherwise directories may keep their S_ISUID + or S_ISGID bits. */ +static mode_t dir_mode_bits = CHMOD_MODE_BITS; + +/* If true, strip executable files after copying them. */ +static bool strip_files; + +/* If true, install a directory instead of a regular file. */ +static bool dir_arg; + +static struct option const long_options[] = +{ + {"backup", optional_argument, NULL, 'b'}, + {"directory", no_argument, NULL, 'd'}, + {"group", required_argument, NULL, 'g'}, + {"mode", required_argument, NULL, 'm'}, + {"no-target-directory", no_argument, NULL, 'T'}, + {"owner", required_argument, NULL, 'o'}, + {"preserve-timestamps", no_argument, NULL, 'p'}, + {"strip", no_argument, NULL, 's'}, + {"suffix", required_argument, NULL, 'S'}, + {"target-directory", required_argument, NULL, 't'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +static void +cp_option_init (struct cp_options *x) +{ + x->copy_as_regular = true; + x->dereference = DEREF_ALWAYS; + x->unlink_dest_before_opening = true; + x->unlink_dest_after_failed_open = false; + x->hard_link = false; + x->interactive = I_UNSPECIFIED; + x->move_mode = false; + x->chown_privileges = chown_privileges (); + x->one_file_system = false; + x->preserve_ownership = false; + x->preserve_links = false; + x->preserve_mode = false; + x->preserve_timestamps = false; + x->require_preserve = false; + x->recursive = false; + x->sparse_mode = SPARSE_AUTO; + x->symbolic_link = false; + x->backup_type = no_backups; + + /* Create destination files initially writable so we can run strip on them. + Although GNU strip works fine on read-only files, some others + would fail. */ + x->set_mode = true; + x->mode = S_IRUSR | S_IWUSR; + x->stdin_tty = false; + + x->update = false; + x->verbose = false; + x->dest_info = NULL; + x->src_info = NULL; +} + +/* FILE is the last operand of this command. Return true if FILE is a + directory. But report an error there is a problem accessing FILE, + or if FILE does not exist but would have to refer to an existing + directory if it referred to anything at all. */ + +static bool +target_directory_operand (char const *file) +{ + char const *b = last_component (file); + size_t blen = strlen (b); + bool looks_like_a_dir = (blen == 0 || ISSLASH (b[blen - 1])); + struct stat st; + int err = (stat (file, &st) == 0 ? 0 : errno); + bool is_a_dir = !err && S_ISDIR (st.st_mode); + if (err && err != ENOENT) + error (EXIT_FAILURE, err, _("accessing %s"), quote (file)); + if (is_a_dir < looks_like_a_dir) + error (EXIT_FAILURE, err, _("target %s is not a directory"), quote (file)); + return is_a_dir; +} + +/* Process a command-line file name, for the -d option. */ +static int +process_dir (char *dir, struct savewd *wd, void *options) +{ + return (make_dir_parents (dir, wd, + make_ancestor, options, + dir_mode, announce_mkdir, + dir_mode_bits, owner_id, group_id, false) + ? EXIT_SUCCESS + : EXIT_FAILURE); +} + +int +main (int argc, char **argv) +{ + int optc; + int exit_status = EXIT_SUCCESS; + const char *specified_mode = NULL; + bool make_backups = false; + char *backup_suffix_string; + char *version_control_string = NULL; + bool mkdir_and_install = false; + struct cp_options x; + char const *target_directory = NULL; + bool no_target_directory = false; + int n_files; + char **file; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + cp_option_init (&x); + + owner_name = NULL; + group_name = NULL; + strip_files = false; + dir_arg = false; + umask (0); + + /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless + we'll actually use backup_suffix_string. */ + backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); + + while ((optc = getopt_long (argc, argv, "bcsDdg:m:o:pt:TvS:", long_options, + NULL)) != -1) + { + switch (optc) + { + case 'b': + make_backups = true; + if (optarg) + version_control_string = optarg; + break; + case 'c': + break; + case 's': + strip_files = true; +#ifdef SIGCHLD + /* System V fork+wait does not work if SIGCHLD is ignored. */ + signal (SIGCHLD, SIG_DFL); +#endif + break; + case 'd': + dir_arg = true; + break; + case 'D': + mkdir_and_install = true; + break; + case 'v': + x.verbose = true; + break; + case 'g': + group_name = optarg; + break; + case 'm': + specified_mode = optarg; + break; + case 'o': + owner_name = optarg; + break; + case 'p': + x.preserve_timestamps = true; + break; + case 'S': + make_backups = true; + backup_suffix_string = optarg; + break; + case 't': + if (target_directory) + error (EXIT_FAILURE, 0, + _("multiple target directories specified")); + else + { + struct stat st; + if (stat (optarg, &st) != 0) + error (EXIT_FAILURE, errno, _("accessing %s"), quote (optarg)); + if (! S_ISDIR (st.st_mode)) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (optarg)); + } + target_directory = optarg; + break; + case 'T': + no_target_directory = true; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + /* Check for invalid combinations of arguments. */ + if (dir_arg & strip_files) + error (EXIT_FAILURE, 0, + _("the strip option may not be used when installing a directory")); + if (dir_arg && target_directory) + error (EXIT_FAILURE, 0, + _("target directory not allowed when installing a directory")); + + if (backup_suffix_string) + simple_backup_suffix = xstrdup (backup_suffix_string); + + x.backup_type = (make_backups + ? xget_version (_("backup type"), + version_control_string) + : no_backups); + + n_files = argc - optind; + file = argv + optind; + + if (n_files <= ! (dir_arg || target_directory)) + { + if (n_files <= 0) + error (0, 0, _("missing file operand")); + else + error (0, 0, _("missing destination file operand after %s"), + quote (file[0])); + usage (EXIT_FAILURE); + } + + if (no_target_directory) + { + if (target_directory) + error (EXIT_FAILURE, 0, + _("Cannot combine --target-directory (-t) " + "and --no-target-directory (-T)")); + if (2 < n_files) + { + error (0, 0, _("extra operand %s"), quote (file[2])); + usage (EXIT_FAILURE); + } + } + else if (! (dir_arg || target_directory)) + { + if (2 <= n_files && target_directory_operand (file[n_files - 1])) + target_directory = file[--n_files]; + else if (2 < n_files) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (file[n_files - 1])); + } + + if (specified_mode) + { + struct mode_change *change = mode_compile (specified_mode); + if (!change) + error (EXIT_FAILURE, 0, _("invalid mode %s"), quote (specified_mode)); + mode = mode_adjust (0, false, 0, change, NULL); + dir_mode = mode_adjust (0, true, 0, change, &dir_mode_bits); + free (change); + } + + get_ids (); + + if (dir_arg) + exit_status = savewd_process_files (n_files, file, process_dir, &x); + else + { + /* FIXME: it's a little gross that this initialization is + required by copy.c::copy. */ + hash_init (); + + if (!target_directory) + { + if (! (mkdir_and_install + ? install_file_in_file_parents (file[0], file[1], &x) + : install_file_in_file (file[0], file[1], &x))) + exit_status = EXIT_FAILURE; + } + else + { + int i; + dest_info_init (&x); + for (i = 0; i < n_files; i++) + if (! install_file_in_dir (file[i], target_directory, &x)) + exit_status = EXIT_FAILURE; + } + } + + exit (exit_status); +} + +/* Copy file FROM onto file TO, creating any missing parent directories of TO. + Return true if successful. */ + +static bool +install_file_in_file_parents (char const *from, char *to, + struct cp_options *x) +{ + bool save_working_directory = + ! (IS_ABSOLUTE_FILE_NAME (from) && IS_ABSOLUTE_FILE_NAME (to)); + int status = EXIT_SUCCESS; + + struct savewd wd; + savewd_init (&wd); + if (! save_working_directory) + savewd_finish (&wd); + + if (mkancesdirs (to, &wd, make_ancestor, x) == -1) + { + error (0, errno, _("cannot create directory %s"), to); + status = EXIT_FAILURE; + } + + if (save_working_directory) + { + int restore_result = savewd_restore (&wd, status); + int restore_errno = errno; + savewd_finish (&wd); + if (EXIT_SUCCESS < restore_result) + return false; + if (restore_result < 0 && status == EXIT_SUCCESS) + { + error (0, restore_errno, _("cannot create directory %s"), to); + return false; + } + } + + return (status == EXIT_SUCCESS && install_file_in_file (from, to, x)); +} + +/* Copy file FROM onto file TO and give TO the appropriate + attributes. + Return true if successful. */ + +static bool +install_file_in_file (const char *from, const char *to, + const struct cp_options *x) +{ + struct stat from_sb; + if (x->preserve_timestamps && stat (from, &from_sb) != 0) + { + error (0, errno, _("cannot stat %s"), quote (from)); + return false; + } + if (! copy_file (from, to, x)) + return false; + if (strip_files) + strip (to); + if (x->preserve_timestamps && (strip_files || ! S_ISREG (from_sb.st_mode)) + && ! change_timestamps (&from_sb, to)) + return false; + return change_attributes (to); +} + +/* Copy file FROM into directory TO_DIR, keeping its same name, + and give the copy the appropriate attributes. + Return true if successful. */ + +static bool +install_file_in_dir (const char *from, const char *to_dir, + const struct cp_options *x) +{ + const char *from_base = last_component (from); + char *to = file_name_concat (to_dir, from_base, NULL); + bool ret = install_file_in_file (from, to, x); + free (to); + return ret; +} + +/* Copy file FROM onto file TO, creating TO if necessary. + Return true if successful. */ + +static bool +copy_file (const char *from, const char *to, const struct cp_options *x) +{ + bool copy_into_self; + + /* Allow installing from non-regular files like /dev/null. + Charles Karney reported that some Sun version of install allows that + and that sendmail's installation process relies on the behavior. + However, since !x->recursive, the call to "copy" will fail if FROM + is a directory. */ + + return copy (from, to, false, x, ©_into_self, NULL); +} + +/* Set the attributes of file or directory NAME. + Return true if successful. */ + +static bool +change_attributes (char const *name) +{ + /* 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 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. */ + + if (! (owner_id == (uid_t) -1 && group_id == (gid_t) -1) + && chown (name, owner_id, group_id) != 0) + error (0, errno, _("cannot change ownership of %s"), quote (name)); + else if (chmod (name, mode) != 0) + error (0, errno, _("cannot change permissions of %s"), quote (name)); + else + return true; + + return false; +} + +/* Set the timestamps of file TO to match those of file FROM. + Return true if successful. */ + +static bool +change_timestamps (struct stat const *from_sb, char const *to) +{ + struct timespec timespec[2]; + timespec[0] = get_stat_atime (from_sb); + timespec[1] = get_stat_mtime (from_sb); + + if (utimens (to, timespec)) + { + error (0, errno, _("cannot set time stamps for %s"), quote (to)); + return false; + } + return true; +} + +/* Strip the symbol table from the file NAME. + 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. */ + +static void +strip (char const *name) +{ + int status; + pid_t pid = fork (); + + switch (pid) + { + case -1: + error (EXIT_FAILURE, errno, _("fork system call failed")); + break; + case 0: /* Child. */ + execlp ("strip", "strip", name, NULL); + error (EXIT_FAILURE, errno, _("cannot run strip")); + break; + default: /* Parent. */ + if (waitpid (pid, &status, 0) < 0) + error (EXIT_FAILURE, errno, _("waiting for strip")); + else if (! WIFEXITED (status) || WEXITSTATUS (status)) + error (EXIT_FAILURE, 0, _("strip process terminated abnormally")); + break; + } +} + +/* Initialize the user and group ownership of the files to install. */ + +static void +get_ids (void) +{ + struct passwd *pw; + struct group *gr; + + if (owner_name) + { + pw = getpwnam (owner_name); + if (pw == NULL) + { + unsigned long int tmp; + if (xstrtoul (owner_name, NULL, 0, &tmp, NULL) != LONGINT_OK + || UID_T_MAX < tmp) + error (EXIT_FAILURE, 0, _("invalid user %s"), quote (owner_name)); + owner_id = tmp; + } + else + owner_id = pw->pw_uid; + endpwent (); + } + else + owner_id = (uid_t) -1; + + if (group_name) + { + gr = getgrnam (group_name); + if (gr == NULL) + { + unsigned long int tmp; + if (xstrtoul (group_name, NULL, 0, &tmp, NULL) != LONGINT_OK + || GID_T_MAX < tmp) + error (EXIT_FAILURE, 0, _("invalid group %s"), quote (group_name)); + group_id = tmp; + } + else + group_id = gr->gr_gid; + endgrent (); + } + else + group_id = (gid_t) -1; +} + +/* Report that directory DIR was made, if OPTIONS requests this. */ +static void +announce_mkdir (char const *dir, void *options) +{ + struct cp_options const *x = options; + if (x->verbose) + error (0, 0, _("creating directory %s"), quote (dir)); +} + +/* Make ancestor directory DIR, whose last file name component is + COMPONENT, with options OPTIONS. Assume the working directory is + COMPONENT's parent. */ +static int +make_ancestor (char const *dir, char const *component, void *options) +{ + int r = mkdir (component, DEFAULT_MODE); + if (r == 0) + announce_mkdir (dir, options); + return r; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [-T] SOURCE DEST\n\ + or: %s [OPTION]... SOURCE... DIRECTORY\n\ + or: %s [OPTION]... -t DIRECTORY SOURCE...\n\ + or: %s [OPTION]... -d DIRECTORY...\n\ +"), + program_name, program_name, program_name, program_name); + fputs (_("\ +In the first three forms, copy SOURCE to DEST or multiple SOURCE(s) to\n\ +the existing DIRECTORY, while setting permission modes and owner/group.\n\ +In the 4th form, create all components of the given DIRECTORY(ies).\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + --backup[=CONTROL] make a backup of each existing destination file\n\ + -b like --backup but does not accept an argument\n\ + -c (ignored)\n\ + -d, --directory treat all arguments as directory names; create all\n\ + components of the specified directories\n\ +"), stdout); + fputs (_("\ + -D create all leading components of DEST except the last,\n\ + then copy SOURCE to DEST\n\ + -g, --group=GROUP set group ownership, instead of process' current group\n\ + -m, --mode=MODE set permission mode (as in chmod), instead of rwxr-xr-x\n\ + -o, --owner=OWNER set ownership (super-user only)\n\ +"), stdout); + fputs (_("\ + -p, --preserve-timestamps apply access/modification times of SOURCE files\n\ + to corresponding destination files\n\ + -s, --strip strip symbol tables\n\ + -S, --suffix=SUFFIX override the usual backup suffix\n\ + -t, --target-directory=DIRECTORY copy all SOURCE arguments into DIRECTORY\n\ + -T, --no-target-directory treat DEST as a normal file\n\ + -v, --verbose print the name of each directory as it is created\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ +The version control method may be selected via the --backup option or through\n\ +the VERSION_CONTROL environment variable. Here are the values:\n\ +\n\ +"), stdout); + fputs (_("\ + none, off never make backups (even if --backup is given)\n\ + numbered, t make numbered backups\n\ + existing, nil numbered if numbered backups exist, simple otherwise\n\ + simple, never always make simple backups\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} diff --git a/src/join.c b/src/join.c new file mode 100644 index 0000000..b113c54 --- /dev/null +++ b/src/join.c @@ -0,0 +1,940 @@ +/* join - join lines of two files on a common field + Copyright (C) 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Written by Mike Haertel, mike@gnu.ai.mit.edu. */ + +#include + +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "hard-locale.h" +#include "linebuffer.h" +#include "memcasecmp.h" +#include "quote.h" +#include "stdio--.h" +#include "xmemcoll.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "join" + +#define AUTHORS "Mike Haertel" + +#define join system_join + +/* An element of the list identifying which fields to print for each + output line. */ +struct outlist + { + /* File number: 0, 1, or 2. 0 means use the join field. + 1 means use the first file argument, 2 the second. */ + int file; + + /* Field index (zero-based), specified only when FILE is 1 or 2. */ + size_t field; + + struct outlist *next; + }; + +/* A field of a line. */ +struct field + { + char *beg; /* First character in field. */ + size_t len; /* The length of the field. */ + }; + +/* A line read from an input file. */ +struct line + { + struct linebuffer buf; /* The line itself. */ + size_t nfields; /* Number of elements in `fields'. */ + size_t nfields_allocated; /* Number of elements allocated for `fields'. */ + struct field *fields; + }; + +/* One or more consecutive lines read from a file that all have the + same join field value. */ +struct seq + { + size_t count; /* Elements used in `lines'. */ + size_t alloc; /* Elements allocated in `lines'. */ + struct line *lines; + }; + +/* The name this program was run with. */ +char *program_name; + +/* True if the LC_COLLATE locale is hard. */ +static bool hard_LC_COLLATE; + +/* If nonzero, print unpairable lines in file 1 or 2. */ +static bool print_unpairables_1, print_unpairables_2; + +/* If nonzero, print pairable lines. */ +static bool print_pairables; + +/* Empty output field filler. */ +static char const *empty_filler; + +/* Field to join on; SIZE_MAX means they haven't been determined yet. */ +static size_t join_field_1 = SIZE_MAX; +static size_t join_field_2 = SIZE_MAX; + +/* List of fields to print. */ +static struct outlist outlist_head; + +/* Last element in `outlist', where a new element can be added. */ +static struct outlist *outlist_end = &outlist_head; + +/* Tab character separating fields. If negative, fields are separated + by any nonempty string of blanks, otherwise by exactly one + tab character whose value (when cast to unsigned char) equals TAB. */ +static int tab = -1; + +static struct option const longopts[] = +{ + {"ignore-case", no_argument, NULL, 'i'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Used to print non-joining lines */ +static struct line uni_blank; + +/* If nonzero, ignore case when comparing join fields. */ +static bool ignore_case; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... FILE1 FILE2\n\ +"), + program_name); + fputs (_("\ +For each pair of input lines with identical join fields, write a line to\n\ +standard output. The default join field is the first, delimited\n\ +by whitespace. When FILE1 or FILE2 (not both) is -, read standard input.\n\ +\n\ + -a FILENUM print unpairable lines coming from file FILENUM, where\n\ + FILENUM is 1 or 2, corresponding to FILE1 or FILE2\n\ + -e EMPTY replace missing input fields with EMPTY\n\ +"), stdout); + fputs (_("\ + -i, --ignore-case ignore differences in case when comparing fields\n\ + -j FIELD equivalent to `-1 FIELD -2 FIELD'\n\ + -o FORMAT obey FORMAT while constructing output line\n\ + -t CHAR use CHAR as input and output field separator\n\ +"), stdout); + fputs (_("\ + -v FILENUM like -a FILENUM, but suppress joined output lines\n\ + -1 FIELD join on this FIELD of file 1\n\ + -2 FIELD join on this FIELD of file 2\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Unless -t CHAR is given, leading blanks separate fields and are ignored,\n\ +else fields are separated by CHAR. Any FIELD is a field number counted\n\ +from 1. FORMAT is one or more comma or blank separated specifications,\n\ +each being `FILENUM.FIELD' or `0'. Default FORMAT outputs the join field,\n\ +the remaining fields from FILE1, the remaining fields from FILE2, all\n\ +separated by CHAR.\n\ +\n\ +Important: FILE1 and FILE2 must be sorted on the join fields.\n\ +E.g., use `sort -k 1b,1' if `join' has no options.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Record a field in LINE, with location FIELD and size LEN. */ + +static void +extract_field (struct line *line, char *field, size_t len) +{ + if (line->nfields >= line->nfields_allocated) + { + line->fields = X2NREALLOC (line->fields, &line->nfields_allocated); + } + line->fields[line->nfields].beg = field; + line->fields[line->nfields].len = len; + ++(line->nfields); +} + +/* Fill in the `fields' structure in LINE. */ + +static void +xfields (struct line *line) +{ + char *ptr = line->buf.buffer; + char const *lim = ptr + line->buf.length - 1; + + if (ptr == lim) + return; + + if (0 <= tab) + { + char *sep; + for (; (sep = memchr (ptr, tab, lim - ptr)) != NULL; ptr = sep + 1) + extract_field (line, ptr, sep - ptr); + } + else + { + /* Skip leading blanks before the first field. */ + while (isblank (to_uchar (*ptr))) + if (++ptr == lim) + return; + + do + { + char *sep; + for (sep = ptr + 1; sep != lim && ! isblank (to_uchar (*sep)); sep++) + continue; + extract_field (line, ptr, sep - ptr); + if (sep == lim) + return; + for (ptr = sep + 1; ptr != lim && isblank (to_uchar (*ptr)); ptr++) + continue; + } + while (ptr != lim); + } + + extract_field (line, ptr, lim - ptr); +} + +/* Read a line from FP into LINE and split it into fields. + Return true if successful. */ + +static bool +get_line (FILE *fp, struct line *line) +{ + initbuffer (&line->buf); + + if (! readlinebuffer (&line->buf, fp)) + { + if (ferror (fp)) + error (EXIT_FAILURE, errno, _("read error")); + free (line->buf.buffer); + line->buf.buffer = NULL; + return false; + } + + line->nfields_allocated = 0; + line->nfields = 0; + line->fields = NULL; + xfields (line); + return true; +} + +static void +freeline (struct line *line) +{ + free (line->fields); + free (line->buf.buffer); + line->buf.buffer = NULL; +} + +static void +initseq (struct seq *seq) +{ + seq->count = 0; + seq->alloc = 0; + seq->lines = NULL; +} + +/* Read a line from FP and add it to SEQ. Return true if successful. */ + +static bool +getseq (FILE *fp, struct seq *seq) +{ + if (seq->count == seq->alloc) + seq->lines = X2NREALLOC (seq->lines, &seq->alloc); + + if (get_line (fp, &seq->lines[seq->count])) + { + ++seq->count; + return true; + } + return false; +} + +static void +delseq (struct seq *seq) +{ + size_t i; + for (i = 0; i < seq->count; i++) + if (seq->lines[i].buf.buffer) + freeline (&seq->lines[i]); + free (seq->lines); +} + +/* Return <0 if the join field in LINE1 compares less than the one in LINE2; + >0 if it compares greater; 0 if it compares equal. + Report an error and exit if the comparison fails. */ + +static int +keycmp (struct line const *line1, struct line const *line2) +{ + /* Start of field to compare in each file. */ + char *beg1; + char *beg2; + + size_t len1; + size_t len2; /* Length of fields to compare. */ + int diff; + + if (join_field_1 < line1->nfields) + { + beg1 = line1->fields[join_field_1].beg; + len1 = line1->fields[join_field_1].len; + } + else + { + beg1 = NULL; + len1 = 0; + } + + if (join_field_2 < line2->nfields) + { + beg2 = line2->fields[join_field_2].beg; + len2 = line2->fields[join_field_2].len; + } + else + { + beg2 = NULL; + len2 = 0; + } + + if (len1 == 0) + return len2 == 0 ? 0 : -1; + if (len2 == 0) + return 1; + + if (ignore_case) + { + /* FIXME: ignore_case does not work with NLS (in particular, + with multibyte chars). */ + diff = memcasecmp (beg1, beg2, MIN (len1, len2)); + } + else + { + if (hard_LC_COLLATE) + return xmemcoll (beg1, len1, beg2, len2); + diff = memcmp (beg1, beg2, MIN (len1, len2)); + } + + if (diff) + return diff; + return len1 < len2 ? -1 : len1 != len2; +} + +/* Print field N of LINE if it exists and is nonempty, otherwise + `empty_filler' if it is nonempty. */ + +static void +prfield (size_t n, struct line const *line) +{ + size_t len; + + if (n < line->nfields) + { + len = line->fields[n].len; + if (len) + fwrite (line->fields[n].beg, 1, len, stdout); + else if (empty_filler) + fputs (empty_filler, stdout); + } + else if (empty_filler) + fputs (empty_filler, stdout); +} + +/* Print the join of LINE1 and LINE2. */ + +static void +prjoin (struct line const *line1, struct line const *line2) +{ + const struct outlist *outlist; + char output_separator = tab < 0 ? ' ' : tab; + + outlist = outlist_head.next; + if (outlist) + { + const struct outlist *o; + + o = outlist; + while (1) + { + size_t field; + struct line const *line; + + if (o->file == 0) + { + if (line1 == &uni_blank) + { + line = line2; + field = join_field_2; + } + else + { + line = line1; + field = join_field_1; + } + } + else + { + line = (o->file == 1 ? line1 : line2); + field = o->field; + } + prfield (field, line); + o = o->next; + if (o == NULL) + break; + putchar (output_separator); + } + putchar ('\n'); + } + else + { + size_t i; + + if (line1 == &uni_blank) + { + struct line const *t; + t = line1; + line1 = line2; + line2 = t; + } + prfield (join_field_1, line1); + for (i = 0; i < join_field_1 && i < line1->nfields; ++i) + { + putchar (output_separator); + prfield (i, line1); + } + for (i = join_field_1 + 1; i < line1->nfields; ++i) + { + putchar (output_separator); + prfield (i, line1); + } + + for (i = 0; i < join_field_2 && i < line2->nfields; ++i) + { + putchar (output_separator); + prfield (i, line2); + } + for (i = join_field_2 + 1; i < line2->nfields; ++i) + { + putchar (output_separator); + prfield (i, line2); + } + putchar ('\n'); + } +} + +/* Print the join of the files in FP1 and FP2. */ + +static void +join (FILE *fp1, FILE *fp2) +{ + struct seq seq1, seq2; + struct line line; + int diff; + bool eof1, eof2; + + /* Read the first line of each file. */ + initseq (&seq1); + getseq (fp1, &seq1); + initseq (&seq2); + getseq (fp2, &seq2); + + while (seq1.count && seq2.count) + { + size_t i; + diff = keycmp (&seq1.lines[0], &seq2.lines[0]); + if (diff < 0) + { + if (print_unpairables_1) + prjoin (&seq1.lines[0], &uni_blank); + freeline (&seq1.lines[0]); + seq1.count = 0; + getseq (fp1, &seq1); + continue; + } + if (diff > 0) + { + if (print_unpairables_2) + prjoin (&uni_blank, &seq2.lines[0]); + freeline (&seq2.lines[0]); + seq2.count = 0; + getseq (fp2, &seq2); + continue; + } + + /* Keep reading lines from file1 as long as they continue to + match the current line from file2. */ + eof1 = false; + do + if (!getseq (fp1, &seq1)) + { + eof1 = true; + ++seq1.count; + break; + } + while (!keycmp (&seq1.lines[seq1.count - 1], &seq2.lines[0])); + + /* Keep reading lines from file2 as long as they continue to + match the current line from file1. */ + eof2 = false; + do + if (!getseq (fp2, &seq2)) + { + eof2 = true; + ++seq2.count; + break; + } + while (!keycmp (&seq1.lines[0], &seq2.lines[seq2.count - 1])); + + if (print_pairables) + { + for (i = 0; i < seq1.count - 1; ++i) + { + size_t j; + for (j = 0; j < seq2.count - 1; ++j) + prjoin (&seq1.lines[i], &seq2.lines[j]); + } + } + + for (i = 0; i < seq1.count - 1; ++i) + freeline (&seq1.lines[i]); + if (!eof1) + { + seq1.lines[0] = seq1.lines[seq1.count - 1]; + seq1.count = 1; + } + else + seq1.count = 0; + + for (i = 0; i < seq2.count - 1; ++i) + freeline (&seq2.lines[i]); + if (!eof2) + { + seq2.lines[0] = seq2.lines[seq2.count - 1]; + seq2.count = 1; + } + else + seq2.count = 0; + } + + if (print_unpairables_1 && seq1.count) + { + prjoin (&seq1.lines[0], &uni_blank); + freeline (&seq1.lines[0]); + while (get_line (fp1, &line)) + { + prjoin (&line, &uni_blank); + freeline (&line); + } + } + + if (print_unpairables_2 && seq2.count) + { + prjoin (&uni_blank, &seq2.lines[0]); + freeline (&seq2.lines[0]); + while (get_line (fp2, &line)) + { + prjoin (&uni_blank, &line); + freeline (&line); + } + } + + delseq (&seq1); + delseq (&seq2); +} + +/* Add a field spec for field FIELD of file FILE to `outlist'. */ + +static void +add_field (int file, size_t field) +{ + struct outlist *o; + + assert (file == 0 || file == 1 || file == 2); + assert (file != 0 || field == 0); + + o = xmalloc (sizeof *o); + o->file = file; + o->field = field; + o->next = NULL; + + /* Add to the end of the list so the fields are in the right order. */ + outlist_end->next = o; + outlist_end = o; +} + +/* Convert a string of decimal digits, STR (the 1-based join field number), + to an integral value. Upon successful conversion, return one less + (the zero-based field number). Silently convert too-large values + to SIZE_MAX - 1. Otherwise, if a value cannot be converted, give a + diagnostic and exit. */ + +static size_t +string_to_join_field (char const *str) +{ + size_t result; + unsigned long int val; + verify (SIZE_MAX <= ULONG_MAX); + + strtol_error s_err = xstrtoul (str, NULL, 10, &val, ""); + if (s_err == LONGINT_OVERFLOW || (s_err == LONGINT_OK && SIZE_MAX < val)) + val = SIZE_MAX; + else if (s_err != LONGINT_OK || val == 0) + error (EXIT_FAILURE, 0, _("invalid field number: %s"), quote (str)); + + result = val - 1; + + return result; +} + +/* Convert a single field specifier string, S, to a *FILE_INDEX, *FIELD_INDEX + pair. In S, the field index string is 1-based; *FIELD_INDEX is zero-based. + If S is valid, return true. Otherwise, give a diagnostic and exit. */ + +static void +decode_field_spec (const char *s, int *file_index, size_t *field_index) +{ + /* The first character must be 0, 1, or 2. */ + switch (s[0]) + { + case '0': + if (s[1]) + { + /* `0' must be all alone -- no `.FIELD'. */ + error (EXIT_FAILURE, 0, _("invalid field specifier: %s"), quote (s)); + } + *file_index = 0; + *field_index = 0; + break; + + case '1': + case '2': + if (s[1] != '.') + error (EXIT_FAILURE, 0, _("invalid field specifier: %s"), quote (s)); + *file_index = s[0] - '0'; + *field_index = string_to_join_field (s + 2); + break; + + default: + error (EXIT_FAILURE, 0, + _("invalid file number in field spec: %s"), quote (s)); + + /* Tell gcc -W -Wall that we can't get beyond this point. + This avoids a warning (otherwise legit) that the caller's copies + of *file_index and *field_index might be used uninitialized. */ + abort (); + + break; + } +} + +/* Add the comma or blank separated field spec(s) in STR to `outlist'. */ + +static void +add_field_list (char *str) +{ + char *p = str; + + do + { + int file_index; + size_t field_index; + char const *spec_item = p; + + p = strpbrk (p, ", \t"); + if (p) + *p++ = '\0'; + decode_field_spec (spec_item, &file_index, &field_index); + add_field (file_index, field_index); + } + while (p); +} + +/* Set the join field *VAR to VAL, but report an error if *VAR is set + more than once to incompatible values. */ + +static void +set_join_field (size_t *var, size_t val) +{ + if (*var != SIZE_MAX && *var != val) + { + unsigned long int var1 = *var + 1; + unsigned long int val1 = val + 1; + error (EXIT_FAILURE, 0, _("incompatible join fields %lu, %lu"), + var1, val1); + } + *var = val; +} + +/* Status of command-line arguments. */ + +enum operand_status + { + /* This argument must be an operand, i.e., one of the files to be + joined. */ + MUST_BE_OPERAND, + + /* This might be the argument of the preceding -j1 or -j2 option, + or it might be an operand. */ + MIGHT_BE_J1_ARG, + MIGHT_BE_J2_ARG, + + /* This might be the argument of the preceding -o option, or it might be + an operand. */ + MIGHT_BE_O_ARG + }; + +/* Add NAME to the array of input file NAMES with operand statuses + OPERAND_STATUS; currently there are NFILES names in the list. */ + +static void +add_file_name (char *name, char *names[2], + int operand_status[2], int joption_count[2], int *nfiles, + int *prev_optc_status, int *optc_status) +{ + int n = *nfiles; + + if (n == 2) + { + bool op0 = (operand_status[0] == MUST_BE_OPERAND); + char *arg = names[op0]; + switch (operand_status[op0]) + { + case MUST_BE_OPERAND: + error (0, 0, _("extra operand %s"), quote (name)); + usage (EXIT_FAILURE); + + case MIGHT_BE_J1_ARG: + joption_count[0]--; + set_join_field (&join_field_1, string_to_join_field (arg)); + break; + + case MIGHT_BE_J2_ARG: + joption_count[1]--; + set_join_field (&join_field_2, string_to_join_field (arg)); + break; + + case MIGHT_BE_O_ARG: + add_field_list (arg); + break; + } + if (!op0) + { + operand_status[0] = operand_status[1]; + names[0] = names[1]; + } + n = 1; + } + + operand_status[n] = *prev_optc_status; + names[n] = name; + *nfiles = n + 1; + if (*prev_optc_status == MIGHT_BE_O_ARG) + *optc_status = MIGHT_BE_O_ARG; +} + +int +main (int argc, char **argv) +{ + int optc_status; + int prev_optc_status = MUST_BE_OPERAND; + int operand_status[2]; + int joption_count[2] = { 0, 0 }; + char *names[2]; + FILE *fp1, *fp2; + int optc; + int nfiles = 0; + int i; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + hard_LC_COLLATE = hard_locale (LC_COLLATE); + + atexit (close_stdout); + + print_pairables = true; + + while ((optc = getopt_long (argc, argv, "-a:e:i1:2:j:o:t:v:", + longopts, NULL)) + != -1) + { + optc_status = MUST_BE_OPERAND; + + switch (optc) + { + case 'v': + print_pairables = false; + /* Fall through. */ + + case 'a': + { + unsigned long int val; + if (xstrtoul (optarg, NULL, 10, &val, "") != LONGINT_OK + || (val != 1 && val != 2)) + error (EXIT_FAILURE, 0, + _("invalid field number: %s"), quote (optarg)); + if (val == 1) + print_unpairables_1 = true; + else + print_unpairables_2 = true; + } + break; + + case 'e': + if (empty_filler && ! STREQ (empty_filler, optarg)) + error (EXIT_FAILURE, 0, + _("conflicting empty-field replacement strings")); + empty_filler = optarg; + break; + + case 'i': + ignore_case = true; + break; + + case '1': + set_join_field (&join_field_1, string_to_join_field (optarg)); + break; + + case '2': + set_join_field (&join_field_2, string_to_join_field (optarg)); + break; + + case 'j': + if ((optarg[0] == '1' || optarg[0] == '2') && !optarg[1] + && optarg == argv[optind - 1] + 2) + { + /* The argument was either "-j1" or "-j2". */ + bool is_j2 = (optarg[0] == '2'); + joption_count[is_j2]++; + optc_status = MIGHT_BE_J1_ARG + is_j2; + } + else + { + set_join_field (&join_field_1, string_to_join_field (optarg)); + set_join_field (&join_field_2, join_field_1); + } + break; + + case 'o': + add_field_list (optarg); + optc_status = MIGHT_BE_O_ARG; + break; + + case 't': + { + unsigned char newtab = optarg[0]; + if (! newtab) + error (EXIT_FAILURE, 0, _("empty tab")); + if (optarg[1]) + { + if (STREQ (optarg, "\\0")) + newtab = '\0'; + else + error (EXIT_FAILURE, 0, _("multi-character tab %s"), + quote (optarg)); + } + if (0 <= tab && tab != newtab) + error (EXIT_FAILURE, 0, _("incompatible tabs")); + tab = newtab; + } + break; + + case 1: /* Non-option argument. */ + add_file_name (optarg, names, operand_status, joption_count, + &nfiles, &prev_optc_status, &optc_status); + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + + prev_optc_status = optc_status; + } + + /* Process any operands after "--". */ + prev_optc_status = MUST_BE_OPERAND; + while (optind < argc) + add_file_name (argv[optind++], names, operand_status, joption_count, + &nfiles, &prev_optc_status, &optc_status); + + if (nfiles != 2) + { + if (nfiles == 0) + error (0, 0, _("missing operand")); + else + error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); + usage (EXIT_FAILURE); + } + + /* If "-j1" was specified and it turns out not to have had an argument, + treat it as "-j 1". Likewise for -j2. */ + for (i = 0; i < 2; i++) + if (joption_count[i] != 0) + { + set_join_field (&join_field_1, i); + set_join_field (&join_field_2, i); + } + + if (join_field_1 == SIZE_MAX) + join_field_1 = 0; + if (join_field_2 == SIZE_MAX) + join_field_2 = 0; + + fp1 = STREQ (names[0], "-") ? stdin : fopen (names[0], "r"); + if (!fp1) + error (EXIT_FAILURE, errno, "%s", names[0]); + fp2 = STREQ (names[1], "-") ? stdin : fopen (names[1], "r"); + if (!fp2) + error (EXIT_FAILURE, errno, "%s", names[1]); + if (fp1 == fp2) + error (EXIT_FAILURE, errno, _("both files cannot be standard input")); + join (fp1, fp2); + + if (fclose (fp1) != 0) + error (EXIT_FAILURE, errno, "%s", names[0]); + if (fclose (fp2) != 0) + error (EXIT_FAILURE, errno, "%s", names[1]); + + exit (EXIT_SUCCESS); +} diff --git a/src/kill.c b/src/kill.c new file mode 100644 index 0000000..c59025f --- /dev/null +++ b/src/kill.c @@ -0,0 +1,373 @@ +/* kill -- send a signal to a process + Copyright (C) 2002, 2003, 2004, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Paul Eggert. */ + +#include +#include +#include +#include +#include + +#if HAVE_SYS_WAIT_H +# include +#endif +#ifndef WIFSIGNALED +# define WIFSIGNALED(s) (((s) & 0xFFFF) - 1 < (unsigned int) 0xFF) +#endif +#ifndef WTERMSIG +# define WTERMSIG(s) ((s) & 0x7F) +#endif + +#include "system.h" +#include "error.h" +#include "sig2str.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "kill" + +#define AUTHORS "Paul Eggert" + +#if ! (HAVE_DECL_STRSIGNAL || defined strsignal) +# if ! (HAVE_DECL_SYS_SIGLIST || defined sys_siglist) +# if HAVE_DECL__SYS_SIGLIST || defined _sys_siglist +# define sys_siglist _sys_siglist +# elif HAVE_DECL___SYS_SIGLIST || defined __sys_siglist +# define sys_siglist __sys_siglist +# endif +# endif +# if HAVE_DECL_SYS_SIGLIST || defined sys_siglist +# define strsignal(signum) (0 <= (signum) && (signum) <= SIGNUM_BOUND \ + ? sys_siglist[signum] \ + : 0) +# endif +# ifndef strsignal +# define strsignal(signum) 0 +# endif +#endif + +/* The name this program was run with, for error messages. */ +char *program_name; + +static char const short_options[] = + "0::1::2::3::4::5::6::7::8::9::" + "A::B::C::D::E::F::G::H::I::J::K::L::M::" + "N::O::P::Q::R::S::T::U::V::W::X::Y::Z::" + "ln:s:t"; + +static struct option const long_options[] = +{ + {"list", no_argument, NULL, 'l'}, + {"signal", required_argument, NULL, 's'}, + {"table", no_argument, NULL, 't'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [-s SIGNAL | -SIGNAL] PID...\n\ + or: %s -l [SIGNAL]...\n\ + or: %s -t [SIGNAL]...\n\ +"), + program_name, program_name, program_name); + fputs (_("\ +Send signals to processes, or list signals.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -s, --signal=SIGNAL, -SIGNAL\n\ + specify the name or number of the signal to be sent\n\ + -l, --list list signal names, or convert signal names to/from numbers\n\ + -t, --table print a table of signal information\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\n\ +SIGNAL may be a signal name like `HUP', or a signal number like `1',\n\ +or an exit status of a process terminated by a signal.\n\ +PID is an integer; if negative it identifies a process group.\n\ +"), stdout); + printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Convert OPERAND to a signal number with printable representation SIGNAME. + Return the signal number, or -1 if unsuccessful. */ + +static int +operand2sig (char const *operand, char *signame) +{ + int signum; + + if (ISDIGIT (*operand)) + { + char *endp; + long int l = (errno = 0, strtol (operand, &endp, 10)); + int i = l; + signum = (operand == endp || *endp || errno || i != l ? -1 + : WIFSIGNALED (i) ? WTERMSIG (i) + : i); + } + else + { + /* Convert signal to upper case in the C locale, not in the + current locale. Don't assume ASCII; it might be EBCDIC. */ + char *upcased = xstrdup (operand); + char *p; + for (p = upcased; *p; p++) + if (strchr ("abcdefghijklmnopqrstuvwxyz", *p)) + *p += 'A' - 'a'; + + /* Look for the signal name, possibly prefixed by "SIG", + and possibly lowercased. */ + if (! (str2sig (upcased, &signum) == 0 + || (upcased[0] == 'S' && upcased[1] == 'I' && upcased[2] == 'G' + && str2sig (upcased + 3, &signum) == 0))) + signum = -1; + + free (upcased); + } + + if (signum < 0 || sig2str (signum, signame) != 0) + { + error (0, 0, _("%s: invalid signal"), operand); + return -1; + } + + return signum; +} + +/* Print a row of `kill -t' output. NUM_WIDTH is the maximum signal + number width, and SIGNUM is the signal number to print. The + maximum name width is NAME_WIDTH, and SIGNAME is the name to print. */ + +static void +print_table_row (unsigned int num_width, int signum, + unsigned int name_width, char const *signame) +{ + char const *description = strsignal (signum); + printf ("%*d %-*s %s\n", num_width, signum, name_width, signame, + description ? description : "?"); +} + +/* Print a list of signal names. If TABLE, print a table. + Print the names specified by ARGV if nonzero; otherwise, + print all known names. Return a suitable exit status. */ + +static int +list_signals (bool table, char *const *argv) +{ + int signum; + int status = EXIT_SUCCESS; + char signame[SIG2STR_MAX]; + + if (table) + { + unsigned int name_width = 0; + + /* Compute the maximum width of a signal number. */ + unsigned int num_width = 1; + for (signum = 1; signum <= SIGNUM_BOUND / 10; signum *= 10) + num_width++; + + /* Compute the maximum width of a signal name. */ + for (signum = 1; signum <= SIGNUM_BOUND; signum++) + if (sig2str (signum, signame) == 0) + { + size_t len = strlen (signame); + if (name_width < len) + name_width = len; + } + + if (argv) + for (; *argv; argv++) + { + signum = operand2sig (*argv, signame); + if (signum < 0) + status = EXIT_FAILURE; + else + print_table_row (num_width, signum, name_width, signame); + } + else + for (signum = 1; signum <= SIGNUM_BOUND; signum++) + if (sig2str (signum, signame) == 0) + print_table_row (num_width, signum, name_width, signame); + } + else + { + if (argv) + for (; *argv; argv++) + { + signum = operand2sig (*argv, signame); + if (signum < 0) + status = EXIT_FAILURE; + else + { + if (ISDIGIT (**argv)) + puts (signame); + else + printf ("%d\n", signum); + } + } + else + for (signum = 1; signum <= SIGNUM_BOUND; signum++) + if (sig2str (signum, signame) == 0) + puts (signame); + } + + return status; +} + +/* Send signal SIGNUM to all the processes or process groups specified + by ARGV. Return a suitable exit status. */ + +static int +send_signals (int signum, char *const *argv) +{ + int status = EXIT_SUCCESS; + char const *arg = *argv; + + do + { + char *endp; + intmax_t n = (errno = 0, strtoimax (arg, &endp, 10)); + pid_t pid = n; + + if (errno == ERANGE || pid != n || arg == endp || *endp) + { + error (0, 0, _("%s: invalid process id"), arg); + status = EXIT_FAILURE; + } + else if (kill (pid, signum) != 0) + { + error (0, errno, "%s", arg); + status = EXIT_FAILURE; + } + } + while ((arg = *++argv)); + + return status; +} + +int +main (int argc, char **argv) +{ + int optc; + bool list = false; + bool table = false; + int signum = -1; + char signame[SIG2STR_MAX]; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, short_options, long_options, NULL)) + != -1) + switch (optc) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (optind != 2) + { + /* This option is actually a process-id. */ + optind--; + goto no_more_options; + } + /* Fall through. */ + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': + case 'Z': + if (! optarg) + optarg = argv[optind - 1] + strlen (argv[optind - 1]); + if (optarg != argv[optind - 1] + 2) + { + error (0, 0, _("invalid option -- %c"), optc); + usage (EXIT_FAILURE); + } + optarg--; + /* Fall through. */ + case 'n': /* -n is not documented, but is for Bash compatibility. */ + case 's': + if (0 <= signum) + { + error (0, 0, _("%s: multiple signals specified"), optarg); + usage (EXIT_FAILURE); + } + signum = operand2sig (optarg, signame); + if (signum < 0) + usage (EXIT_FAILURE); + break; + + case 't': + table = true; + /* Fall through. */ + case 'l': + if (list) + { + error (0, 0, _("multiple -l or -t options specified")); + usage (EXIT_FAILURE); + } + list = true; + break; + + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + no_more_options:; + + if (signum < 0) + signum = SIGTERM; + else if (list) + { + error (0, 0, _("cannot combine signal with -l or -t")); + usage (EXIT_FAILURE); + } + + if ( ! list && argc <= optind) + { + error (0, 0, _("no process ID specified")); + usage (EXIT_FAILURE); + } + + return (list + ? list_signals (table, optind < argc ? argv + optind : NULL) + : send_signals (signum, argv + optind)); +} diff --git a/src/lbracket.c b/src/lbracket.c new file mode 100644 index 0000000..b57ca9b --- /dev/null +++ b/src/lbracket.c @@ -0,0 +1,2 @@ +#define LBRACKET 1 +#include "test.c" diff --git a/src/link.c b/src/link.c new file mode 100644 index 0000000..277cf23 --- /dev/null +++ b/src/link.c @@ -0,0 +1,99 @@ +/* link utility for GNU. + Copyright (C) 2001, 2002, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Michael Stone */ + +/* Implementation overview: + + Simply call the system 'link' function */ + +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "long-options.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "link" + +#define AUTHORS "Michael Stone" + +/* Name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s FILE1 FILE2\n\ + or: %s OPTION\n"), program_name, program_name); + fputs (_("Call the link function to create a link named FILE2\ + to an existing FILE1.\n\n"), + stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (argc < optind + 2) + { + if (argc < optind + 1) + error (0, 0, _("missing operand")); + else + error (0, 0, _("missing operand after %s"), quote (argv[optind])); + usage (EXIT_FAILURE); + } + + if (optind + 2 < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 2])); + usage (EXIT_FAILURE); + } + + if (link (argv[optind], argv[optind + 1]) != 0) + error (EXIT_FAILURE, errno, _("cannot create link %s to %s"), + quote_n (0, argv[optind + 1]), quote_n (1, argv[optind])); + + exit (EXIT_SUCCESS); +} diff --git a/src/ln.c b/src/ln.c new file mode 100644 index 0000000..fae3708 --- /dev/null +++ b/src/ln.c @@ -0,0 +1,534 @@ +/* `ln' program to create links between files. + Copyright (C) 1986, 1989-1991, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Mike Parker and David MacKenzie. */ + +#include +#include +#include +#include + +#include "system.h" +#include "same.h" +#include "backupfile.h" +#include "error.h" +#include "filenamecat.h" +#include "quote.h" +#include "yesno.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "ln" + +#define AUTHORS "Mike Parker", "David MacKenzie" + +#ifndef ENABLE_HARD_LINK_TO_SYMLINK_WARNING +# define ENABLE_HARD_LINK_TO_SYMLINK_WARNING 0 +#endif + +/* In being careful not even to try to make hard links to directories, + we have to know whether link(2) follows symlinks. If it does, then + we have to *stat* the `source' to see if the resulting link would be + to a directory. Otherwise, we have to use *lstat* so that we allow + users to make hard links to symlinks-that-point-to-directories. */ + +#if LINK_FOLLOWS_SYMLINKS +# define STAT_LIKE_LINK(File, Stat_buf) \ + stat (File, Stat_buf) +#else +# define STAT_LIKE_LINK(File, Stat_buf) \ + lstat (File, Stat_buf) +#endif + +/* The name by which the program was run, for error messages. */ +char *program_name; + +/* FIXME: document */ +static enum backup_type backup_type; + +/* If true, make symbolic links; otherwise, make hard links. */ +static bool symbolic_link; + +/* If true, ask the user before removing existing files. */ +static bool interactive; + +/* If true, remove existing files unconditionally. */ +static bool remove_existing_files; + +/* If true, list each file as it is moved. */ +static bool verbose; + +/* If true, allow the superuser to *attempt* to make hard links + to directories. However, it appears that this option is not useful + in practice, since even the superuser is prohibited from hard-linking + directories on most (all?) existing systems. */ +static bool hard_dir_link; + +/* If nonzero, and the specified destination is a symbolic link to a + directory, treat it just as if it were a directory. Otherwise, the + command `ln --force --no-dereference file symlink-to-dir' deletes + symlink-to-dir before creating the new link. */ +static bool dereference_dest_dir_symlinks = true; + +static struct option const long_options[] = +{ + {"backup", optional_argument, NULL, 'b'}, + {"directory", no_argument, NULL, 'F'}, + {"no-dereference", no_argument, NULL, 'n'}, + {"no-target-directory", no_argument, NULL, 'T'}, + {"force", no_argument, NULL, 'f'}, + {"interactive", no_argument, NULL, 'i'}, + {"suffix", required_argument, NULL, 'S'}, + {"target-directory", required_argument, NULL, 't'}, + {"symbolic", no_argument, NULL, 's'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* FILE is the last operand of this command. Return true if FILE is a + directory. But report an error there is a problem accessing FILE, + or if FILE does not exist but would have to refer to an existing + directory if it referred to anything at all. */ + +static bool +target_directory_operand (char const *file) +{ + char const *b = last_component (file); + size_t blen = strlen (b); + bool looks_like_a_dir = (blen == 0 || ISSLASH (b[blen - 1])); + struct stat st; + int stat_result = + (dereference_dest_dir_symlinks ? stat (file, &st) : lstat (file, &st)); + int err = (stat_result == 0 ? 0 : errno); + bool is_a_dir = !err && S_ISDIR (st.st_mode); + if (err && err != ENOENT) + error (EXIT_FAILURE, err, _("accessing %s"), quote (file)); + if (is_a_dir < looks_like_a_dir) + error (EXIT_FAILURE, err, _("target %s is not a directory"), quote (file)); + return is_a_dir; +} + +/* Make a link DEST to the (usually) existing file SOURCE. + Symbolic links to nonexistent files are allowed. + Return true if successful. */ + +static bool +do_link (const char *source, const char *dest) +{ + struct stat source_stats; + struct stat dest_stats; + char *dest_backup = NULL; + bool dest_lstat_ok = false; + bool source_is_dir = false; + bool ok; + + /* Use stat here 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) + { + if (STAT_LIKE_LINK (source, &source_stats) != 0) + { + error (0, errno, _("accessing %s"), quote (source)); + return false; + } + + if (ENABLE_HARD_LINK_TO_SYMLINK_WARNING + && S_ISLNK (source_stats.st_mode)) + { + error (0, 0, _("%s: warning: making a hard link to a symbolic link\ + is not portable"), + quote (source)); + } + + if (S_ISDIR (source_stats.st_mode)) + { + source_is_dir = true; + if (! hard_dir_link) + { + error (0, 0, _("%s: hard link not allowed for directory"), + quote (source)); + return false; + } + } + } + + if (remove_existing_files || interactive || backup_type != no_backups) + { + dest_lstat_ok = (lstat (dest, &dest_stats) == 0); + if (!dest_lstat_ok && errno != ENOENT) + { + error (0, errno, _("accessing %s"), quote (dest)); + return false; + } + } + + /* If --force (-f) has been specified without --backup, then before + making a link ln must remove the destination file if it exists. + (with --backup, it just renames any existing destination file) + But if the source and destination are the same, don't remove + anything and fail right here. */ + if ((remove_existing_files + /* Ensure that "ln --backup f f" fails here, with the + "... same file" diagnostic, below. Otherwise, subsequent + code would give a misleading "file not found" diagnostic. + This case is different than the others handled here, since + the command in question doesn't use --force. */ + || (!symbolic_link && backup_type != no_backups)) + && dest_lstat_ok + /* Allow `ln -sf --backup k k' to succeed in creating the + self-referential symlink, but don't allow the hard-linking + equivalent: `ln -f k k' (with or without --backup) to get + beyond this point, because the error message you'd get is + misleading. */ + && (backup_type == no_backups || !symbolic_link) + && (!symbolic_link || stat (source, &source_stats) == 0) + && SAME_INODE (source_stats, dest_stats) + /* The following detects whether removing DEST will also remove + SOURCE. If the file has only one link then both are surely + the same link. Otherwise check whether they point to the same + name in the same directory. */ + && (source_stats.st_nlink == 1 || same_name (source, dest))) + { + error (0, 0, _("%s and %s are the same file"), + quote_n (0, source), quote_n (1, dest)); + return false; + } + + if (dest_lstat_ok) + { + if (S_ISDIR (dest_stats.st_mode)) + { + error (0, 0, _("%s: cannot overwrite directory"), quote (dest)); + return false; + } + if (interactive) + { + fprintf (stderr, _("%s: replace %s? "), program_name, quote (dest)); + if (!yesno ()) + return true; + remove_existing_files = true; + } + + if (backup_type != no_backups) + { + dest_backup = find_backup_file_name (dest, backup_type); + if (rename (dest, dest_backup) != 0) + { + int rename_errno = errno; + free (dest_backup); + dest_backup = NULL; + if (rename_errno != ENOENT) + { + error (0, rename_errno, _("cannot backup %s"), quote (dest)); + return false; + } + } + } + } + + ok = ((symbolic_link ? symlink (source, dest) : link (source, dest)) + == 0); + + /* If the attempt to create a link failed and we are removing or + backing up destinations, unlink the destination and try again. + + POSIX 1003.1-2004 requires that ln -f A B must unlink B even on + failure (e.g., when A does not exist). This is counterintuitive, + and we submitted a defect report + + (2004-06-24). If the committee does not fix the standard we'll + have to change the behavior of ln -f, at least if POSIXLY_CORRECT + is set. In the meantime ln -f A B will not unlink B unless the + attempt to link A to B failed because B already existed. + + Try to unlink DEST even if we may have backed it up successfully. + In some unusual cases (when DEST and DEST_BACKUP are hard-links + that refer to the same file), rename succeeds and DEST remains. + If we didn't remove DEST in that case, the subsequent symlink or link + call would fail. */ + + if (!ok && errno == EEXIST && (remove_existing_files || dest_backup)) + { + if (unlink (dest) != 0) + { + error (0, errno, _("cannot remove %s"), quote (dest)); + free (dest_backup); + return false; + } + + ok = ((symbolic_link ? symlink (source, dest) : link (source, dest)) + == 0); + } + + if (ok) + { + if (verbose) + { + if (dest_backup) + printf ("%s ~ ", quote (dest_backup)); + printf ("%s %c> %s\n", quote_n (0, dest), (symbolic_link ? '-' : '='), + quote_n (1, source)); + } + } + else + { + error (0, errno, + (symbolic_link + ? (errno != ENAMETOOLONG && *source + ? _("creating symbolic link %s") + : _("creating symbolic link %s -> %s")) + : (errno == EMLINK && !source_is_dir + ? _("creating hard link to %.0s%s") + : (errno == EDQUOT || errno == EEXIST || errno == ENOSPC + || errno == EROFS) + ? _("creating hard link %s") + : _("creating hard link %s => %s"))), + quote_n (0, dest), quote_n (1, source)); + + if (dest_backup) + { + if (rename (dest_backup, dest) != 0) + error (0, errno, _("cannot un-backup %s"), quote (dest)); + } + } + + free (dest_backup); + return ok; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [-T] TARGET LINK_NAME (1st form)\n\ + or: %s [OPTION]... TARGET (2nd form)\n\ + or: %s [OPTION]... TARGET... DIRECTORY (3rd form)\n\ + or: %s [OPTION]... -t DIRECTORY TARGET... (4th form)\n\ +"), + program_name, program_name, program_name, program_name); + fputs (_("\ +In the 1st form, create a link to TARGET with the name LINK_NAME.\n\ +In the 2nd form, create a link to TARGET in the current directory.\n\ +In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.\n\ +Create hard links by default, symbolic links with --symbolic.\n\ +When creating hard links, each TARGET must exist.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + --backup[=CONTROL] make a backup of each existing destination file\n\ + -b like --backup but does not accept an argument\n\ + -d, -F, --directory allow the superuser to attempt to hard link\n\ + directories (note: will probably fail due to\n\ + system restrictions, even for the superuser)\n\ + -f, --force remove existing destination files\n\ +"), stdout); + fputs (_("\ + -n, --no-dereference treat destination that is a symlink to a\n\ + directory as if it were a normal file\n\ + -i, --interactive prompt whether to remove destinations\n\ + -s, --symbolic make symbolic links instead of hard links\n\ +"), stdout); + fputs (_("\ + -S, --suffix=SUFFIX override the usual backup suffix\n\ + -t, --target-directory=DIRECTORY specify the DIRECTORY in which to create\n\ + the links\n\ + -T, --no-target-directory treat LINK_NAME as a normal file\n\ + -v, --verbose print name of each linked file\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ +The version control method may be selected via the --backup option or through\n\ +the VERSION_CONTROL environment variable. Here are the values:\n\ +\n\ +"), stdout); + fputs (_("\ + none, off never make backups (even if --backup is given)\n\ + numbered, t make numbered backups\n\ + existing, nil numbered if numbered backups exist, simple otherwise\n\ + simple, never always make simple backups\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int c; + bool ok; + bool make_backups = false; + char *backup_suffix_string; + char *version_control_string = NULL; + char const *target_directory = NULL; + bool no_target_directory = false; + int n_files; + char **file; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless + we'll actually use backup_suffix_string. */ + backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); + + symbolic_link = remove_existing_files = interactive = verbose + = hard_dir_link = false; + + while ((c = getopt_long (argc, argv, "bdfinst:vFS:T", long_options, NULL)) + != -1) + { + switch (c) + { + case 'b': + make_backups = true; + if (optarg) + version_control_string = optarg; + break; + case 'd': + case 'F': + hard_dir_link = true; + break; + case 'f': + remove_existing_files = true; + interactive = false; + break; + case 'i': + remove_existing_files = false; + interactive = true; + break; + case 'n': + dereference_dest_dir_symlinks = false; + break; + case 's': + symbolic_link = true; + break; + case 't': + if (target_directory) + error (EXIT_FAILURE, 0, _("multiple target directories specified")); + else + { + struct stat st; + if (stat (optarg, &st) != 0) + error (EXIT_FAILURE, errno, _("accessing %s"), quote (optarg)); + if (! S_ISDIR (st.st_mode)) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (optarg)); + } + target_directory = optarg; + break; + case 'T': + no_target_directory = true; + break; + case 'v': + verbose = true; + break; + case 'S': + make_backups = true; + backup_suffix_string = optarg; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + break; + } + } + + n_files = argc - optind; + file = argv + optind; + + if (n_files <= 0) + { + error (0, 0, _("missing file operand")); + usage (EXIT_FAILURE); + } + + if (no_target_directory) + { + if (target_directory) + error (EXIT_FAILURE, 0, + _("Cannot combine --target-directory " + "and --no-target-directory")); + if (n_files != 2) + { + if (n_files < 2) + error (0, 0, + _("missing destination file operand after %s"), + quote (file[0])); + else + error (0, 0, _("extra operand %s"), quote (file[2])); + usage (EXIT_FAILURE); + } + } + else if (!target_directory) + { + if (n_files < 2) + target_directory = "."; + else if (2 <= n_files && target_directory_operand (file[n_files - 1])) + target_directory = file[--n_files]; + else if (2 < n_files) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (file[n_files - 1])); + } + + if (backup_suffix_string) + simple_backup_suffix = xstrdup (backup_suffix_string); + + backup_type = (make_backups + ? xget_version (_("backup type"), version_control_string) + : no_backups); + + if (target_directory) + { + int i; + ok = true; + for (i = 0; i < n_files; ++i) + { + char *dest_base; + char *dest = file_name_concat (target_directory, + last_component (file[i]), + &dest_base); + strip_trailing_slashes (dest_base); + ok &= do_link (file[i], dest); + free (dest); + } + } + else + ok = do_link (file[0], file[1]); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/logname.c b/src/logname.c new file mode 100644 index 0000000..dc82967 --- /dev/null +++ b/src/logname.c @@ -0,0 +1,91 @@ +/* logname -- print user's login name + Copyright (C) 1990-1997, 1999-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "long-options.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "logname" + +#define AUTHORS "FIXME: unknown" + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]\n"), program_name); + fputs (_("\ +Print the name of the current user.\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + char *cp; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (optind < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind])); + usage (EXIT_FAILURE); + } + + /* POSIX requires using getlogin (or equivalent code). */ + cp = getlogin (); + if (cp) + { + puts (cp); + exit (EXIT_SUCCESS); + } + /* POSIX prohibits using a fallback technique. */ + + error (0, 0, _("no login name")); + exit (EXIT_FAILURE); +} diff --git a/src/ls-dir.c b/src/ls-dir.c new file mode 100644 index 0000000..85fe242 --- /dev/null +++ b/src/ls-dir.c @@ -0,0 +1,2 @@ +#include "ls.h" +int ls_mode = LS_MULTI_COL; diff --git a/src/ls-ls.c b/src/ls-ls.c new file mode 100644 index 0000000..f33fbbc --- /dev/null +++ b/src/ls-ls.c @@ -0,0 +1,2 @@ +#include "ls.h" +int ls_mode = LS_LS; diff --git a/src/ls-vdir.c b/src/ls-vdir.c new file mode 100644 index 0000000..36ebf91 --- /dev/null +++ b/src/ls-vdir.c @@ -0,0 +1,2 @@ +#include "ls.h" +int ls_mode = LS_LONG_FORMAT; diff --git a/src/ls.c b/src/ls.c new file mode 100644 index 0000000..3d48900 --- /dev/null +++ b/src/ls.c @@ -0,0 +1,4430 @@ +/* `dir', `vdir' and `ls' directory listing programs for GNU. + Copyright (C) 85, 88, 90, 91, 1995-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* If ls_mode is LS_MULTI_COL, + the multi-column format is the default regardless + of the type of output device. + This is for the `dir' program. + + If ls_mode is LS_LONG_FORMAT, + the long format is the default regardless of the + type of output device. + This is for the `vdir' program. + + If ls_mode is LS_LS, + 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. */ + +/* Color support by Peter Anvin and Dennis + Flaherty based on original patches by + Greg Lee . */ + +#include +#include + +#if HAVE_TERMIOS_H +# include +#endif +#if HAVE_STROPTS_H +# include +#endif +#if HAVE_SYS_IOCTL_H +# include +#endif + +#ifdef WINSIZE_IN_PTEM +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is + present. */ +#ifndef SA_NOCLDSTOP +# define SA_NOCLDSTOP 0 +# define sigprocmask(How, Set, Oset) /* empty */ +# define sigset_t int +# if ! HAVE_SIGINTERRUPT +# define siginterrupt(sig, flag) /* empty */ +# endif +#endif +#ifndef SA_RESTART +# define SA_RESTART 0 +#endif + +#include "system.h" +#include + +#include "acl.h" +#include "argmatch.h" +#include "dev-ino.h" +#include "dirfd.h" +#include "error.h" +#include "filenamecat.h" +#include "hard-locale.h" +#include "hash.h" +#include "human.h" +#include "filemode.h" +#include "inttostr.h" +#include "ls.h" +#include "lstat.h" +#include "mbswidth.h" +#include "mpsort.h" +#include "obstack.h" +#include "quote.h" +#include "quotearg.h" +#include "same.h" +#include "stat-time.h" +#include "strftime.h" +#include "strverscmp.h" +#include "wcwidth.h" +#include "xstrtol.h" +#include "xreadlink.h" + +#define PROGRAM_NAME (ls_mode == LS_LS ? "ls" \ + : (ls_mode == LS_MULTI_COL \ + ? "dir" : "vdir")) + +#define AUTHORS "Richard Stallman", "David MacKenzie" + +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free + +/* Return an int indicating the result of comparing two integers. + Subtracting doesn't always work, due to overflow. */ +#define longdiff(a, b) ((a) < (b) ? -1 : (a) > (b)) + +#if ! HAVE_STRUCT_STAT_ST_AUTHOR +# define st_author st_uid +#endif + +enum filetype + { + unknown, + fifo, + chardev, + directory, + blockdev, + normal, + symbolic_link, + sock, + whiteout, + arg_directory + }; + +/* Display letters and indicators for each filetype. + Keep these in sync with enum filetype. */ +static char const filetype_letter[] = "?pcdb-lswd"; + +/* Ensure that filetype and filetype_letter have the same + number of elements. */ +verify (sizeof filetype_letter - 1 == arg_directory + 1); + +#define FILETYPE_INDICATORS \ + { \ + C_ORPHAN, C_FIFO, C_CHR, C_DIR, C_BLK, C_FILE, \ + C_LINK, C_SOCK, C_FILE, C_DIR \ + } + + +struct fileinfo + { + /* The file name. */ + char *name; + + /* For symbolic link, name of the file linked to, otherwise zero. */ + char *linkname; + + struct stat stat; + + enum filetype filetype; + + /* For symbolic link and long listing, st_mode of file linked to, otherwise + zero. */ + mode_t linkmode; + + bool stat_ok; + + /* For symbolic link and color printing, true if linked-to file + exists, otherwise false. */ + bool linkok; + +#if USE_ACL + /* For long listings, true if the file has an access control list. */ + bool have_acl; +#endif + }; + +#if USE_ACL +# define FILE_HAS_ACL(F) ((F)->have_acl) +#else +# define FILE_HAS_ACL(F) 0 +#endif + +#define LEN_STR_PAIR(s) sizeof (s) - 1, s + +/* Null is a valid character in a color indicator (think about Epson + printers, for example) so we have to use a length/buffer string + type. */ + +struct bin_str + { + size_t len; /* Number of bytes */ + const char *string; /* Pointer to the same */ + }; + +char *getgroup (); +char *getuser (); + +#if ! HAVE_TCGETPGRP +# define tcgetpgrp(Fd) 0 +#endif + +static size_t quote_name (FILE *out, const char *name, + struct quoting_options const *options, + size_t *width); +static char *make_link_name (char const *name, char const *linkname); +static int decode_switches (int argc, char **argv); +static bool file_ignored (char const *name); +static uintmax_t gobble_file (char const *name, enum filetype type, + ino_t inode, bool command_line_arg, + char const *dirname); +static void print_color_indicator (const char *name, mode_t mode, int linkok, + bool stat_ok, enum filetype type); +static void put_indicator (const struct bin_str *ind); +static void add_ignore_pattern (const char *pattern); +static void attach (char *dest, const char *dirname, const char *name); +static void clear_files (void); +static void extract_dirs_from_files (char const *dirname, + bool command_line_arg); +static void get_link_name (char const *filename, struct fileinfo *f, + bool command_line_arg); +static void indent (size_t from, size_t to); +static size_t calculate_columns (bool by_columns); +static void print_current_files (void); +static void print_dir (char const *name, char const *realname, + bool command_line_arg); +static void print_file_name_and_frills (const struct fileinfo *f); +static void print_horizontal (void); +static int format_user_width (uid_t u); +static int format_group_width (gid_t g); +static void print_long_format (const struct fileinfo *f); +static void print_many_per_line (void); +static void print_name_with_quoting (const char *p, mode_t mode, + int linkok, bool stat_ok, + enum filetype type, + struct obstack *stack); +static void prep_non_filename_text (void); +static void print_type_indicator (bool stat_ok, mode_t mode, + enum filetype type); +static void print_with_commas (void); +static void queue_directory (char const *name, char const *realname, + bool command_line_arg); +static void sort_files (void); +static void parse_ls_color (void); +void usage (int status); + +/* The name this program was run with. */ +char *program_name; + +/* Initial size of hash table. + Most hierarchies are likely to be shallower than this. */ +#define INITIAL_TABLE_SIZE 30 + +/* The set of `active' directories, from the current command-line argument + to the level in the hierarchy at which files are being listed. + A directory is represented by its device and inode numbers (struct dev_ino). + A directory is added to this set when ls begins listing it or its + entries, and it is removed from the set just after ls has finished + processing it. This set is used solely to detect loops, e.g., with + mkdir loop; cd loop; ln -s ../loop sub; ls -RL */ +static Hash_table *active_dir_set; + +#define LOOP_DETECT (!!active_dir_set) + +/* The table of files in the current directory: + + `cwd_file' points to a vector of `struct fileinfo', one per file. + `cwd_n_alloc' is the number of elements space has been allocated for. + `cwd_n_used' is the number actually in use. */ + +/* Address of block containing the files that are described. */ +static struct fileinfo *cwd_file; + +/* Length of block that `cwd_file' points to, measured in files. */ +static size_t cwd_n_alloc; + +/* Index of first unused slot in `cwd_file'. */ +static size_t cwd_n_used; + +/* Vector of pointers to files, in proper sorted order, and the number + of entries allocated for it. */ +static void **sorted_file; +static size_t sorted_file_alloc; + +/* When true, in a color listing, color each symlink name according to the + type of file it points to. Otherwise, color them according to the `ln' + directive in LS_COLORS. Dangling (orphan) symlinks are treated specially, + regardless. This is set when `ln=target' appears in LS_COLORS. */ + +static bool color_symlink_as_referent; + +/* mode of appropriate file for colorization */ +#define FILE_OR_LINK_MODE(File) \ + ((color_symlink_as_referent & (File)->linkok) \ + ? (File)->linkmode : (File)->stat.st_mode) + + +/* 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; + bool command_line_arg; + struct pending *next; + }; + +static struct pending *pending_dirs; + +/* Current time in seconds and nanoseconds since 1970, updated as + needed when deciding whether a file is recent. */ + +static time_t current_time = TYPE_MINIMUM (time_t); +static int current_time_ns = -1; + +/* Whether any of the files has an ACL. This affects the width of the + mode column. */ + +#if USE_ACL +static bool any_has_acl; +#else +enum { any_has_acl = false }; +#endif + +/* The number of columns to use for columns containing inode numbers, + block sizes, link counts, owners, groups, authors, major device + numbers, minor device numbers, and file sizes, respectively. */ + +static int inode_number_width; +static int block_size_width; +static int nlink_width; +static int owner_width; +static int group_width; +static int author_width; +static int major_device_number_width; +static int minor_device_number_width; +static int file_size_width; + +/* 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 (and other options that imply -l), -1, -C, -x and -m control + this parameter. */ + +enum format + { + long_format, /* -l and other options that imply -l */ + one_per_line, /* -1 */ + many_per_line, /* -C */ + horizontal, /* -x */ + with_commas /* -m */ + }; + +static enum format format; + +/* `full-iso' uses full ISO-style dates and times. `long-iso' uses longer + ISO-style time stamps, though shorter than `full-iso'. `iso' uses shorter + ISO-style time stamps. `locale' uses locale-dependent time stamps. */ +enum time_style + { + full_iso_time_style, /* --time-style=full-iso */ + long_iso_time_style, /* --time-style=long-iso */ + iso_time_style, /* --time-style=iso */ + locale_time_style /* --time-style=locale */ + }; + +static char const *const time_style_args[] = +{ + "full-iso", "long-iso", "iso", "locale", NULL +}; +static enum time_style const time_style_types[] = +{ + full_iso_time_style, long_iso_time_style, iso_time_style, + locale_time_style +}; +ARGMATCH_VERIFY (time_style_args, time_style_types); + +/* Type of time to print or sort by. Controlled by -c and -u. + The values of each item of this enum are important since they are + used as indices in the sort functions array (see sort_files()). */ + +enum time_type + { + time_mtime, /* default */ + time_ctime, /* -c */ + time_atime, /* -u */ + time_numtypes /* the number of elements of this enum */ + }; + +static enum time_type time_type; + +/* The file characteristic to sort by. Controlled by -t, -S, -U, -X, -v. + The values of each item of this enum are important since they are + used as indices in the sort functions array (see sort_files()). */ + +enum sort_type + { + sort_none = -1, /* -U */ + sort_name, /* default */ + sort_extension, /* -X */ + sort_size, /* -S */ + sort_version, /* -v */ + sort_time, /* -t */ + sort_numtypes /* the number of elements of this enum */ + }; + +static enum sort_type sort_type; + +/* Direction of sort. + false means highest first if numeric, + lowest first if alphabetic; + these are the defaults. + true means the opposite order in each case. -r */ + +static bool sort_reverse; + +/* True means to display owner information. -g turns this off. */ + +static bool print_owner = true; + +/* True means to display author information. */ + +static bool print_author; + +/* True means to display group information. -G and -o turn this off. */ + +static bool print_group = true; + +/* True means print the user and group id's as numbers rather + than as names. -n */ + +static bool numeric_ids; + +/* True means mention the size in blocks of each file. -s */ + +static bool print_block_size; + +/* Human-readable options for output. */ +static int human_output_opts; + +/* The units to use when printing sizes other than file sizes. */ +static uintmax_t output_block_size; + +/* Likewise, but for file sizes. */ +static uintmax_t file_output_block_size = 1; + +/* Follow the output with a special string. Using this format, + Emacs' dired mode starts up twice as fast, and can handle all + strange characters in file names. */ +static bool dired; + +/* `none' means don't mention the type of files. + `slash' means mention directories only, with a '/'. + `file_type' means mention file types. + `classify' means mention file types and mark executables. + + Controlled by -F, -p, and --indicator-style. */ + +enum indicator_style + { + none, /* --indicator-style=none */ + slash, /* -p, --indicator-style=slash */ + file_type, /* --indicator-style=file-type */ + classify /* -F, --indicator-style=classify */ + }; + +static enum indicator_style indicator_style; + +/* Names of indicator styles. */ +static char const *const indicator_style_args[] = +{ + "none", "slash", "file-type", "classify", NULL +}; +static enum indicator_style const indicator_style_types[] = +{ + none, slash, file_type, classify +}; +ARGMATCH_VERIFY (indicator_style_args, indicator_style_types); + +/* True means use colors to mark types. Also define the different + colors as well as the stuff for the LS_COLORS environment variable. + The LS_COLORS variable is now in a termcap-like format. */ + +static bool print_with_color; + +enum color_type + { + color_never, /* 0: default or --color=never */ + color_always, /* 1: --color=always */ + color_if_tty /* 2: --color=tty */ + }; + +enum Dereference_symlink + { + DEREF_UNDEFINED = 1, + DEREF_NEVER, + DEREF_COMMAND_LINE_ARGUMENTS, /* -H */ + DEREF_COMMAND_LINE_SYMLINK_TO_DIR, /* the default, in certain cases */ + DEREF_ALWAYS /* -L */ + }; + +enum indicator_no + { + C_LEFT, C_RIGHT, C_END, C_NORM, C_FILE, C_DIR, C_LINK, C_FIFO, C_SOCK, + C_BLK, C_CHR, C_MISSING, C_ORPHAN, C_EXEC, C_DOOR, C_SETUID, C_SETGID, + C_STICKY, C_OTHER_WRITABLE, C_STICKY_OTHER_WRITABLE + }; + +static const char *const indicator_name[]= + { + "lc", "rc", "ec", "no", "fi", "di", "ln", "pi", "so", + "bd", "cd", "mi", "or", "ex", "do", "su", "sg", "st", + "ow", "tw", NULL + }; + +struct color_ext_type + { + struct bin_str ext; /* The extension we're looking for */ + struct bin_str seq; /* The sequence to output when we do */ + struct color_ext_type *next; /* Next in list */ + }; + +static struct bin_str color_indicator[] = + { + { LEN_STR_PAIR ("\033[") }, /* lc: Left of color sequence */ + { LEN_STR_PAIR ("m") }, /* rc: Right of color sequence */ + { 0, NULL }, /* ec: End color (replaces lc+no+rc) */ + { LEN_STR_PAIR ("0") }, /* no: Normal */ + { LEN_STR_PAIR ("0") }, /* fi: File: default */ + { LEN_STR_PAIR ("01;34") }, /* di: Directory: bright blue */ + { LEN_STR_PAIR ("01;36") }, /* ln: Symlink: bright cyan */ + { LEN_STR_PAIR ("33") }, /* pi: Pipe: yellow/brown */ + { LEN_STR_PAIR ("01;35") }, /* so: Socket: bright magenta */ + { LEN_STR_PAIR ("01;33") }, /* bd: Block device: bright yellow */ + { LEN_STR_PAIR ("01;33") }, /* cd: Char device: bright yellow */ + { 0, NULL }, /* mi: Missing file: undefined */ + { 0, NULL }, /* or: Orphaned symlink: undefined */ + { LEN_STR_PAIR ("01;32") }, /* ex: Executable: bright green */ + { LEN_STR_PAIR ("01;35") }, /* do: Door: bright magenta */ + { LEN_STR_PAIR ("37;41") }, /* su: setuid: white on red */ + { LEN_STR_PAIR ("30;43") }, /* sg: setgid: black on yellow */ + { LEN_STR_PAIR ("37;44") }, /* st: sticky: black on blue */ + { LEN_STR_PAIR ("34;42") }, /* ow: other-writable: blue on green */ + { LEN_STR_PAIR ("30;42") }, /* tw: ow w/ sticky: black on green */ + }; + +/* FIXME: comment */ +static struct color_ext_type *color_ext_list = NULL; + +/* Buffer for color sequences */ +static char *color_buf; + +/* True means to check for orphaned symbolic link, for displaying + colors. */ + +static bool check_symlink_color; + +/* True means mention the inode number of each file. -i */ + +static bool print_inode; + +/* What to do with symbolic links. Affected by -d, -F, -H, -l (and + other options that imply -l), and -L. */ + +static enum Dereference_symlink dereference; + +/* True means when a directory is found, display info on its + contents. -R */ + +static bool recursive; + +/* True means when an argument is a directory name, display info + on it itself. -d */ + +static bool immediate_dirs; + +/* True means that directories are grouped before files. */ + +static bool directories_first; + +/* Which files to ignore. */ + +static enum +{ + /* Ignore files whose names start with `.', and files specified by + --hide and --ignore. */ + IGNORE_DEFAULT, + + /* Ignore `.', `..', and files specified by --ignore. */ + IGNORE_DOT_AND_DOTDOT, + + /* Ignore only files specified by --ignore. */ + IGNORE_MINIMAL +} ignore_mode; + +/* A linked list of shell-style globbing patterns. If a non-argument + file name matches any of these patterns, it is ignored. + Controlled by -I. Multiple -I options accumulate. + The -B option adds `*~' and `.*~' to this list. */ + +struct ignore_pattern + { + const char *pattern; + struct ignore_pattern *next; + }; + +static struct ignore_pattern *ignore_patterns; + +/* Similar to IGNORE_PATTERNS, except that -a or -A causes this + variable itself to be ignored. */ +static struct ignore_pattern *hide_patterns; + +/* True means output nongraphic chars in file names as `?'. + (-q, --hide-control-chars) + qmark_funny_chars and the quoting style (-Q, --quoting-style=WORD) are + independent. The algorithm is: first, obey the quoting style to get a + string representing the file name; then, if qmark_funny_chars is set, + replace all nonprintable chars in that string with `?'. It's necessary + to replace nonprintable chars even in quoted strings, because we don't + want to mess up the terminal if control chars get sent to it, and some + quoting methods pass through control chars as-is. */ +static bool qmark_funny_chars; + +/* Quoting options for file and dir name output. */ + +static struct quoting_options *filename_quoting_options; +static struct quoting_options *dirname_quoting_options; + +/* The number of chars per hardware tab stop. Setting this to zero + inhibits the use of TAB characters for separating columns. -T */ +static size_t tabsize; + +/* True means print each directory name before listing it. */ + +static bool print_dir_name; + +/* The line length to use for breaking lines in many-per-line format. + Can be set with -w. */ + +static size_t line_length; + +/* If true, the file listing format requires that stat be called on + each file. */ + +static bool format_needs_stat; + +/* Similar to `format_needs_stat', but set if only the file type is + needed. */ + +static bool format_needs_type; + +/* An arbitrary limit on the number of bytes in a printed time stamp. + This is set to a relatively small value to avoid the need to worry + about denial-of-service attacks on servers that run "ls" on behalf + of remote clients. 1000 bytes should be enough for any practical + time stamp format. */ + +enum { TIME_STAMP_LEN_MAXIMUM = MAX (1000, INT_STRLEN_BOUND (time_t)) }; + +/* strftime formats for non-recent and recent files, respectively, in + -l output. */ + +static char const *long_time_format[2] = + { + /* strftime format for non-recent files (older than 6 months), in + -l output. This should contain the year, month and day (at + least), in an order that is understood by people in your + locale's territory. Please try to keep the number of used + screen columns small, because many people work in windows with + only 80 columns. But make this as wide as the other string + below, for recent files. */ + N_("%b %e %Y"), + /* strftime format for recent files (younger than 6 months), in -l + output. This should contain the month, day and time (at + least), in an order that is understood by people in your + locale's territory. Please try to keep the number of used + screen columns small, because many people work in windows with + only 80 columns. But make this as wide as the other string + above, for non-recent files. */ + N_("%b %e %H:%M") + }; + +/* The set of signals that are caught. */ + +static sigset_t caught_signals; + +/* If nonzero, the value of the pending fatal signal. */ + +static sig_atomic_t volatile interrupt_signal; + +/* A count of the number of pending stop signals that have been received. */ + +static sig_atomic_t volatile stop_signal_count; + +/* Desired exit status. */ + +static int exit_status; + +/* Exit statuses. */ +enum + { + /* "ls" had a minor problem (e.g., it could not stat a directory + entry). */ + LS_MINOR_PROBLEM = 1, + + /* "ls" had more serious trouble. */ + LS_FAILURE = 2 + }; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + AUTHOR_OPTION = CHAR_MAX + 1, + BLOCK_SIZE_OPTION, + COLOR_OPTION, + DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR_OPTION, + FILE_TYPE_INDICATOR_OPTION, + FORMAT_OPTION, + FULL_TIME_OPTION, + GROUP_DIRECTORIES_FIRST_OPTION, + HIDE_OPTION, + INDICATOR_STYLE_OPTION, + + /* FIXME: --kilobytes is deprecated (but not -k); remove in late 2006 */ + KILOBYTES_LONG_OPTION, + + QUOTING_STYLE_OPTION, + SHOW_CONTROL_CHARS_OPTION, + SI_OPTION, + SORT_OPTION, + TIME_OPTION, + TIME_STYLE_OPTION +}; + +static struct option const long_options[] = +{ + {"all", no_argument, NULL, 'a'}, + {"escape", no_argument, NULL, 'b'}, + {"directory", no_argument, NULL, 'd'}, + {"dired", no_argument, NULL, 'D'}, + {"full-time", no_argument, NULL, FULL_TIME_OPTION}, + {"group-directories-first", no_argument, NULL, + GROUP_DIRECTORIES_FIRST_OPTION}, + {"human-readable", no_argument, NULL, 'h'}, + {"inode", no_argument, NULL, 'i'}, + {"kilobytes", no_argument, NULL, KILOBYTES_LONG_OPTION}, + {"numeric-uid-gid", no_argument, NULL, 'n'}, + {"no-group", no_argument, NULL, 'G'}, + {"hide-control-chars", no_argument, NULL, 'q'}, + {"reverse", no_argument, NULL, 'r'}, + {"size", no_argument, NULL, 's'}, + {"width", required_argument, NULL, 'w'}, + {"almost-all", no_argument, NULL, 'A'}, + {"ignore-backups", no_argument, NULL, 'B'}, + {"classify", no_argument, NULL, 'F'}, + {"file-type", no_argument, NULL, FILE_TYPE_INDICATOR_OPTION}, + {"si", no_argument, NULL, SI_OPTION}, + {"dereference-command-line", no_argument, NULL, 'H'}, + {"dereference-command-line-symlink-to-dir", no_argument, NULL, + DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR_OPTION}, + {"hide", required_argument, NULL, HIDE_OPTION}, + {"ignore", required_argument, NULL, 'I'}, + {"indicator-style", required_argument, NULL, INDICATOR_STYLE_OPTION}, + {"dereference", no_argument, NULL, 'L'}, + {"literal", no_argument, NULL, 'N'}, + {"quote-name", no_argument, NULL, 'Q'}, + {"quoting-style", required_argument, NULL, QUOTING_STYLE_OPTION}, + {"recursive", no_argument, NULL, 'R'}, + {"format", required_argument, NULL, FORMAT_OPTION}, + {"show-control-chars", no_argument, NULL, SHOW_CONTROL_CHARS_OPTION}, + {"sort", required_argument, NULL, SORT_OPTION}, + {"tabsize", required_argument, NULL, 'T'}, + {"time", required_argument, NULL, TIME_OPTION}, + {"time-style", required_argument, NULL, TIME_STYLE_OPTION}, + {"color", optional_argument, NULL, COLOR_OPTION}, + {"block-size", required_argument, NULL, BLOCK_SIZE_OPTION}, + {"author", no_argument, NULL, AUTHOR_OPTION}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +static char const *const format_args[] = +{ + "verbose", "long", "commas", "horizontal", "across", + "vertical", "single-column", NULL +}; +static enum format const format_types[] = +{ + long_format, long_format, with_commas, horizontal, horizontal, + many_per_line, one_per_line +}; +ARGMATCH_VERIFY (format_args, format_types); + +static char const *const sort_args[] = +{ + "none", "time", "size", "extension", "version", NULL +}; +static enum sort_type const sort_types[] = +{ + sort_none, sort_time, sort_size, sort_extension, sort_version +}; +ARGMATCH_VERIFY (sort_args, sort_types); + +static char const *const time_args[] = +{ + "atime", "access", "use", "ctime", "status", NULL +}; +static enum time_type const time_types[] = +{ + time_atime, time_atime, time_atime, time_ctime, time_ctime +}; +ARGMATCH_VERIFY (time_args, time_types); + +static char const *const color_args[] = +{ + /* force and none are for compatibility with another color-ls version */ + "always", "yes", "force", + "never", "no", "none", + "auto", "tty", "if-tty", NULL +}; +static enum color_type const color_types[] = +{ + color_always, color_always, color_always, + color_never, color_never, color_never, + color_if_tty, color_if_tty, color_if_tty +}; +ARGMATCH_VERIFY (color_args, color_types); + +/* Information about filling a column. */ +struct column_info +{ + bool valid_len; + size_t line_len; + size_t *col_arr; +}; + +/* Array with information about column filledness. */ +static struct column_info *column_info; + +/* Maximum number of columns ever possible for this display. */ +static size_t max_idx; + +/* The minimum width of a column is 3: 1 character for the name and 2 + for the separating white space. */ +#define MIN_COLUMN_WIDTH 3 + + +/* This zero-based index is used solely with the --dired option. + When that option is in effect, this counter is incremented for each + byte of output generated by this program so that the beginning + and ending indices (in that output) of every file name can be recorded + and later output themselves. */ +static size_t dired_pos; + +#define DIRED_PUTCHAR(c) do {putchar ((c)); ++dired_pos;} while (0) + +/* Write S to STREAM and increment DIRED_POS by S_LEN. */ +#define DIRED_FPUTS(s, stream, s_len) \ + do {fputs (s, stream); dired_pos += s_len;} while (0) + +/* Like DIRED_FPUTS, but for use when S is a literal string. */ +#define DIRED_FPUTS_LITERAL(s, stream) \ + do {fputs (s, stream); dired_pos += sizeof (s) - 1;} while (0) + +#define DIRED_INDENT() \ + do \ + { \ + if (dired) \ + DIRED_FPUTS_LITERAL (" ", stdout); \ + } \ + while (0) + +/* With --dired, store pairs of beginning and ending indices of filenames. */ +static struct obstack dired_obstack; + +/* With --dired, store pairs of beginning and ending indices of any + directory names that appear as headers (just before `total' line) + for lists of directory entries. Such directory names are seen when + listing hierarchies using -R and when a directory is listed with at + least one other command line argument. */ +static struct obstack subdired_obstack; + +/* Save the current index on the specified obstack, OBS. */ +#define PUSH_CURRENT_DIRED_POS(obs) \ + do \ + { \ + if (dired) \ + obstack_grow (obs, &dired_pos, sizeof (dired_pos)); \ + } \ + while (0) + +/* With -R, this stack is used to help detect directory cycles. + The device/inode pairs on this stack mirror the pairs in the + active_dir_set hash table. */ +static struct obstack dev_ino_obstack; + +/* Push a pair onto the device/inode stack. */ +#define DEV_INO_PUSH(Dev, Ino) \ + do \ + { \ + struct dev_ino *di; \ + obstack_blank (&dev_ino_obstack, sizeof (struct dev_ino)); \ + di = -1 + (struct dev_ino *) obstack_next_free (&dev_ino_obstack); \ + di->st_dev = (Dev); \ + di->st_ino = (Ino); \ + } \ + while (0) + +/* Pop a dev/ino struct off the global dev_ino_obstack + and return that struct. */ +static struct dev_ino +dev_ino_pop (void) +{ + assert (sizeof (struct dev_ino) <= obstack_object_size (&dev_ino_obstack)); + obstack_blank (&dev_ino_obstack, -(int) (sizeof (struct dev_ino))); + return *(struct dev_ino *) obstack_next_free (&dev_ino_obstack); +} + +#define ASSERT_MATCHING_DEV_INO(Name, Di) \ + do \ + { \ + struct stat sb; \ + assert (Name); \ + assert (0 <= stat (Name, &sb)); \ + assert (sb.st_dev == Di.st_dev); \ + assert (sb.st_ino == Di.st_ino); \ + } \ + while (0) + + +/* Write to standard output PREFIX, followed by the quoting style and + a space-separated list of the integers stored in OS all on one line. */ + +static void +dired_dump_obstack (const char *prefix, struct obstack *os) +{ + size_t n_pos; + + n_pos = obstack_object_size (os) / sizeof (dired_pos); + if (n_pos > 0) + { + size_t i; + size_t *pos; + + pos = (size_t *) obstack_finish (os); + fputs (prefix, stdout); + for (i = 0; i < n_pos; i++) + printf (" %lu", (unsigned long int) pos[i]); + putchar ('\n'); + } +} + +static size_t +dev_ino_hash (void const *x, size_t table_size) +{ + struct dev_ino const *p = x; + return (uintmax_t) p->st_ino % table_size; +} + +static bool +dev_ino_compare (void const *x, void const *y) +{ + struct dev_ino const *a = x; + struct dev_ino const *b = y; + return SAME_INODE (*a, *b) ? true : false; +} + +static void +dev_ino_free (void *x) +{ + free (x); +} + +/* Add the device/inode pair (P->st_dev/P->st_ino) to the set of + active directories. Return true if there is already a matching + entry in the table. */ + +static bool +visit_dir (dev_t dev, ino_t ino) +{ + struct dev_ino *ent; + struct dev_ino *ent_from_table; + bool found_match; + + ent = xmalloc (sizeof *ent); + ent->st_ino = ino; + ent->st_dev = dev; + + /* Attempt to insert this entry into the table. */ + ent_from_table = hash_insert (active_dir_set, ent); + + if (ent_from_table == NULL) + { + /* Insertion failed due to lack of memory. */ + xalloc_die (); + } + + found_match = (ent_from_table != ent); + + if (found_match) + { + /* ent was not inserted, so free it. */ + free (ent); + } + + return found_match; +} + +static void +free_pending_ent (struct pending *p) +{ + free (p->name); + free (p->realname); + free (p); +} + +static bool +is_colored (enum indicator_no type) +{ + size_t len = color_indicator[type].len; + char const *s = color_indicator[type].string; + return ! (len == 0 + || (len == 1 && strncmp (s, "0", 1) == 0) + || (len == 2 && strncmp (s, "00", 2) == 0)); +} + +static void +restore_default_color (void) +{ + put_indicator (&color_indicator[C_LEFT]); + put_indicator (&color_indicator[C_RIGHT]); +} + +/* An ordinary signal was received; arrange for the program to exit. */ + +static void +sighandler (int sig) +{ + if (! SA_NOCLDSTOP) + signal (sig, SIG_IGN); + if (! interrupt_signal) + interrupt_signal = sig; +} + +/* A SIGTSTP was received; arrange for the program to suspend itself. */ + +static void +stophandler (int sig) +{ + if (! SA_NOCLDSTOP) + signal (sig, stophandler); + if (! interrupt_signal) + stop_signal_count++; +} + +/* Process any pending signals. If signals are caught, this function + should be called periodically. Ideally there should never be an + unbounded amount of time when signals are not being processed. + Signal handling can restore the default colors, so callers must + immediately change colors after invoking this function. */ + +static void +process_signals (void) +{ + while (interrupt_signal | stop_signal_count) + { + int sig; + int stops; + sigset_t oldset; + + restore_default_color (); + fflush (stdout); + + sigprocmask (SIG_BLOCK, &caught_signals, &oldset); + + /* Reload interrupt_signal and stop_signal_count, in case a new + signal was handled before sigprocmask took effect. */ + sig = interrupt_signal; + stops = stop_signal_count; + + /* SIGTSTP is special, since the application can receive that signal + more than once. In this case, don't set the signal handler to the + default. Instead, just raise the uncatchable SIGSTOP. */ + if (stops) + { + stop_signal_count = stops - 1; + sig = SIGSTOP; + } + else + signal (sig, SIG_DFL); + + /* Exit or suspend the program. */ + raise (sig); + sigprocmask (SIG_SETMASK, &oldset, NULL); + + /* If execution reaches here, then the program has been + continued (after being suspended). */ + } +} + +int +main (int argc, char **argv) +{ + int i; + struct pending *thispend; + int n_files; + + /* The signals that are trapped, and the number of such signals. */ + static int const sig[] = + { + /* This one is handled specially. */ + SIGTSTP, + + /* The usual suspects. */ + SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, +#ifdef SIGPOLL + SIGPOLL, +#endif +#ifdef SIGPROF + SIGPROF, +#endif +#ifdef SIGVTALRM + SIGVTALRM, +#endif +#ifdef SIGXCPU + SIGXCPU, +#endif +#ifdef SIGXFSZ + SIGXFSZ, +#endif + }; + enum { nsigs = sizeof sig / sizeof sig[0] }; + +#if ! SA_NOCLDSTOP + bool caught_sig[nsigs]; +#endif + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (LS_FAILURE); + atexit (close_stdout); + +#define N_ENTRIES(Array) (sizeof Array / sizeof *(Array)) + assert (N_ENTRIES (color_indicator) + 1 == N_ENTRIES (indicator_name)); + + exit_status = EXIT_SUCCESS; + print_dir_name = true; + pending_dirs = NULL; + + i = decode_switches (argc, argv); + + if (print_with_color) + parse_ls_color (); + + /* Test print_with_color again, because the call to parse_ls_color + may have just reset it -- e.g., if LS_COLORS is invalid. */ + if (print_with_color) + { + /* Avoid following symbolic links when possible. */ + if (is_colored (C_ORPHAN) + || is_colored (C_EXEC) + || (is_colored (C_MISSING) && format == long_format)) + check_symlink_color = true; + + /* If the standard output is a controlling terminal, watch out + for signals, so that the colors can be restored to the + default state if "ls" is suspended or interrupted. */ + + if (0 <= tcgetpgrp (STDOUT_FILENO)) + { + int j; +#if SA_NOCLDSTOP + struct sigaction act; + + sigemptyset (&caught_signals); + for (j = 0; j < nsigs; j++) + { + sigaction (sig[j], NULL, &act); + if (act.sa_handler != SIG_IGN) + sigaddset (&caught_signals, sig[j]); + } + + act.sa_mask = caught_signals; + act.sa_flags = SA_RESTART; + + for (j = 0; j < nsigs; j++) + if (sigismember (&caught_signals, sig[j])) + { + act.sa_handler = sig[j] == SIGTSTP ? stophandler : sighandler; + sigaction (sig[j], &act, NULL); + } +#else + for (j = 0; j < nsigs; j++) + { + caught_sig[j] = (signal (sig[j], SIG_IGN) != SIG_IGN); + if (caught_sig[j]) + { + signal (sig[j], sig[j] == SIGTSTP ? stophandler : sighandler); + siginterrupt (sig[j], 0); + } + } +#endif + } + + prep_non_filename_text (); + } + + if (dereference == DEREF_UNDEFINED) + dereference = ((immediate_dirs + || indicator_style == classify + || format == long_format) + ? DEREF_NEVER + : DEREF_COMMAND_LINE_SYMLINK_TO_DIR); + + /* When using -R, initialize a data structure we'll use to + detect any directory cycles. */ + if (recursive) + { + active_dir_set = hash_initialize (INITIAL_TABLE_SIZE, NULL, + dev_ino_hash, + dev_ino_compare, + dev_ino_free); + if (active_dir_set == NULL) + xalloc_die (); + + obstack_init (&dev_ino_obstack); + } + + format_needs_stat = sort_type == sort_time || sort_type == sort_size + || format == long_format + || print_block_size; + format_needs_type = (! format_needs_stat + && (recursive + || print_with_color + || indicator_style != none + || directories_first)); + + if (dired) + { + obstack_init (&dired_obstack); + obstack_init (&subdired_obstack); + } + + cwd_n_alloc = 100; + cwd_file = xnmalloc (cwd_n_alloc, sizeof *cwd_file); + cwd_n_used = 0; + + clear_files (); + + n_files = argc - i; + + if (n_files <= 0) + { + if (immediate_dirs) + gobble_file (".", directory, NOT_AN_INODE_NUMBER, true, ""); + else + queue_directory (".", NULL, true); + } + else + do + gobble_file (argv[i++], unknown, NOT_AN_INODE_NUMBER, true, ""); + while (i < argc); + + if (cwd_n_used) + { + sort_files (); + if (!immediate_dirs) + extract_dirs_from_files (NULL, true); + /* `cwd_n_used' might be zero now. */ + } + + /* In the following if/else blocks, it is sufficient to test `pending_dirs' + (and not pending_dirs->name) because there may be no markers in the queue + at this point. A marker may be enqueued when extract_dirs_from_files is + called with a non-empty string or via print_dir. */ + if (cwd_n_used) + { + print_current_files (); + if (pending_dirs) + DIRED_PUTCHAR ('\n'); + } + else if (n_files <= 1 && pending_dirs && pending_dirs->next == 0) + print_dir_name = false; + + while (pending_dirs) + { + thispend = pending_dirs; + pending_dirs = pending_dirs->next; + + if (LOOP_DETECT) + { + if (thispend->name == NULL) + { + /* thispend->name == NULL means this is a marker entry + indicating we've finished processing the directory. + Use its dev/ino numbers to remove the corresponding + entry from the active_dir_set hash table. */ + struct dev_ino di = dev_ino_pop (); + struct dev_ino *found = hash_delete (active_dir_set, &di); + /* ASSERT_MATCHING_DEV_INO (thispend->realname, di); */ + assert (found); + dev_ino_free (found); + free_pending_ent (thispend); + continue; + } + } + + print_dir (thispend->name, thispend->realname, + thispend->command_line_arg); + + free_pending_ent (thispend); + print_dir_name = true; + } + + if (print_with_color) + { + int j; + + restore_default_color (); + fflush (stdout); + + /* Restore the default signal handling. */ +#if SA_NOCLDSTOP + for (j = 0; j < nsigs; j++) + if (sigismember (&caught_signals, sig[j])) + signal (sig[j], SIG_DFL); +#else + for (j = 0; j < nsigs; j++) + if (caught_sig[j]) + signal (sig[j], SIG_DFL); +#endif + + /* Act on any signals that arrived before the default was restored. + This can process signals out of order, but there doesn't seem to + be an easy way to do them in order, and the order isn't that + important anyway. */ + for (j = stop_signal_count; j; j--) + raise (SIGSTOP); + j = interrupt_signal; + if (j) + raise (j); + } + + if (dired) + { + /* No need to free these since we're about to exit. */ + dired_dump_obstack ("//DIRED//", &dired_obstack); + dired_dump_obstack ("//SUBDIRED//", &subdired_obstack); + printf ("//DIRED-OPTIONS// --quoting-style=%s\n", + quoting_style_args[get_quoting_style (filename_quoting_options)]); + } + + if (LOOP_DETECT) + { + assert (hash_get_n_entries (active_dir_set) == 0); + hash_free (active_dir_set); + } + + exit (exit_status); +} + +/* Set all the option flags according to the switches specified. + Return the index of the first non-option argument. */ + +static int +decode_switches (int argc, char **argv) +{ + int c; + char *time_style_option = NULL; + + /* Record whether there is an option specifying sort type. */ + bool sort_type_specified = false; + + qmark_funny_chars = false; + + /* initialize all switches to default settings */ + + switch (ls_mode) + { + case LS_MULTI_COL: + /* This is for the `dir' program. */ + format = many_per_line; + set_quoting_style (NULL, escape_quoting_style); + break; + + case LS_LONG_FORMAT: + /* This is for the `vdir' program. */ + format = long_format; + set_quoting_style (NULL, escape_quoting_style); + break; + + case LS_LS: + /* This is for the `ls' program. */ + if (isatty (STDOUT_FILENO)) + { + format = many_per_line; + /* See description of qmark_funny_chars, above. */ + qmark_funny_chars = true; + } + else + { + format = one_per_line; + qmark_funny_chars = false; + } + break; + + default: + abort (); + } + + time_type = time_mtime; + sort_type = sort_name; + sort_reverse = false; + numeric_ids = false; + print_block_size = false; + indicator_style = none; + print_inode = false; + dereference = DEREF_UNDEFINED; + recursive = false; + immediate_dirs = false; + ignore_mode = IGNORE_DEFAULT; + ignore_patterns = NULL; + hide_patterns = NULL; + + /* FIXME: put this in a function. */ + { + char const *q_style = getenv ("QUOTING_STYLE"); + if (q_style) + { + int i = ARGMATCH (q_style, quoting_style_args, quoting_style_vals); + if (0 <= i) + set_quoting_style (NULL, quoting_style_vals[i]); + else + error (0, 0, + _("ignoring invalid value of environment variable QUOTING_STYLE: %s"), + quotearg (q_style)); + } + } + + { + char const *ls_block_size = getenv ("LS_BLOCK_SIZE"); + human_output_opts = human_options (ls_block_size, false, + &output_block_size); + if (ls_block_size || getenv ("BLOCK_SIZE")) + file_output_block_size = output_block_size; + } + + line_length = 80; + { + char const *p = getenv ("COLUMNS"); + if (p && *p) + { + unsigned long int tmp_ulong; + if (xstrtoul (p, NULL, 0, &tmp_ulong, NULL) == LONGINT_OK + && 0 < tmp_ulong && tmp_ulong <= SIZE_MAX) + { + line_length = tmp_ulong; + } + else + { + error (0, 0, + _("ignoring invalid width in environment variable COLUMNS: %s"), + quotearg (p)); + } + } + } + +#ifdef TIOCGWINSZ + { + struct winsize ws; + + if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 + && 0 < ws.ws_col && ws.ws_col == (size_t) ws.ws_col) + line_length = ws.ws_col; + } +#endif + + { + char const *p = getenv ("TABSIZE"); + tabsize = 8; + if (p) + { + unsigned long int tmp_ulong; + if (xstrtoul (p, NULL, 0, &tmp_ulong, NULL) == LONGINT_OK + && tmp_ulong <= SIZE_MAX) + { + tabsize = tmp_ulong; + } + else + { + error (0, 0, + _("ignoring invalid tab size in environment variable TABSIZE: %s"), + quotearg (p)); + } + } + } + + while ((c = getopt_long (argc, argv, + "abcdfghiklmnopqrstuvw:xABCDFGHI:LNQRST:UX1", + long_options, NULL)) != -1) + { + switch (c) + { + case 'a': + ignore_mode = IGNORE_MINIMAL; + break; + + case 'b': + set_quoting_style (NULL, escape_quoting_style); + break; + + case 'c': + time_type = time_ctime; + break; + + case 'd': + immediate_dirs = true; + break; + + case 'f': + /* Same as enabling -a -U and disabling -l -s. */ + ignore_mode = IGNORE_MINIMAL; + sort_type = sort_none; + sort_type_specified = true; + /* disable -l */ + if (format == long_format) + format = (isatty (STDOUT_FILENO) ? many_per_line : one_per_line); + print_block_size = false; /* disable -s */ + print_with_color = false; /* disable --color */ + break; + + case FILE_TYPE_INDICATOR_OPTION: /* --file-type */ + indicator_style = file_type; + break; + + case 'g': + format = long_format; + print_owner = false; + break; + + case 'h': + human_output_opts = human_autoscale | human_SI | human_base_1024; + file_output_block_size = output_block_size = 1; + break; + + case 'i': + print_inode = true; + break; + + case KILOBYTES_LONG_OPTION: + error (0, 0, + _("the --kilobytes option is deprecated; use -k instead")); + /* fall through */ + case 'k': + human_output_opts = 0; + file_output_block_size = output_block_size = 1024; + break; + + case 'l': + format = long_format; + break; + + case 'm': + format = with_commas; + break; + + case 'n': + numeric_ids = true; + format = long_format; + break; + + case 'o': /* Just like -l, but don't display group info. */ + format = long_format; + print_group = false; + break; + + case 'p': + indicator_style = slash; + break; + + case 'q': + qmark_funny_chars = true; + break; + + case 'r': + sort_reverse = true; + break; + + case 's': + print_block_size = true; + break; + + case 't': + sort_type = sort_time; + sort_type_specified = true; + break; + + case 'u': + time_type = time_atime; + break; + + case 'v': + sort_type = sort_version; + sort_type_specified = true; + break; + + case 'w': + { + unsigned long int tmp_ulong; + if (xstrtoul (optarg, NULL, 0, &tmp_ulong, NULL) != LONGINT_OK + || ! (0 < tmp_ulong && tmp_ulong <= SIZE_MAX)) + error (LS_FAILURE, 0, _("invalid line width: %s"), + quotearg (optarg)); + line_length = tmp_ulong; + break; + } + + case 'x': + format = horizontal; + break; + + case 'A': + if (ignore_mode == IGNORE_DEFAULT) + ignore_mode = IGNORE_DOT_AND_DOTDOT; + break; + + case 'B': + add_ignore_pattern ("*~"); + add_ignore_pattern (".*~"); + break; + + case 'C': + format = many_per_line; + break; + + case 'D': + dired = true; + break; + + case 'F': + indicator_style = classify; + break; + + case 'G': /* inhibit display of group info */ + print_group = false; + break; + + case 'H': + dereference = DEREF_COMMAND_LINE_ARGUMENTS; + break; + + case DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR_OPTION: + dereference = DEREF_COMMAND_LINE_SYMLINK_TO_DIR; + break; + + case 'I': + add_ignore_pattern (optarg); + break; + + case 'L': + dereference = DEREF_ALWAYS; + break; + + case 'N': + set_quoting_style (NULL, literal_quoting_style); + break; + + case 'Q': + set_quoting_style (NULL, c_quoting_style); + break; + + case 'R': + recursive = true; + break; + + case 'S': + sort_type = sort_size; + sort_type_specified = true; + break; + + case 'T': + { + unsigned long int tmp_ulong; + if (xstrtoul (optarg, NULL, 0, &tmp_ulong, NULL) != LONGINT_OK + || SIZE_MAX < tmp_ulong) + error (LS_FAILURE, 0, _("invalid tab size: %s"), + quotearg (optarg)); + tabsize = tmp_ulong; + break; + } + + case 'U': + sort_type = sort_none; + sort_type_specified = true; + break; + + case 'X': + sort_type = sort_extension; + sort_type_specified = true; + break; + + case '1': + /* -1 has no effect after -l. */ + if (format != long_format) + format = one_per_line; + break; + + case AUTHOR_OPTION: + print_author = true; + break; + + case HIDE_OPTION: + { + struct ignore_pattern *hide = xmalloc (sizeof *hide); + hide->pattern = optarg; + hide->next = hide_patterns; + hide_patterns = hide; + } + break; + + case SORT_OPTION: + sort_type = XARGMATCH ("--sort", optarg, sort_args, sort_types); + sort_type_specified = true; + break; + + case GROUP_DIRECTORIES_FIRST_OPTION: + directories_first = true; + break; + + case TIME_OPTION: + time_type = XARGMATCH ("--time", optarg, time_args, time_types); + break; + + case FORMAT_OPTION: + format = XARGMATCH ("--format", optarg, format_args, format_types); + break; + + case FULL_TIME_OPTION: + format = long_format; + time_style_option = "full-iso"; + break; + + case COLOR_OPTION: + { + int i; + if (optarg) + i = XARGMATCH ("--color", optarg, color_args, color_types); + else + /* Using --color with no argument is equivalent to using + --color=always. */ + i = color_always; + + print_with_color = (i == color_always + || (i == color_if_tty + && isatty (STDOUT_FILENO))); + + if (print_with_color) + { + /* Don't use TAB characters in output. Some terminal + emulators can't handle the combination of tabs and + color codes on the same line. */ + tabsize = 0; + } + break; + } + + case INDICATOR_STYLE_OPTION: + indicator_style = XARGMATCH ("--indicator-style", optarg, + indicator_style_args, + indicator_style_types); + break; + + case QUOTING_STYLE_OPTION: + set_quoting_style (NULL, + XARGMATCH ("--quoting-style", optarg, + quoting_style_args, + quoting_style_vals)); + break; + + case TIME_STYLE_OPTION: + time_style_option = optarg; + break; + + case SHOW_CONTROL_CHARS_OPTION: + qmark_funny_chars = false; + break; + + case BLOCK_SIZE_OPTION: + human_output_opts = human_options (optarg, true, &output_block_size); + file_output_block_size = output_block_size; + break; + + case SI_OPTION: + human_output_opts = human_autoscale | human_SI; + file_output_block_size = output_block_size = 1; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (LS_FAILURE); + } + } + + max_idx = MAX (1, line_length / MIN_COLUMN_WIDTH); + + filename_quoting_options = clone_quoting_options (NULL); + if (get_quoting_style (filename_quoting_options) == escape_quoting_style) + set_char_quoting (filename_quoting_options, ' ', 1); + if (file_type <= indicator_style) + { + char const *p; + for (p = "*=>@|" + indicator_style - file_type; *p; p++) + set_char_quoting (filename_quoting_options, *p, 1); + } + + dirname_quoting_options = clone_quoting_options (NULL); + set_char_quoting (dirname_quoting_options, ':', 1); + + /* --dired is meaningful only with --format=long (-l). + Otherwise, ignore it. FIXME: warn about this? + Alternatively, make --dired imply --format=long? */ + if (dired && format != long_format) + dired = false; + + /* If -c or -u is specified and not -l (or any other option that implies -l), + and no sort-type was specified, then sort by the ctime (-c) or atime (-u). + The behavior of ls when using either -c or -u but with neither -l nor -t + appears to be unspecified by POSIX. So, with GNU ls, `-u' alone means + sort by atime (this is the one that's not specified by the POSIX spec), + -lu means show atime and sort by name, -lut means show atime and sort + by atime. */ + + if ((time_type == time_ctime || time_type == time_atime) + && !sort_type_specified && format != long_format) + { + sort_type = sort_time; + } + + if (format == long_format) + { + char *style = time_style_option; + static char const posix_prefix[] = "posix-"; + + if (! style) + if (! (style = getenv ("TIME_STYLE"))) + style = "locale"; + + while (strncmp (style, posix_prefix, sizeof posix_prefix - 1) == 0) + { + if (! hard_locale (LC_TIME)) + return optind; + style += sizeof posix_prefix - 1; + } + + if (*style == '+') + { + char *p0 = style + 1; + char *p1 = strchr (p0, '\n'); + if (! p1) + p1 = p0; + else + { + if (strchr (p1 + 1, '\n')) + error (LS_FAILURE, 0, _("invalid time style format %s"), + quote (p0)); + *p1++ = '\0'; + } + long_time_format[0] = p0; + long_time_format[1] = p1; + } + else + switch (XARGMATCH ("time style", style, + time_style_args, + time_style_types)) + { + case full_iso_time_style: + long_time_format[0] = long_time_format[1] = + "%Y-%m-%d %H:%M:%S.%N %z"; + break; + + case long_iso_time_style: + case_long_iso_time_style: + long_time_format[0] = long_time_format[1] = "%Y-%m-%d %H:%M"; + break; + + case iso_time_style: + long_time_format[0] = "%Y-%m-%d "; + long_time_format[1] = "%m-%d %H:%M"; + break; + + case locale_time_style: + if (hard_locale (LC_TIME)) + { + /* Ensure that the locale has translations for both + formats. If not, fall back on long-iso format. */ + int i; + for (i = 0; i < 2; i++) + { + char const *locale_format = + dcgettext (NULL, long_time_format[i], LC_TIME); + if (locale_format == long_time_format[i]) + goto case_long_iso_time_style; + long_time_format[i] = locale_format; + } + } + } + } + + return optind; +} + +/* Parse a string as part of the LS_COLORS variable; this may involve + decoding all kinds of escape characters. If equals_end is set an + unescaped equal sign ends the string, otherwise only a : or \0 + does. Set *OUTPUT_COUNT to the number of bytes output. Return + true if successful. + + The resulting string is *not* null-terminated, but may contain + embedded nulls. + + Note that both dest and src are char **; on return they point to + the first free byte after the array and the character that ended + the input string, respectively. */ + +static bool +get_funky_string (char **dest, const char **src, bool equals_end, + size_t *output_count) +{ + char num; /* For numerical codes */ + size_t count; /* Something to count with */ + enum { + ST_GND, ST_BACKSLASH, ST_OCTAL, ST_HEX, ST_CARET, ST_END, ST_ERROR + } state; + const char *p; + char *q; + + p = *src; /* We don't want to double-indirect */ + q = *dest; /* the whole darn time. */ + + count = 0; /* No characters counted in yet. */ + num = 0; + + state = ST_GND; /* Start in ground state. */ + while (state < ST_END) + { + switch (state) + { + case ST_GND: /* Ground state (no escapes) */ + switch (*p) + { + case ':': + case '\0': + state = ST_END; /* End of string */ + break; + case '\\': + state = ST_BACKSLASH; /* Backslash scape sequence */ + ++p; + break; + case '^': + state = ST_CARET; /* Caret escape */ + ++p; + break; + case '=': + if (equals_end) + { + state = ST_END; /* End */ + break; + } + /* else fall through */ + default: + *(q++) = *(p++); + ++count; + break; + } + break; + + case ST_BACKSLASH: /* Backslash escaped character */ + switch (*p) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + state = ST_OCTAL; /* Octal sequence */ + num = *p - '0'; + break; + case 'x': + case 'X': + state = ST_HEX; /* Hex sequence */ + num = 0; + break; + case 'a': /* Bell */ + num = '\a'; + break; + case 'b': /* Backspace */ + num = '\b'; + break; + case 'e': /* Escape */ + num = 27; + break; + case 'f': /* Form feed */ + num = '\f'; + break; + case 'n': /* Newline */ + num = '\n'; + break; + case 'r': /* Carriage return */ + num = '\r'; + break; + case 't': /* Tab */ + num = '\t'; + break; + case 'v': /* Vtab */ + num = '\v'; + break; + case '?': /* Delete */ + num = 127; + break; + case '_': /* Space */ + num = ' '; + break; + case '\0': /* End of string */ + state = ST_ERROR; /* Error! */ + break; + default: /* Escaped character like \ ^ : = */ + num = *p; + break; + } + if (state == ST_BACKSLASH) + { + *(q++) = num; + ++count; + state = ST_GND; + } + ++p; + break; + + case ST_OCTAL: /* Octal sequence */ + if (*p < '0' || *p > '7') + { + *(q++) = num; + ++count; + state = ST_GND; + } + else + num = (num << 3) + (*(p++) - '0'); + break; + + case ST_HEX: /* Hex sequence */ + switch (*p) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + num = (num << 4) + (*(p++) - '0'); + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + num = (num << 4) + (*(p++) - 'a') + 10; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + num = (num << 4) + (*(p++) - 'A') + 10; + break; + default: + *(q++) = num; + ++count; + state = ST_GND; + break; + } + break; + + case ST_CARET: /* Caret escape */ + state = ST_GND; /* Should be the next state... */ + if (*p >= '@' && *p <= '~') + { + *(q++) = *(p++) & 037; + ++count; + } + else if (*p == '?') + { + *(q++) = 127; + ++count; + } + else + state = ST_ERROR; + break; + + default: + abort (); + } + } + + *dest = q; + *src = p; + *output_count = count; + + return state != ST_ERROR; +} + +static void +parse_ls_color (void) +{ + const char *p; /* Pointer to character being parsed */ + char *buf; /* color_buf buffer pointer */ + int state; /* State of parser */ + int ind_no; /* Indicator number */ + char label[3]; /* Indicator label */ + struct color_ext_type *ext; /* Extension we are working on */ + + if ((p = getenv ("LS_COLORS")) == NULL || *p == '\0') + return; + + ext = NULL; + strcpy (label, "??"); + + /* This is an overly conservative estimate, but any possible + LS_COLORS string will *not* generate a color_buf longer than + itself, so it is a safe way of allocating a buffer in + advance. */ + buf = color_buf = xstrdup (p); + + state = 1; + while (state > 0) + { + switch (state) + { + case 1: /* First label character */ + switch (*p) + { + case ':': + ++p; + break; + + case '*': + /* Allocate new extension block and add to head of + linked list (this way a later definition will + override an earlier one, which can be useful for + having terminal-specific defs override global). */ + + ext = xmalloc (sizeof *ext); + ext->next = color_ext_list; + color_ext_list = ext; + + ++p; + ext->ext.string = buf; + + state = (get_funky_string (&buf, &p, true, &ext->ext.len) + ? 4 : -1); + break; + + case '\0': + state = 0; /* Done! */ + break; + + default: /* Assume it is file type label */ + label[0] = *(p++); + state = 2; + break; + } + break; + + case 2: /* Second label character */ + if (*p) + { + label[1] = *(p++); + state = 3; + } + else + state = -1; /* Error */ + break; + + case 3: /* Equal sign after indicator label */ + state = -1; /* Assume failure... */ + if (*(p++) == '=')/* It *should* be... */ + { + for (ind_no = 0; indicator_name[ind_no] != NULL; ++ind_no) + { + if (STREQ (label, indicator_name[ind_no])) + { + color_indicator[ind_no].string = buf; + state = (get_funky_string (&buf, &p, false, + &color_indicator[ind_no].len) + ? 1 : -1); + break; + } + } + if (state == -1) + error (0, 0, _("unrecognized prefix: %s"), quotearg (label)); + } + break; + + case 4: /* Equal sign after *.ext */ + if (*(p++) == '=') + { + ext->seq.string = buf; + state = (get_funky_string (&buf, &p, false, &ext->seq.len) + ? 1 : -1); + } + else + state = -1; + break; + } + } + + if (state < 0) + { + struct color_ext_type *e; + struct color_ext_type *e2; + + error (0, 0, + _("unparsable value for LS_COLORS environment variable")); + free (color_buf); + for (e = color_ext_list; e != NULL; /* empty */) + { + e2 = e; + e = e->next; + free (e2); + } + print_with_color = false; + } + + if (color_indicator[C_LINK].len == 6 + && !strncmp (color_indicator[C_LINK].string, "target", 6)) + color_symlink_as_referent = true; +} + +/* Set the exit status to report a failure. If SERIOUS, it is a + serious failure; otherwise, it is merely a minor problem. */ + +static void +set_exit_status (bool serious) +{ + if (serious) + exit_status = LS_FAILURE; + else if (exit_status == EXIT_SUCCESS) + exit_status = LS_MINOR_PROBLEM; +} + +/* Assuming a failure is serious if SERIOUS, use the printf-style + MESSAGE to report the failure to access a file named FILE. Assume + errno is set appropriately for the failure. */ + +static void +file_failure (bool serious, char const *message, char const *file) +{ + error (0, errno, message, quotearg_colon (file)); + set_exit_status (serious); +} + +/* 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. NAME == NULL is used to insert a marker entry for the + directory named in REALNAME. + If NAME is non-NULL, we use its dev/ino information to save + a call to stat -- when doing a recursive (-R) traversal. + COMMAND_LINE_ARG means this directory was mentioned on the command line. */ + +static void +queue_directory (char const *name, char const *realname, bool command_line_arg) +{ + struct pending *new = xmalloc (sizeof *new); + new->realname = realname ? xstrdup (realname) : NULL; + new->name = name ? xstrdup (name) : NULL; + new->command_line_arg = command_line_arg; + new->next = pending_dirs; + pending_dirs = new; +} + +/* 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. + COMMAND_LINE_ARG means this directory was mentioned on the command line. */ + +static void +print_dir (char const *name, char const *realname, bool command_line_arg) +{ + DIR *dirp; + struct dirent *next; + uintmax_t total_blocks = 0; + static bool first = true; + + errno = 0; + dirp = opendir (name); + if (!dirp) + { + file_failure (command_line_arg, _("cannot open directory %s"), name); + return; + } + + if (LOOP_DETECT) + { + struct stat dir_stat; + int fd = dirfd (dirp); + + /* If dirfd failed, endure the overhead of using stat. */ + if ((0 <= fd + ? fstat (fd, &dir_stat) + : stat (name, &dir_stat)) < 0) + { + file_failure (command_line_arg, + _("cannot determine device and inode of %s"), name); + closedir (dirp); + return; + } + + /* If we've already visited this dev/inode pair, warn that + we've found a loop, and do not process this directory. */ + if (visit_dir (dir_stat.st_dev, dir_stat.st_ino)) + { + error (0, 0, _("%s: not listing already-listed directory"), + quotearg_colon (name)); + closedir (dirp); + return; + } + + DEV_INO_PUSH (dir_stat.st_dev, dir_stat.st_ino); + } + + /* Read the directory entries, and insert the subfiles into the `cwd_file' + table. */ + + clear_files (); + + while (1) + { + /* Set errno to zero so we can distinguish between a readdir failure + and when readdir simply finds that there are no more entries. */ + errno = 0; + next = readdir (dirp); + if (next) + { + if (! file_ignored (next->d_name)) + { + enum filetype type = unknown; + +#if HAVE_STRUCT_DIRENT_D_TYPE + switch (next->d_type) + { + case DT_BLK: type = blockdev; break; + case DT_CHR: type = chardev; break; + case DT_DIR: type = directory; break; + case DT_FIFO: type = fifo; break; + case DT_LNK: type = symbolic_link; break; + case DT_REG: type = normal; break; + case DT_SOCK: type = sock; break; +# ifdef DT_WHT + case DT_WHT: type = whiteout; break; +# endif + } +#endif + total_blocks += gobble_file (next->d_name, type, D_INO (next), + false, name); + } + } + else if (errno != 0) + { + file_failure (command_line_arg, _("reading directory %s"), name); + if (errno != EOVERFLOW) + break; + } + else + break; + } + + if (closedir (dirp) != 0) + { + file_failure (command_line_arg, _("closing directory %s"), name); + /* 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 (recursive) + extract_dirs_from_files (name, command_line_arg); + + if (recursive | print_dir_name) + { + if (!first) + DIRED_PUTCHAR ('\n'); + first = false; + DIRED_INDENT (); + PUSH_CURRENT_DIRED_POS (&subdired_obstack); + dired_pos += quote_name (stdout, realname ? realname : name, + dirname_quoting_options, NULL); + PUSH_CURRENT_DIRED_POS (&subdired_obstack); + DIRED_FPUTS_LITERAL (":\n", stdout); + } + + if (format == long_format || print_block_size) + { + const char *p; + char buf[LONGEST_HUMAN_READABLE + 1]; + + DIRED_INDENT (); + p = _("total"); + DIRED_FPUTS (p, stdout, strlen (p)); + DIRED_PUTCHAR (' '); + p = human_readable (total_blocks, buf, human_output_opts, + ST_NBLOCKSIZE, output_block_size); + DIRED_FPUTS (p, stdout, strlen (p)); + DIRED_PUTCHAR ('\n'); + } + + if (cwd_n_used) + print_current_files (); +} + +/* Add `pattern' to the list of patterns for which files that match are + not listed. */ + +static void +add_ignore_pattern (const char *pattern) +{ + struct ignore_pattern *ignore; + + ignore = xmalloc (sizeof *ignore); + ignore->pattern = pattern; + /* Add it to the head of the linked list. */ + ignore->next = ignore_patterns; + ignore_patterns = ignore; +} + +/* Return true if one of the PATTERNS matches FILE. */ + +static bool +patterns_match (struct ignore_pattern const *patterns, char const *file) +{ + struct ignore_pattern const *p; + for (p = patterns; p; p = p->next) + if (fnmatch (p->pattern, file, FNM_PERIOD) == 0) + return true; + return false; +} + +/* Return true if FILE should be ignored. */ + +static bool +file_ignored (char const *name) +{ + return ((ignore_mode != IGNORE_MINIMAL + && name[0] == '.' + && (ignore_mode == IGNORE_DEFAULT || ! name[1 + (name[1] == '.')])) + || (ignore_mode == IGNORE_DEFAULT + && patterns_match (hide_patterns, name)) + || patterns_match (ignore_patterns, name)); +} + +/* POSIX requires that a file size be printed without a sign, even + when negative. Assume the typical case where negative sizes are + actually positive values that have wrapped around. */ + +static uintmax_t +unsigned_file_size (off_t size) +{ + return size + (size < 0) * ((uintmax_t) OFF_T_MAX - OFF_T_MIN + 1); +} + +/* Enter and remove entries in the table `cwd_file'. */ + +/* Empty the table of files. */ + +static void +clear_files (void) +{ + size_t i; + + for (i = 0; i < cwd_n_used; i++) + { + struct fileinfo *f = sorted_file[i]; + free (f->name); + free (f->linkname); + } + + cwd_n_used = 0; +#if USE_ACL + any_has_acl = false; +#endif + inode_number_width = 0; + block_size_width = 0; + nlink_width = 0; + owner_width = 0; + group_width = 0; + author_width = 0; + major_device_number_width = 0; + minor_device_number_width = 0; + file_size_width = 0; +} + +/* 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. */ + +static uintmax_t +gobble_file (char const *name, enum filetype type, ino_t inode, + bool command_line_arg, char const *dirname) +{ + uintmax_t blocks = 0; + struct fileinfo *f; + + /* An inode value prior to gobble_file necessarily came from readdir, + which is not used for command line arguments. */ + assert (! command_line_arg || inode == NOT_AN_INODE_NUMBER); + + if (cwd_n_used == cwd_n_alloc) + { + cwd_file = xnrealloc (cwd_file, cwd_n_alloc, 2 * sizeof *cwd_file); + cwd_n_alloc *= 2; + } + + f = &cwd_file[cwd_n_used]; + memset (f, '\0', sizeof *f); + f->stat.st_ino = inode; + f->filetype = type; + + if (command_line_arg + || format_needs_stat + /* When coloring a directory (we may know the type from + direct.d_type), we have to stat it in order to indicate + sticky and/or other-writable attributes. */ + || (type == directory && print_with_color) + /* When dereferencing symlinks, the inode and type must come from + stat, but readdir provides the inode and type of lstat. */ + || ((print_inode || format_needs_type) + && (type == symbolic_link || type == unknown) + && (dereference == DEREF_ALWAYS + || (command_line_arg && dereference != DEREF_NEVER))) + /* Command line dereferences are already taken care of by the above + assertion that the inode number is not yet known. */ + || (print_inode && inode == NOT_AN_INODE_NUMBER) + || (format_needs_type + && (type == unknown || command_line_arg + /* --indicator-style=classify (aka -F) + requires that we stat each regular file + to see if it's executable. */ + || (type == normal && (indicator_style == classify + /* This is so that --color ends up + highlighting files with the executable + bit set even when options like -F are + not specified. */ + || (print_with_color + && is_colored (C_EXEC)) + ))))) + + { + /* Absolute name of this file. */ + char *absolute_name; + + int err; + + if (name[0] == '/' || dirname[0] == 0) + absolute_name = (char *) name; + else + { + absolute_name = alloca (strlen (name) + strlen (dirname) + 2); + attach (absolute_name, dirname, name); + } + + switch (dereference) + { + case DEREF_ALWAYS: + err = stat (absolute_name, &f->stat); + break; + + case DEREF_COMMAND_LINE_ARGUMENTS: + case DEREF_COMMAND_LINE_SYMLINK_TO_DIR: + if (command_line_arg) + { + bool need_lstat; + err = stat (absolute_name, &f->stat); + + if (dereference == DEREF_COMMAND_LINE_ARGUMENTS) + break; + + need_lstat = (err < 0 + ? errno == ENOENT + : ! S_ISDIR (f->stat.st_mode)); + if (!need_lstat) + break; + + /* stat failed because of ENOENT, maybe indicating a dangling + symlink. Or stat succeeded, ABSOLUTE_NAME does not refer to a + directory, and --dereference-command-line-symlink-to-dir is + in effect. Fall through so that we call lstat instead. */ + } + + default: /* DEREF_NEVER */ + err = lstat (absolute_name, &f->stat); + break; + } + + if (err != 0) + { + /* Failure to stat a command line argument leads to + an exit status of 2. For other files, stat failure + provokes an exit status of 1. */ + file_failure (command_line_arg, + _("cannot access %s"), absolute_name); + if (command_line_arg) + return 0; + + f->name = xstrdup (name); + cwd_n_used++; + + return 0; + } + + f->stat_ok = true; + +#if USE_ACL + if (format == long_format) + { + int n = file_has_acl (absolute_name, &f->stat); + f->have_acl = (0 < n); + any_has_acl |= f->have_acl; + if (n < 0) + error (0, errno, "%s", quotearg_colon (absolute_name)); + } +#endif + + if (S_ISLNK (f->stat.st_mode) + && (format == long_format || check_symlink_color)) + { + char *linkname; + struct stat linkstats; + + get_link_name (absolute_name, f, command_line_arg); + linkname = make_link_name (absolute_name, f->linkname); + + /* Avoid following symbolic links when possible, ie, when + they won't be traced and when no indicator is needed. */ + if (linkname + && (file_type <= indicator_style || check_symlink_color) + && stat (linkname, &linkstats) == 0) + { + f->linkok = true; + + /* Symbolic links to directories that are mentioned on the + command line are automatically traced if not being + listed as files. */ + if (!command_line_arg || format == long_format + || !S_ISDIR (linkstats.st_mode)) + { + /* Get the linked-to file's mode for the filetype indicator + in long listings. */ + f->linkmode = linkstats.st_mode; + } + } + free (linkname); + } + + if (S_ISLNK (f->stat.st_mode)) + f->filetype = symbolic_link; + else if (S_ISDIR (f->stat.st_mode)) + { + if (command_line_arg & !immediate_dirs) + f->filetype = arg_directory; + else + f->filetype = directory; + } + else + f->filetype = normal; + + blocks = ST_NBLOCKS (f->stat); + { + char buf[LONGEST_HUMAN_READABLE + 1]; + int len = mbswidth (human_readable (blocks, buf, human_output_opts, + ST_NBLOCKSIZE, output_block_size), + 0); + if (block_size_width < len) + block_size_width = len; + } + + if (print_owner) + { + int len = format_user_width (f->stat.st_uid); + if (owner_width < len) + owner_width = len; + } + + if (print_group) + { + int len = format_group_width (f->stat.st_gid); + if (group_width < len) + group_width = len; + } + + if (print_author) + { + int len = format_user_width (f->stat.st_author); + if (author_width < len) + author_width = len; + } + + { + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + int len = strlen (umaxtostr (f->stat.st_nlink, buf)); + if (nlink_width < len) + nlink_width = len; + } + + if (S_ISCHR (f->stat.st_mode) || S_ISBLK (f->stat.st_mode)) + { + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + int len = strlen (umaxtostr (major (f->stat.st_rdev), buf)); + if (major_device_number_width < len) + major_device_number_width = len; + len = strlen (umaxtostr (minor (f->stat.st_rdev), buf)); + if (minor_device_number_width < len) + minor_device_number_width = len; + len = major_device_number_width + 2 + minor_device_number_width; + if (file_size_width < len) + file_size_width = len; + } + else + { + char buf[LONGEST_HUMAN_READABLE + 1]; + uintmax_t size = unsigned_file_size (f->stat.st_size); + int len = mbswidth (human_readable (size, buf, human_output_opts, + 1, file_output_block_size), + 0); + if (file_size_width < len) + file_size_width = len; + } + } + + if (print_inode) + { + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + int len = strlen (umaxtostr (f->stat.st_ino, buf)); + if (inode_number_width < len) + inode_number_width = len; + } + + f->name = xstrdup (name); + cwd_n_used++; + + return blocks; +} + +/* Return true if F refers to a directory. */ +static bool +is_directory (const struct fileinfo *f) +{ + return f->filetype == directory || f->filetype == arg_directory; +} + +/* Put the name of the file that FILENAME is a symbolic link to + into the LINKNAME field of `f'. COMMAND_LINE_ARG indicates whether + FILENAME is a command-line argument. */ + +static void +get_link_name (char const *filename, struct fileinfo *f, bool command_line_arg) +{ + f->linkname = xreadlink_with_size (filename, f->stat.st_size); + if (f->linkname == NULL) + file_failure (command_line_arg, _("cannot read symbolic link %s"), + filename); +} + +/* If `linkname' is a relative name and `name' contains one or more + leading directories, return `linkname' with those directories + prepended; otherwise, return a copy of `linkname'. + If `linkname' is zero, return zero. */ + +static char * +make_link_name (char const *name, char const *linkname) +{ + char *linkbuf; + size_t bufsiz; + + if (!linkname) + return NULL; + + if (*linkname == '/') + return xstrdup (linkname); + + /* The link is to a relative name. Prepend any leading directory + in `name' to the link name. */ + linkbuf = strrchr (name, '/'); + if (linkbuf == 0) + return xstrdup (linkname); + + bufsiz = linkbuf - name + 1; + linkbuf = xmalloc (bufsiz + strlen (linkname) + 1); + strncpy (linkbuf, name, bufsiz); + strcpy (linkbuf + bufsiz, linkname); + return linkbuf; +} + +/* Return true if the last component of NAME is `.' or `..' + This is so we don't try to recurse on `././././. ...' */ + +static bool +basename_is_dot_or_dotdot (const char *name) +{ + char const *base = last_component (name); + return dot_or_dotdot (base); +} + +/* Remove any entries from CWD_FILE 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; + if it is null, no prefix is needed and "." and ".." should not be ignored. + If COMMAND_LINE_ARG is true, this directory was mentioned at the top level, + This is desirable when processing directories recursively. */ + +static void +extract_dirs_from_files (char const *dirname, bool command_line_arg) +{ + size_t i; + size_t j; + bool ignore_dot_and_dot_dot = (dirname != NULL); + + if (dirname && LOOP_DETECT) + { + /* Insert a marker entry first. When we dequeue this marker entry, + we'll know that DIRNAME has been processed and may be removed + from the set of active directories. */ + queue_directory (NULL, dirname, false); + } + + /* Queue the directories last one first, because queueing reverses the + order. */ + for (i = cwd_n_used; i-- != 0; ) + { + struct fileinfo *f = sorted_file[i]; + + if (is_directory (f) + && (! ignore_dot_and_dot_dot + || ! basename_is_dot_or_dotdot (f->name))) + { + if (!dirname || f->name[0] == '/') + queue_directory (f->name, f->linkname, command_line_arg); + else + { + char *name = file_name_concat (dirname, f->name, NULL); + queue_directory (name, f->linkname, command_line_arg); + free (name); + } + if (f->filetype == arg_directory) + free (f->name); + } + } + + /* Now delete the directories from the table, compacting all the remaining + entries. */ + + for (i = 0, j = 0; i < cwd_n_used; i++) + { + struct fileinfo *f = sorted_file[i]; + sorted_file[j] = f; + j += (f->filetype != arg_directory); + } + cwd_n_used = j; +} + +/* Use strcoll to compare strings in this locale. If an error occurs, + report an error and longjmp to failed_strcoll. */ + +static jmp_buf failed_strcoll; + +static int +xstrcoll (char const *a, char const *b) +{ + int diff; + errno = 0; + diff = strcoll (a, b); + if (errno) + { + error (0, errno, _("cannot compare file names %s and %s"), + quote_n (0, a), quote_n (1, b)); + set_exit_status (false); + longjmp (failed_strcoll, 1); + } + return diff; +} + +/* Comparison routines for sorting the files. */ + +typedef void const *V; +typedef int (*qsortFunc)(V a, V b); + +/* Used below in DEFINE_SORT_FUNCTIONS for _df_ sort function variants. + The do { ... } while(0) makes it possible to use the macro more like + a statement, without violating C89 rules: */ +#define DIRFIRST_CHECK(a, b) \ + do \ + { \ + bool a_is_dir = is_directory ((struct fileinfo const *) a); \ + bool b_is_dir = is_directory ((struct fileinfo const *) b); \ + if (a_is_dir && !b_is_dir) \ + return -1; /* a goes before b */ \ + if (!a_is_dir && b_is_dir) \ + return 1; /* b goes before a */ \ + } \ + while (0) + +/* Define the 8 different sort function variants required for each sortkey. + KEY_NAME is a token describing the sort key, e.g., ctime, atime, size. + KEY_CMP_FUNC is a function to compare records based on that key, e.g., + ctime_cmp, atime_cmp, size_cmp. Append KEY_NAME to the string, + '[rev_][x]str{cmp|coll}[_df]_', to create each function name. */ +#define DEFINE_SORT_FUNCTIONS(key_name, key_cmp_func) \ + /* direct, non-dirfirst versions */ \ + static int xstrcoll_##key_name (V a, V b) \ + { return key_cmp_func (a, b, xstrcoll); } \ + static int strcmp_##key_name (V a, V b) \ + { return key_cmp_func (a, b, strcmp); } \ + \ + /* reverse, non-dirfirst versions */ \ + static int rev_xstrcoll_##key_name (V a, V b) \ + { return key_cmp_func (b, a, xstrcoll); } \ + static int rev_strcmp_##key_name (V a, V b) \ + { return key_cmp_func (b, a, strcmp); } \ + \ + /* direct, dirfirst versions */ \ + static int xstrcoll_df_##key_name (V a, V b) \ + { DIRFIRST_CHECK (a, b); return key_cmp_func (a, b, xstrcoll); } \ + static int strcmp_df_##key_name (V a, V b) \ + { DIRFIRST_CHECK (a, b); return key_cmp_func (a, b, strcmp); } \ + \ + /* reverse, dirfirst versions */ \ + static int rev_xstrcoll_df_##key_name (V a, V b) \ + { DIRFIRST_CHECK (a, b); return key_cmp_func (b, a, xstrcoll); } \ + static int rev_strcmp_df_##key_name (V a, V b) \ + { DIRFIRST_CHECK (a, b); return key_cmp_func (b, a, strcmp); } + +static inline int +cmp_ctime (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + int diff = timespec_cmp (get_stat_ctime (&b->stat), + get_stat_ctime (&a->stat)); + return diff ? diff : cmp (a->name, b->name); +} + +static inline int +cmp_mtime (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + int diff = timespec_cmp (get_stat_mtime (&b->stat), + get_stat_mtime (&a->stat)); + return diff ? diff : cmp (a->name, b->name); +} + +static inline int +cmp_atime (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + int diff = timespec_cmp (get_stat_atime (&b->stat), + get_stat_atime (&a->stat)); + return diff ? diff : cmp (a->name, b->name); +} + +static inline int +cmp_size (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + int diff = longdiff (b->stat.st_size, a->stat.st_size); + return diff ? diff : cmp (a->name, b->name); +} + +static inline int +cmp_name (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + return cmp (a->name, b->name); +} + +/* Compare file extensions. Files with no extension are `smallest'. + If extensions are the same, compare by filenames instead. */ + +static inline int +cmp_extension (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + char const *base1 = strrchr (a->name, '.'); + char const *base2 = strrchr (b->name, '.'); + int diff = cmp (base1 ? base1 : "", base2 ? base2 : ""); + return diff ? diff : cmp (a->name, b->name); +} + +DEFINE_SORT_FUNCTIONS (ctime, cmp_ctime) +DEFINE_SORT_FUNCTIONS (mtime, cmp_mtime) +DEFINE_SORT_FUNCTIONS (atime, cmp_atime) +DEFINE_SORT_FUNCTIONS (size, cmp_size) +DEFINE_SORT_FUNCTIONS (name, cmp_name) +DEFINE_SORT_FUNCTIONS (extension, cmp_extension) + +/* Compare file versions. + Unlike all other compare functions above, cmp_version depends only + on strverscmp, which does not fail (even for locale reasons), and does not + need a secondary sort key. + All the other sort options, in fact, need xstrcoll and strcmp variants, + because they all use a string comparison (either as the primary or secondary + sort key), and xstrcoll has the ability to do a longjmp if strcoll fails for + locale reasons. Last, strverscmp is ALWAYS available in coreutils, + thanks to the gnulib library. */ +static inline int +cmp_version (struct fileinfo const *a, struct fileinfo const *b) +{ + return strverscmp (a->name, b->name); +} + +static int xstrcoll_version (V a, V b) +{ return cmp_version (a, b); } +static int rev_xstrcoll_version (V a, V b) +{ return cmp_version (b, a); } +static int xstrcoll_df_version (V a, V b) +{ DIRFIRST_CHECK (a, b); return cmp_version (a, b); } +static int rev_xstrcoll_df_version (V a, V b) +{ DIRFIRST_CHECK (a, b); return cmp_version (b, a); } + + +/* We have 2^3 different variants for each sortkey function + (for 3 independent sort modes). + The function pointers stored in this array must be dereferenced as: + + sort_variants[sort_key][use_strcmp][reverse][dirs_first] + + Note that the order in which sortkeys are listed in the function pointer + array below is defined by the order of the elements in the time_type and + sort_type enums! */ + +#define LIST_SORTFUNCTION_VARIANTS(key_name) \ + { \ + { \ + { xstrcoll_##key_name, xstrcoll_df_##key_name }, \ + { rev_xstrcoll_##key_name, rev_xstrcoll_df_##key_name }, \ + }, \ + { \ + { strcmp_##key_name, strcmp_df_##key_name }, \ + { rev_strcmp_##key_name, rev_strcmp_df_##key_name }, \ + } \ + } + +static qsortFunc sort_functions[][2][2][2] = + { + LIST_SORTFUNCTION_VARIANTS (name), + LIST_SORTFUNCTION_VARIANTS (extension), + LIST_SORTFUNCTION_VARIANTS (size), + + { + { + { xstrcoll_version, xstrcoll_df_version }, + { rev_xstrcoll_version, rev_xstrcoll_df_version }, + }, + + /* We use NULL for the strcmp variants of version comparison + since as explained in cmp_version definition, version comparison + does not rely on xstrcoll, so it will never longjmp, and never + need to try the strcmp fallback. */ + { + { NULL, NULL }, + { NULL, NULL }, + } + }, + + /* last are time sort functions */ + LIST_SORTFUNCTION_VARIANTS (mtime), + LIST_SORTFUNCTION_VARIANTS (ctime), + LIST_SORTFUNCTION_VARIANTS (atime) + }; + +/* The number of sortkeys is calculated as + the number of elements in the sort_type enum (i.e. sort_numtypes) + + the number of elements in the time_type enum (i.e. time_numtypes) - 1 + This is because when sort_type==sort_time, we have up to + time_numtypes possible sortkeys. + + This line verifies at compile-time that the array of sort functions has been + initialized for all possible sortkeys. */ +verify (ARRAY_CARDINALITY (sort_functions) + == sort_numtypes + time_numtypes - 1 ); + +/* Set up SORTED_FILE to point to the in-use entries in CWD_FILE, in order. */ + +static void +initialize_ordering_vector (void) +{ + size_t i; + for (i = 0; i < cwd_n_used; i++) + sorted_file[i] = &cwd_file[i]; +} + +/* Sort the files now in the table. */ + +static void +sort_files (void) +{ + bool use_strcmp; + + if (sorted_file_alloc < cwd_n_used + cwd_n_used / 2) + { + free (sorted_file); + sorted_file = xnmalloc (cwd_n_used, 3 * sizeof *sorted_file); + sorted_file_alloc = 3 * cwd_n_used; + } + + initialize_ordering_vector (); + + if (sort_type == sort_none) + return; + + /* Try strcoll. If it fails, fall back on strcmp. We can't safely + ignore strcoll failures, as a failing strcoll might be a + comparison function that is not a total order, and if we ignored + the failure this might cause qsort to dump core. */ + + if (! setjmp (failed_strcoll)) + use_strcmp = false; /* strcoll() succeeded */ + else + { + use_strcmp = true; + assert (sort_type != sort_version); + initialize_ordering_vector (); + } + + /* When sort_type == sort_time, use time_type as subindex. */ + mpsort ((void const **) sorted_file, cwd_n_used, + sort_functions[sort_type + (sort_type == sort_time ? time_type : 0)] + [use_strcmp][sort_reverse] + [directories_first]); +} + +/* List all the files now in the table. */ + +static void +print_current_files (void) +{ + size_t i; + + switch (format) + { + case one_per_line: + for (i = 0; i < cwd_n_used; i++) + { + print_file_name_and_frills (sorted_file[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 < cwd_n_used; i++) + { + print_long_format (sorted_file[i]); + DIRED_PUTCHAR ('\n'); + } + break; + } +} + +/* Return the expected number of columns in a long-format time stamp, + or zero if it cannot be calculated. */ + +static int +long_time_expected_width (void) +{ + static int width = -1; + + if (width < 0) + { + time_t epoch = 0; + struct tm const *tm = localtime (&epoch); + char buf[TIME_STAMP_LEN_MAXIMUM + 1]; + + /* In case you're wondering if localtime can fail with an input time_t + value of 0, let's just say it's very unlikely, but not inconceivable. + The TZ environment variable would have to specify a time zone that + is 2**31-1900 years or more ahead of UTC. This could happen only on + a 64-bit system that blindly accepts e.g., TZ=UTC+20000000000000. + However, this is not possible with Solaris 10 or glibc-2.3.5, since + their implementations limit the offset to 167:59 and 24:00, resp. */ + if (tm) + { + size_t len = + nstrftime (buf, sizeof buf, long_time_format[0], tm, 0, 0); + if (len != 0) + width = mbsnwidth (buf, len, 0); + } + + if (width < 0) + width = 0; + } + + return width; +} + +/* Get the current time. */ + +static void +get_current_time (void) +{ +#if HAVE_CLOCK_GETTIME && defined CLOCK_REALTIME + { + struct timespec timespec; + if (clock_gettime (CLOCK_REALTIME, ×pec) == 0) + { + current_time = timespec.tv_sec; + current_time_ns = timespec.tv_nsec; + return; + } + } +#endif + + /* The clock does not have nanosecond resolution, so get the maximum + possible value for the current time that is consistent with the + reported clock. That way, files are not considered to be in the + future merely because their time stamps have higher resolution + than the clock resolution. */ + +#if HAVE_GETTIMEOFDAY + { + struct timeval timeval; + gettimeofday (&timeval, NULL); + current_time = timeval.tv_sec; + current_time_ns = timeval.tv_usec * 1000 + 999; + } +#else + current_time = time (NULL); + current_time_ns = 999999999; +#endif +} + +/* Print the user or group name NAME, with numeric id ID, using a + print width of WIDTH columns. */ + +static void +format_user_or_group (char const *name, unsigned long int id, int width) +{ + size_t len; + + if (name) + { + int width_gap = width - mbswidth (name, 0); + int pad = MAX (0, width_gap); + fputs (name, stdout); + len = strlen (name) + pad; + + do + putchar (' '); + while (pad--); + } + else + { + printf ("%*lu ", width, id); + len = width; + } + + dired_pos += len + 1; +} + +/* Print the name or id of the user with id U, using a print width of + WIDTH. */ + +static void +format_user (uid_t u, int width, bool stat_ok) +{ + format_user_or_group (! stat_ok ? "?" : + (numeric_ids ? NULL : getuser (u)), u, width); +} + +/* Likewise, for groups. */ + +static void +format_group (gid_t g, int width, bool stat_ok) +{ + format_user_or_group (! stat_ok ? "?" : + (numeric_ids ? NULL : getgroup (g)), g, width); +} + +/* Return the number of columns that format_user_or_group will print. */ + +static int +format_user_or_group_width (char const *name, unsigned long int id) +{ + if (name) + { + int len = mbswidth (name, 0); + return MAX (0, len); + } + else + { + char buf[INT_BUFSIZE_BOUND (unsigned long int)]; + sprintf (buf, "%lu", id); + return strlen (buf); + } +} + +/* Return the number of columns that format_user will print. */ + +static int +format_user_width (uid_t u) +{ + return format_user_or_group_width (numeric_ids ? NULL : getuser (u), u); +} + +/* Likewise, for groups. */ + +static int +format_group_width (gid_t g) +{ + return format_user_or_group_width (numeric_ids ? NULL : getgroup (g), g); +} + + +/* Print information about F in long format. */ + +static void +print_long_format (const struct fileinfo *f) +{ + char modebuf[12]; + char buf + [LONGEST_HUMAN_READABLE + 1 /* inode */ + + LONGEST_HUMAN_READABLE + 1 /* size in blocks */ + + sizeof (modebuf) - 1 + 1 /* mode string */ + + INT_BUFSIZE_BOUND (uintmax_t) /* st_nlink */ + + LONGEST_HUMAN_READABLE + 2 /* major device number */ + + LONGEST_HUMAN_READABLE + 1 /* minor device number */ + + TIME_STAMP_LEN_MAXIMUM + 1 /* max length of time/date */ + ]; + size_t s; + char *p; + time_t when; + int when_ns; + struct timespec when_timespec; + struct tm *when_local; + + /* Compute the mode string, except remove the trailing space if no + files in this directory have ACLs. */ + if (f->stat_ok) + filemodestring (&f->stat, modebuf); + else + { + modebuf[0] = filetype_letter[f->filetype]; + memset (modebuf + 1, '?', 10); + modebuf[11] = '\0'; + } + if (! any_has_acl) + modebuf[10] = '\0'; + else if (FILE_HAS_ACL (f)) + modebuf[10] = '+'; + + switch (time_type) + { + case time_ctime: + when_timespec = get_stat_ctime (&f->stat); + break; + case time_mtime: + when_timespec = get_stat_mtime (&f->stat); + break; + case time_atime: + when_timespec = get_stat_atime (&f->stat); + break; + default: + abort (); + } + + when = when_timespec.tv_sec; + when_ns = when_timespec.tv_nsec; + + p = buf; + + if (print_inode) + { + char hbuf[INT_BUFSIZE_BOUND (uintmax_t)]; + sprintf (p, "%*s ", inode_number_width, + (f->stat.st_ino == NOT_AN_INODE_NUMBER + ? "?" + : umaxtostr (f->stat.st_ino, hbuf))); + /* Increment by strlen (p) here, rather than by inode_number_width + 1. + The latter is wrong when inode_number_width is zero. */ + p += strlen (p); + } + + if (print_block_size) + { + char hbuf[LONGEST_HUMAN_READABLE + 1]; + char const *blocks = + (! f->stat_ok + ? "?" + : human_readable (ST_NBLOCKS (f->stat), hbuf, human_output_opts, + ST_NBLOCKSIZE, output_block_size)); + int pad; + for (pad = block_size_width - mbswidth (blocks, 0); 0 < pad; pad--) + *p++ = ' '; + while ((*p++ = *blocks++)) + continue; + p[-1] = ' '; + } + + /* The last byte of the mode string is the POSIX + "optional alternate access method flag". */ + { + char hbuf[INT_BUFSIZE_BOUND (uintmax_t)]; + sprintf (p, "%s %*s ", modebuf, nlink_width, + ! f->stat_ok ? "?" : umaxtostr (f->stat.st_nlink, hbuf)); + } + /* Increment by strlen (p) here, rather than by, e.g., + sizeof modebuf - 2 + any_has_acl + 1 + nlink_width + 1. + The latter is wrong when nlink_width is zero. */ + p += strlen (p); + + DIRED_INDENT (); + + if (print_owner | print_group | print_author) + { + DIRED_FPUTS (buf, stdout, p - buf); + + if (print_owner) + format_user (f->stat.st_uid, owner_width, f->stat_ok); + + if (print_group) + format_group (f->stat.st_gid, group_width, f->stat_ok); + + if (print_author) + format_user (f->stat.st_author, author_width, f->stat_ok); + + p = buf; + } + + if (f->stat_ok + && (S_ISCHR (f->stat.st_mode) || S_ISBLK (f->stat.st_mode))) + { + char majorbuf[INT_BUFSIZE_BOUND (uintmax_t)]; + char minorbuf[INT_BUFSIZE_BOUND (uintmax_t)]; + int blanks_width = (file_size_width + - (major_device_number_width + 2 + + minor_device_number_width)); + sprintf (p, "%*s, %*s ", + major_device_number_width + MAX (0, blanks_width), + umaxtostr (major (f->stat.st_rdev), majorbuf), + minor_device_number_width, + umaxtostr (minor (f->stat.st_rdev), minorbuf)); + p += file_size_width + 1; + } + else + { + char hbuf[LONGEST_HUMAN_READABLE + 1]; + char const *size = + (! f->stat_ok + ? "?" + : human_readable (unsigned_file_size (f->stat.st_size), + hbuf, human_output_opts, 1, file_output_block_size)); + int pad; + for (pad = file_size_width - mbswidth (size, 0); 0 < pad; pad--) + *p++ = ' '; + while ((*p++ = *size++)) + continue; + p[-1] = ' '; + } + + when_local = localtime (&when_timespec.tv_sec); + s = 0; + *p = '\1'; + + if (f->stat_ok && when_local) + { + time_t six_months_ago; + bool recent; + char const *fmt; + + /* If the file appears to be in the future, update the current + time, in case the file happens to have been modified since + the last time we checked the clock. */ + if (current_time < when + || (current_time == when && current_time_ns < when_ns)) + { + /* Note that get_current_time calls gettimeofday which, on some non- + compliant systems, clobbers the buffer used for localtime's result. + But it's ok here, because we use a gettimeofday wrapper that + saves and restores the buffer around the gettimeofday call. */ + get_current_time (); + } + + /* Consider a time to be recent if it is within the past six + months. A Gregorian year has 365.2425 * 24 * 60 * 60 == + 31556952 seconds on the average. Write this value as an + integer constant to avoid floating point hassles. */ + six_months_ago = current_time - 31556952 / 2; + recent = (six_months_ago <= when + && (when < current_time + || (when == current_time && when_ns <= current_time_ns))); + fmt = long_time_format[recent]; + + s = nstrftime (p, TIME_STAMP_LEN_MAXIMUM + 1, fmt, + when_local, 0, when_ns); + } + + if (s || !*p) + { + p += s; + *p++ = ' '; + + /* NUL-terminate the string -- fputs (via DIRED_FPUTS) requires it. */ + *p = '\0'; + } + else + { + /* The time cannot be converted using the desired format, so + print it as a huge integer number of seconds. */ + char hbuf[INT_BUFSIZE_BOUND (intmax_t)]; + sprintf (p, "%*s ", long_time_expected_width (), + (! f->stat_ok + ? "?" + : (TYPE_SIGNED (time_t) + ? imaxtostr (when, hbuf) + : umaxtostr (when, hbuf)))); + p += strlen (p); + } + + DIRED_FPUTS (buf, stdout, p - buf); + print_name_with_quoting (f->name, FILE_OR_LINK_MODE (f), f->linkok, + f->stat_ok, f->filetype, &dired_obstack); + + if (f->filetype == symbolic_link) + { + if (f->linkname) + { + DIRED_FPUTS_LITERAL (" -> ", stdout); + print_name_with_quoting (f->linkname, f->linkmode, f->linkok - 1, + f->stat_ok, f->filetype, NULL); + if (indicator_style != none) + print_type_indicator (true, f->linkmode, unknown); + } + } + else if (indicator_style != none) + print_type_indicator (f->stat_ok, f->stat.st_mode, f->filetype); +} + +/* Output to OUT a quoted representation of the file name NAME, + using OPTIONS to control quoting. Produce no output if OUT is NULL. + Store the number of screen columns occupied by NAME's quoted + representation into WIDTH, if non-NULL. Return the number of bytes + produced. */ + +static size_t +quote_name (FILE *out, const char *name, struct quoting_options const *options, + size_t *width) +{ + char smallbuf[BUFSIZ]; + size_t len = quotearg_buffer (smallbuf, sizeof smallbuf, name, -1, options); + char *buf; + size_t displayed_width IF_LINT (= 0); + + if (len < sizeof smallbuf) + buf = smallbuf; + else + { + buf = alloca (len + 1); + quotearg_buffer (buf, len + 1, name, -1, options); + } + + if (qmark_funny_chars) + { +#if HAVE_MBRTOWC + if (MB_CUR_MAX > 1) + { + char const *p = buf; + char const *plimit = buf + len; + char *q = buf; + displayed_width = 0; + + while (p < plimit) + switch (*p) + { + case ' ': case '!': case '"': case '#': case '%': + case '&': case '\'': case '(': case ')': case '*': + case '+': case ',': case '-': case '.': case '/': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case ':': case ';': case '<': case '=': case '>': + case '?': + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': + case 'Z': + case '[': case '\\': case ']': case '^': case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': + case 'z': case '{': case '|': case '}': case '~': + /* These characters are printable ASCII characters. */ + *q++ = *p++; + displayed_width += 1; + break; + default: + /* If we have a multibyte sequence, copy it until we + reach its end, replacing each non-printable multibyte + character with a single question mark. */ + { + mbstate_t mbstate = { 0, }; + do + { + wchar_t wc; + size_t bytes; + int w; + + bytes = mbrtowc (&wc, p, plimit - p, &mbstate); + + if (bytes == (size_t) -1) + { + /* An invalid multibyte sequence was + encountered. Skip one input byte, and + put a question mark. */ + p++; + *q++ = '?'; + displayed_width += 1; + break; + } + + if (bytes == (size_t) -2) + { + /* An incomplete multibyte character + at the end. Replace it entirely with + a question mark. */ + p = plimit; + *q++ = '?'; + displayed_width += 1; + break; + } + + if (bytes == 0) + /* A null wide character was encountered. */ + bytes = 1; + + w = wcwidth (wc); + if (w >= 0) + { + /* A printable multibyte character. + Keep it. */ + for (; bytes > 0; --bytes) + *q++ = *p++; + displayed_width += w; + } + else + { + /* An unprintable multibyte character. + Replace it entirely with a question + mark. */ + p += bytes; + *q++ = '?'; + displayed_width += 1; + } + } + while (! mbsinit (&mbstate)); + } + break; + } + + /* The buffer may have shrunk. */ + len = q - buf; + } + else +#endif + { + char *p = buf; + char const *plimit = buf + len; + + while (p < plimit) + { + if (! isprint (to_uchar (*p))) + *p = '?'; + p++; + } + displayed_width = len; + } + } + else if (width != NULL) + { +#if HAVE_MBRTOWC + if (MB_CUR_MAX > 1) + displayed_width = mbsnwidth (buf, len, 0); + else +#endif + { + char const *p = buf; + char const *plimit = buf + len; + + displayed_width = 0; + while (p < plimit) + { + if (isprint (to_uchar (*p))) + displayed_width++; + p++; + } + } + } + + if (out != NULL) + fwrite (buf, 1, len, out); + if (width != NULL) + *width = displayed_width; + return len; +} + +static void +print_name_with_quoting (const char *p, mode_t mode, int linkok, + bool stat_ok, enum filetype type, + struct obstack *stack) +{ + if (print_with_color) + print_color_indicator (p, mode, linkok, stat_ok, type); + + if (stack) + PUSH_CURRENT_DIRED_POS (stack); + + dired_pos += quote_name (stdout, p, filename_quoting_options, NULL); + + if (stack) + PUSH_CURRENT_DIRED_POS (stack); + + if (print_with_color) + { + process_signals (); + prep_non_filename_text (); + } +} + +static void +prep_non_filename_text (void) +{ + if (color_indicator[C_END].string != NULL) + put_indicator (&color_indicator[C_END]); + else + { + put_indicator (&color_indicator[C_LEFT]); + put_indicator (&color_indicator[C_NORM]); + put_indicator (&color_indicator[C_RIGHT]); + } +} + +/* Print the file name of `f' with appropriate quoting. + Also print file size, inode number, and filetype indicator character, + as requested by switches. */ + +static void +print_file_name_and_frills (const struct fileinfo *f) +{ + char buf[MAX (LONGEST_HUMAN_READABLE + 1, INT_BUFSIZE_BOUND (uintmax_t))]; + + if (print_inode) + printf ("%*s ", format == with_commas ? 0 : inode_number_width, + umaxtostr (f->stat.st_ino, buf)); + + if (print_block_size) + printf ("%*s ", format == with_commas ? 0 : block_size_width, + human_readable (ST_NBLOCKS (f->stat), buf, human_output_opts, + ST_NBLOCKSIZE, output_block_size)); + + print_name_with_quoting (f->name, FILE_OR_LINK_MODE (f), f->linkok, + f->stat_ok, f->filetype, NULL); + + if (indicator_style != none) + print_type_indicator (f->stat_ok, f->stat.st_mode, f->filetype); +} + +/* Given these arguments describing a file, return the single-byte + type indicator, or 0. */ +static char +get_type_indicator (bool stat_ok, mode_t mode, enum filetype type) +{ + char c; + + if (stat_ok ? S_ISREG (mode) : type == normal) + { + if (stat_ok && indicator_style == classify && (mode & S_IXUGO)) + c = '*'; + else + c = 0; + } + else + { + if (stat_ok ? S_ISDIR (mode) : type == directory || type == arg_directory) + c = '/'; + else if (indicator_style == slash) + c = 0; + else if (stat_ok ? S_ISLNK (mode) : type == symbolic_link) + c = '@'; + else if (stat_ok ? S_ISFIFO (mode) : type == fifo) + c = '|'; + else if (stat_ok ? S_ISSOCK (mode) : type == sock) + c = '='; + else if (stat_ok && S_ISDOOR (mode)) + c = '>'; + else + c = 0; + } + return c; +} + +static void +print_type_indicator (bool stat_ok, mode_t mode, enum filetype type) +{ + char c = get_type_indicator (stat_ok, mode, type); + if (c) + DIRED_PUTCHAR (c); +} + +static void +print_color_indicator (const char *name, mode_t mode, int linkok, + bool stat_ok, enum filetype filetype) +{ + int type; + struct color_ext_type *ext; /* Color extension */ + size_t len; /* Length of name */ + + /* Is this a nonexistent file? If so, linkok == -1. */ + + if (linkok == -1 && color_indicator[C_MISSING].string != NULL) + type = C_MISSING; + else if (! stat_ok) + { + static enum indicator_no filetype_indicator[] = FILETYPE_INDICATORS; + type = filetype_indicator[filetype]; + } + else + { + if (S_ISREG (mode)) + { + type = C_FILE; + if ((mode & S_ISUID) != 0) + type = C_SETUID; + else if ((mode & S_ISGID) != 0) + type = C_SETGID; + else if ((mode & S_IXUGO) != 0) + type = C_EXEC; + } + else if (S_ISDIR (mode)) + { + if ((mode & S_ISVTX) && (mode & S_IWOTH)) + type = C_STICKY_OTHER_WRITABLE; + else if ((mode & S_IWOTH) != 0) + type = C_OTHER_WRITABLE; + else if ((mode & S_ISVTX) != 0) + type = C_STICKY; + else + type = C_DIR; + } + else if (S_ISLNK (mode)) + type = ((!linkok && color_indicator[C_ORPHAN].string) + ? C_ORPHAN : C_LINK); + else if (S_ISFIFO (mode)) + type = C_FIFO; + else if (S_ISSOCK (mode)) + type = C_SOCK; + else if (S_ISBLK (mode)) + type = C_BLK; + else if (S_ISCHR (mode)) + type = C_CHR; + else if (S_ISDOOR (mode)) + type = C_DOOR; + else + { + /* Classify a file of some other type as C_ORPHAN. */ + type = C_ORPHAN; + } + } + + /* Check the file's suffix only if still classified as C_FILE. */ + ext = NULL; + if (type == C_FILE) + { + /* Test if NAME has a recognized suffix. */ + + len = strlen (name); + name += len; /* Pointer to final \0. */ + for (ext = color_ext_list; ext != NULL; ext = ext->next) + { + if (ext->ext.len <= len + && strncmp (name - ext->ext.len, ext->ext.string, + ext->ext.len) == 0) + break; + } + } + + put_indicator (&color_indicator[C_LEFT]); + put_indicator (ext ? &(ext->seq) : &color_indicator[type]); + put_indicator (&color_indicator[C_RIGHT]); +} + +/* Output a color indicator (which may contain nulls). */ +static void +put_indicator (const struct bin_str *ind) +{ + size_t i; + const char *p; + + p = ind->string; + + for (i = ind->len; i != 0; --i) + putchar (*(p++)); +} + +static size_t +length_of_file_name_and_frills (const struct fileinfo *f) +{ + size_t len = 0; + size_t name_width; + char buf[MAX (LONGEST_HUMAN_READABLE + 1, INT_BUFSIZE_BOUND (uintmax_t))]; + + if (print_inode) + len += 1 + (format == with_commas + ? strlen (umaxtostr (f->stat.st_ino, buf)) + : inode_number_width); + + if (print_block_size) + len += 1 + (format == with_commas + ? strlen (human_readable (ST_NBLOCKS (f->stat), buf, + human_output_opts, ST_NBLOCKSIZE, + output_block_size)) + : block_size_width); + + quote_name (NULL, f->name, filename_quoting_options, &name_width); + len += name_width; + + if (indicator_style != none) + { + char c = get_type_indicator (f->stat_ok, f->stat.st_mode, f->filetype); + len += (c != 0); + } + + return len; +} + +static void +print_many_per_line (void) +{ + size_t row; /* Current row. */ + size_t cols = calculate_columns (true); + struct column_info const *line_fmt = &column_info[cols - 1]; + + /* Calculate the number of rows that will be in each column except possibly + for a short column on the right. */ + size_t rows = cwd_n_used / cols + (cwd_n_used % cols != 0); + + for (row = 0; row < rows; row++) + { + size_t col = 0; + size_t filesno = row; + size_t pos = 0; + + /* Print the next row. */ + while (1) + { + struct fileinfo const *f = sorted_file[filesno]; + size_t name_length = length_of_file_name_and_frills (f); + size_t max_name_length = line_fmt->col_arr[col++]; + print_file_name_and_frills (f); + + filesno += rows; + if (filesno >= cwd_n_used) + break; + + indent (pos + name_length, pos + max_name_length); + pos += max_name_length; + } + putchar ('\n'); + } +} + +static void +print_horizontal (void) +{ + size_t filesno; + size_t pos = 0; + size_t cols = calculate_columns (false); + struct column_info const *line_fmt = &column_info[cols - 1]; + size_t name_length = length_of_file_name_and_frills (cwd_file); + size_t max_name_length = line_fmt->col_arr[0]; + + /* Print first entry. */ + print_file_name_and_frills (cwd_file); + + /* Now the rest. */ + for (filesno = 1; filesno < cwd_n_used; ++filesno) + { + struct fileinfo const *f; + size_t col = filesno % cols; + + if (col == 0) + { + putchar ('\n'); + pos = 0; + } + else + { + indent (pos + name_length, pos + max_name_length); + pos += max_name_length; + } + + f = sorted_file[filesno]; + print_file_name_and_frills (f); + + name_length = length_of_file_name_and_frills (f); + max_name_length = line_fmt->col_arr[col]; + } + putchar ('\n'); +} + +static void +print_with_commas (void) +{ + size_t filesno; + size_t pos = 0; + + for (filesno = 0; filesno < cwd_n_used; filesno++) + { + struct fileinfo const *f = sorted_file[filesno]; + size_t len = length_of_file_name_and_frills (f); + + if (filesno != 0) + { + char separator; + + if (pos + len + 2 < line_length) + { + pos += 2; + separator = ' '; + } + else + { + pos = 0; + separator = '\n'; + } + + putchar (','); + putchar (separator); + } + + print_file_name_and_frills (f); + pos += len; + } + putchar ('\n'); +} + +/* Assuming cursor is at position FROM, indent up to position TO. + Use a TAB character instead of two or more spaces whenever possible. */ + +static void +indent (size_t from, size_t to) +{ + while (from < to) + { + if (tabsize != 0 && to / tabsize > (from + 1) / tabsize) + { + putchar ('\t'); + from += tabsize - from % tabsize; + } + else + { + putchar (' '); + from++; + } + } +} + +/* Put DIRNAME/NAME into DEST, handling `.' and `/' properly. */ +/* FIXME: maybe remove this function someday. See about using a + non-malloc'ing version of file_name_concat. */ + +static void +attach (char *dest, const char *dirname, const char *name) +{ + const 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; +} + +/* Allocate enough column info suitable for the current number of + files and display columns, and initialize the info to represent the + narrowest possible columns. */ + +static void +init_column_info (void) +{ + size_t i; + size_t max_cols = MIN (max_idx, cwd_n_used); + + /* Currently allocated columns in column_info. */ + static size_t column_info_alloc; + + if (column_info_alloc < max_cols) + { + size_t new_column_info_alloc; + size_t *p; + + if (max_cols < max_idx / 2) + { + /* The number of columns is far less than the display width + allows. Grow the allocation, but only so that it's + double the current requirements. If the display is + extremely wide, this avoids allocating a lot of memory + that is never needed. */ + column_info = xnrealloc (column_info, max_cols, + 2 * sizeof *column_info); + new_column_info_alloc = 2 * max_cols; + } + else + { + column_info = xnrealloc (column_info, max_idx, sizeof *column_info); + new_column_info_alloc = max_idx; + } + + /* Allocate the new size_t objects by computing the triangle + formula n * (n + 1) / 2, except that we don't need to + allocate the part of the triangle that we've already + allocated. Check for address arithmetic overflow. */ + { + size_t column_info_growth = new_column_info_alloc - column_info_alloc; + size_t s = column_info_alloc + 1 + new_column_info_alloc; + size_t t = s * column_info_growth; + if (s < new_column_info_alloc || t / column_info_growth != s) + xalloc_die (); + p = xnmalloc (t / 2, sizeof *p); + } + + /* Grow the triangle by parceling out the cells just allocated. */ + for (i = column_info_alloc; i < new_column_info_alloc; i++) + { + column_info[i].col_arr = p; + p += i + 1; + } + + column_info_alloc = new_column_info_alloc; + } + + for (i = 0; i < max_cols; ++i) + { + size_t j; + + column_info[i].valid_len = true; + column_info[i].line_len = (i + 1) * MIN_COLUMN_WIDTH; + for (j = 0; j <= i; ++j) + column_info[i].col_arr[j] = MIN_COLUMN_WIDTH; + } +} + +/* Calculate the number of columns needed to represent the current set + of files in the current display width. */ + +static size_t +calculate_columns (bool by_columns) +{ + size_t filesno; /* Index into cwd_file. */ + size_t cols; /* Number of files across. */ + + /* Normally the maximum number of columns is determined by the + screen width. But if few files are available this might limit it + as well. */ + size_t max_cols = MIN (max_idx, cwd_n_used); + + init_column_info (); + + /* Compute the maximum number of possible columns. */ + for (filesno = 0; filesno < cwd_n_used; ++filesno) + { + struct fileinfo const *f = sorted_file[filesno]; + size_t name_length = length_of_file_name_and_frills (f); + size_t i; + + for (i = 0; i < max_cols; ++i) + { + if (column_info[i].valid_len) + { + size_t idx = (by_columns + ? filesno / ((cwd_n_used + i) / (i + 1)) + : filesno % (i + 1)); + size_t real_length = name_length + (idx == i ? 0 : 2); + + if (column_info[i].col_arr[idx] < real_length) + { + column_info[i].line_len += (real_length + - column_info[i].col_arr[idx]); + column_info[i].col_arr[idx] = real_length; + column_info[i].valid_len = (column_info[i].line_len + < line_length); + } + } + } + } + + /* Find maximum allowed columns. */ + for (cols = max_cols; 1 < cols; --cols) + { + if (column_info[cols - 1].valid_len) + break; + } + + return cols; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name); + fputs (_("\ +List information about the FILEs (the current directory by default).\n\ +Sort entries alphabetically if none of -cftuvSUX nor --sort.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -a, --all do not ignore entries starting with .\n\ + -A, --almost-all do not list implied . and ..\n\ + --author with -l, print the author of each file\n\ + -b, --escape print octal escapes for nongraphic characters\n\ +"), stdout); + fputs (_("\ + --block-size=SIZE use SIZE-byte blocks\n\ + -B, --ignore-backups do not list implied entries ending with ~\n\ + -c with -lt: sort by, and show, ctime (time of last\n\ + modification of file status information)\n\ + with -l: show ctime and sort by name\n\ + otherwise: sort by ctime\n\ +"), stdout); + fputs (_("\ + -C list entries by columns\n\ + --color[=WHEN] control whether color is used to distinguish file\n\ + types. WHEN may be `never', `always', or `auto'\n\ + -d, --directory list directory entries instead of contents,\n\ + and do not dereference symbolic links\n\ + -D, --dired generate output designed for Emacs' dired mode\n\ +"), stdout); + fputs (_("\ + -f do not sort, enable -aU, disable -ls --color\n\ + -F, --classify append indicator (one of */=>@|) to entries\n\ + --file-type likewise, except do not append `*'\n\ + --format=WORD across -x, commas -m, horizontal -x, long -l,\n\ + single-column -1, verbose -l, vertical -C\n\ + --full-time like -l --time-style=full-iso\n\ +"), stdout); + fputs (_("\ + -g like -l, but do not list owner\n\ +"), stdout); + fputs (_("\ + --group-directories-first\n\ + group directories before files\n\ +"), stdout); + fputs (_("\ + -G, --no-group in a long listing, don't print group names\n\ + -h, --human-readable with -l, print sizes in human readable format\n\ + (e.g., 1K 234M 2G)\n\ + --si likewise, but use powers of 1000 not 1024\n\ +"), stdout); + fputs (_("\ + -H, --dereference-command-line\n\ + follow symbolic links listed on the command line\n\ + --dereference-command-line-symlink-to-dir\n\ + follow each command line symbolic link\n\ + that points to a directory\n\ + --hide=PATTERN do not list implied entries matching shell PATTERN\n\ + (overridden by -a or -A)\n\ +"), stdout); + fputs (_("\ + --indicator-style=WORD append indicator with style WORD to entry names:\n\ + none (default), slash (-p),\n\ + file-type (--file-type), classify (-F)\n\ + -i, --inode print the index number of each file\n\ + -I, --ignore=PATTERN do not list implied entries matching shell PATTERN\n\ + -k like --block-size=1K\n\ +"), stdout); + fputs (_("\ + -l use a long listing format\n\ + -L, --dereference when showing file information for a symbolic\n\ + link, show information for the file the link\n\ + references rather than for the link itself\n\ + -m fill width with a comma separated list of entries\n\ +"), stdout); + fputs (_("\ + -n, --numeric-uid-gid like -l, but list numeric user and group IDs\n\ + -N, --literal print raw entry names (don't treat e.g. control\n\ + characters specially)\n\ + -o like -l, but do not list group information\n\ + -p, --indicator-style=slash\n\ + append / indicator to directories\n\ +"), stdout); + fputs (_("\ + -q, --hide-control-chars print ? instead of non graphic characters\n\ + --show-control-chars show non graphic characters as-is (default\n\ + unless program is `ls' and output is a terminal)\n\ + -Q, --quote-name enclose entry names in double quotes\n\ + --quoting-style=WORD use quoting style WORD for entry names:\n\ + literal, locale, shell, shell-always, c, escape\n\ +"), stdout); + fputs (_("\ + -r, --reverse reverse order while sorting\n\ + -R, --recursive list subdirectories recursively\n\ + -s, --size print the size of each file, in blocks\n\ +"), stdout); + fputs (_("\ + -S sort by file size\n\ + --sort=WORD sort by WORD instead of name: none -U,\n\ + extension -X, size -S, time -t, version -v\n\ + --time=WORD with -l, show time as WORD instead of modification\n\ + time: atime -u, access -u, use -u, ctime -c,\n\ + or status -c; use specified time as sort key\n\ + if --sort=time\n\ +"), stdout); + fputs (_("\ + --time-style=STYLE with -l, show times using style STYLE:\n\ + full-iso, long-iso, iso, locale, +FORMAT.\n\ + FORMAT is interpreted like `date'; if FORMAT is\n\ + FORMAT1FORMAT2, FORMAT1 applies to\n\ + non-recent files and FORMAT2 to recent files;\n\ + if STYLE is prefixed with `posix-', STYLE\n\ + takes effect only outside the POSIX locale\n\ +"), stdout); + fputs (_("\ + -t sort by modification time\n\ + -T, --tabsize=COLS assume tab stops at each COLS instead of 8\n\ +"), stdout); + fputs (_("\ + -u with -lt: sort by, and show, access time\n\ + with -l: show access time and sort by name\n\ + otherwise: sort by access time\n\ + -U do not sort; list entries in directory order\n\ + -v sort by version\n\ +"), stdout); + fputs (_("\ + -w, --width=COLS assume screen width instead of current value\n\ + -x list entries by lines instead of by columns\n\ + -X sort alphabetically by entry extension\n\ + -1 list one file per line\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\n\ +SIZE may be (or may be an integer optionally followed by) one of following:\n\ +kB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\ +"), stdout); + fputs (_("\ +\n\ +By default, color is not used to distinguish types of files. That is\n\ +equivalent to using --color=none. Using the --color option without the\n\ +optional WHEN argument is equivalent to using --color=always. With\n\ +--color=auto, color codes are output only if standard output is connected\n\ +to a terminal (tty). The environment variable LS_COLORS can influence the\n\ +colors, and can be set easily by the dircolors command.\n\ +"), stdout); + fputs (_("\ +\n\ +Exit status is 0 if OK, 1 if minor problems, 2 if serious trouble.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} diff --git a/src/ls.h b/src/ls.h new file mode 100644 index 0000000..028271f --- /dev/null +++ b/src/ls.h @@ -0,0 +1,10 @@ +/* This is for the `ls' program. */ +#define LS_LS 1 + +/* This is for the `dir' program. */ +#define LS_MULTI_COL 2 + +/* This is for the `vdir' program. */ +#define LS_LONG_FORMAT 3 + +extern int ls_mode; diff --git a/src/md5sum.c b/src/md5sum.c new file mode 100644 index 0000000..a8ce1cf --- /dev/null +++ b/src/md5sum.c @@ -0,0 +1,723 @@ +/* Compute MD5, SHA1, SHA224, SHA256, SHA384 or SHA512 checksum of files or strings + Copyright (C) 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Ulrich Drepper . */ + +#include + +#include +#include + +#include "system.h" + +#if HASH_ALGO_MD5 +# include "md5.h" +#endif +#if HASH_ALGO_SHA1 +# include "sha1.h" +#endif +#if HASH_ALGO_SHA256 || HASH_ALGO_SHA224 +# include "sha256.h" +#endif +#if HASH_ALGO_SHA512 || HASH_ALGO_SHA384 +# include "sha512.h" +#endif +#include "getline.h" +#include "error.h" +#include "quote.h" +#include "stdio--.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#if HASH_ALGO_MD5 +# define PROGRAM_NAME "md5sum" +# define DIGEST_TYPE_STRING "MD5" +# define DIGEST_STREAM md5_stream +# define DIGEST_BITS 128 +# define DIGEST_REFERENCE "RFC 1321" +# define DIGEST_ALIGN 4 +#elif HASH_ALGO_SHA1 +# define PROGRAM_NAME "sha1sum" +# define DIGEST_TYPE_STRING "SHA1" +# define DIGEST_STREAM sha1_stream +# define DIGEST_BITS 160 +# define DIGEST_REFERENCE "FIPS-180-1" +# define DIGEST_ALIGN 4 +#elif HASH_ALGO_SHA256 +# define PROGRAM_NAME "sha256sum" +# define DIGEST_TYPE_STRING "SHA256" +# define DIGEST_STREAM sha256_stream +# define DIGEST_BITS 256 +# define DIGEST_REFERENCE "FIPS-180-2" +# define DIGEST_ALIGN 4 +#elif HASH_ALGO_SHA224 +# define PROGRAM_NAME "sha224sum" +# define DIGEST_TYPE_STRING "SHA224" +# define DIGEST_STREAM sha224_stream +# define DIGEST_BITS 224 +# define DIGEST_REFERENCE "RFC 3874" +# define DIGEST_ALIGN 4 +#elif HASH_ALGO_SHA512 +# define PROGRAM_NAME "sha512sum" +# define DIGEST_TYPE_STRING "SHA512" +# define DIGEST_STREAM sha512_stream +# define DIGEST_BITS 512 +# define DIGEST_REFERENCE "FIPS-180-2" +# define DIGEST_ALIGN 8 +#elif HASH_ALGO_SHA384 +# define PROGRAM_NAME "sha384sum" +# define DIGEST_TYPE_STRING "SHA384" +# define DIGEST_STREAM sha384_stream +# define DIGEST_BITS 384 +# define DIGEST_REFERENCE "FIPS-180-2" +# define DIGEST_ALIGN 8 +#else +# error "Can't decide which hash algorithm to compile." +#endif + +#define DIGEST_HEX_BYTES (DIGEST_BITS / 4) +#define DIGEST_BIN_BYTES (DIGEST_BITS / 8) + +#define AUTHORS "Ulrich Drepper", "Scott Miller", "David Madore" + +/* The minimum length of a valid digest line. This length does + not include any newline character at the end of a line. */ +#define MIN_DIGEST_LINE_LENGTH \ + (DIGEST_HEX_BYTES /* length of hexadecimal message digest */ \ + + 2 /* blank and binary indicator */ \ + + 1 /* minimum filename length */ ) + +/* True if any of the files read were the standard input. */ +static bool have_read_stdin; + +/* The minimum length of a valid checksum line for the selected algorithm. */ +static size_t min_digest_line_length; + +/* Set to the length of a digest hex string for the selected algorithm. */ +static size_t digest_hex_bytes; + +/* With --check, don't generate any output. + The exit code indicates success or failure. */ +static bool status_only = false; + +/* With --check, print a message to standard error warning about each + improperly formatted checksum line. */ +static bool warn = false; + +/* The name this program was run with. */ +char *program_name; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + STATUS_OPTION = CHAR_MAX + 1 +}; + +static const struct option long_options[] = +{ + { "binary", no_argument, NULL, 'b' }, + { "check", no_argument, NULL, 'c' }, + { "status", no_argument, NULL, STATUS_OPTION }, + { "text", no_argument, NULL, 't' }, + { "warn", no_argument, NULL, 'w' }, + { GETOPT_HELP_OPTION_DECL }, + { GETOPT_VERSION_OPTION_DECL }, + { NULL, 0, NULL, 0 } +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [FILE]...\n\ +Print or check %s (%d-bit) checksums.\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +"), + program_name, + DIGEST_TYPE_STRING, + DIGEST_BITS); + if (O_BINARY) + fputs (_("\ + -b, --binary read in binary mode (default unless reading tty stdin)\n\ +"), stdout); + else + fputs (_("\ + -b, --binary read in binary mode\n\ +"), stdout); + printf (_("\ + -c, --check read %s sums from the FILEs and check them\n"), + DIGEST_TYPE_STRING); + if (O_BINARY) + fputs (_("\ + -t, --text read in text mode (default if reading tty stdin)\n\ +"), stdout); + else + fputs (_("\ + -t, --text read in text mode (default)\n\ +"), stdout); + fputs (_("\ +\n\ +The following two options are useful only when verifying checksums:\n\ + --status don't output anything, status code shows success\n\ + -w, --warn warn about improperly formatted checksum lines\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\ +\n\ +The sums are computed as described in %s. When checking, the input\n\ +should be a former output of this program. The default mode is to print\n\ +a line with checksum, a character indicating type (`*' for binary, ` ' for\n\ +text), and name for each FILE.\n"), + DIGEST_REFERENCE); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + + exit (status); +} + +#define ISWHITE(c) ((c) == ' ' || (c) == '\t') + +/* Split the checksum string S (of length S_LEN) from a BSD 'md5' or + 'sha1' command into two parts: a hexadecimal digest, and the file + name. S is modified. Return true if successful. */ + +static bool +bsd_split_3 (char *s, size_t s_len, unsigned char **hex_digest, char **file_name) +{ + size_t i; + + *file_name = s; + + /* Find end of filename. The BSD 'md5' and 'sha1' commands do not escape + filenames, so search backwards for the last ')'. */ + i = s_len - 1; + while (i && s[i] != ')') + i--; + + if (s[i] != ')') + return false; + + s[i++] = '\0'; + + while (ISWHITE (s[i])) + i++; + + if (s[i] != '=') + return false; + + i++; + + while (ISWHITE (s[i])) + i++; + + *hex_digest = (unsigned char *) &s[i]; + return true; +} + +/* Split the string S (of length S_LEN) into three parts: + a hexadecimal digest, binary flag, and the file name. + S is modified. Return true if successful. */ + +static bool +split_3 (char *s, size_t s_len, + unsigned char **hex_digest, int *binary, char **file_name) +{ + size_t i; + bool escaped_filename = false; + size_t algo_name_len; + + i = 0; + while (ISWHITE (s[i])) + ++i; + + /* Check for BSD-style checksum line. */ + algo_name_len = strlen (DIGEST_TYPE_STRING); + if (strncmp (s + i, DIGEST_TYPE_STRING, algo_name_len) == 0) + { + if (strncmp (s + i + algo_name_len, " (", 2) == 0) + { + *binary = 0; + return bsd_split_3 (s + i + algo_name_len + 2, + s_len - (i + algo_name_len + 2), + hex_digest, file_name); + } + } + + /* Ignore this line if it is too short. + Each line must have at least `min_digest_line_length - 1' (or one more, if + the first is a backslash) more characters to contain correct message digest + information. */ + if (s_len - i < min_digest_line_length + (s[i] == '\\')) + return false; + + if (s[i] == '\\') + { + ++i; + escaped_filename = true; + } + *hex_digest = (unsigned char *) &s[i]; + + /* The first field has to be the n-character hexadecimal + representation of the message digest. If it is not followed + immediately by a white space it's an error. */ + i += digest_hex_bytes; + if (!ISWHITE (s[i])) + return false; + + s[i++] = '\0'; + + if (s[i] != ' ' && s[i] != '*') + return false; + *binary = (s[i++] == '*'); + + /* All characters between the type indicator and end of line are + significant -- that includes leading and trailing white space. */ + *file_name = &s[i]; + + if (escaped_filename) + { + /* Translate each `\n' string in the file name to a NEWLINE, + and each `\\' string to a backslash. */ + + char *dst = &s[i]; + + while (i < s_len) + { + switch (s[i]) + { + case '\\': + if (i == s_len - 1) + { + /* A valid line does not end with a backslash. */ + return false; + } + ++i; + switch (s[i++]) + { + case 'n': + *dst++ = '\n'; + break; + case '\\': + *dst++ = '\\'; + break; + default: + /* Only `\' or `n' may follow a backslash. */ + return false; + } + break; + + case '\0': + /* The file name may not contain a NUL. */ + return false; + break; + + default: + *dst++ = s[i++]; + break; + } + } + *dst = '\0'; + } + return true; +} + +static bool +hex_digits (unsigned char const *s) +{ + while (*s) + { + if (!isxdigit (*s)) + return false; + ++s; + } + return true; +} + +/* An interface to the function, DIGEST_STREAM. + Operate on FILENAME (it may be "-"). + + *BINARY indicates whether the file is binary. BINARY < 0 means it + depends on whether binary mode makes any difference and the file is + a terminal; in that case, clear *BINARY if the file was treated as + text because it was a terminal. + + Put the checksum in *BIN_RESULT, which must be properly aligned. + Return true if successful. */ + +static bool +digest_file (const char *filename, int *binary, unsigned char *bin_result) +{ + FILE *fp; + int err; + bool is_stdin = STREQ (filename, "-"); + + if (is_stdin) + { + have_read_stdin = true; + fp = stdin; + if (O_BINARY && *binary) + { + if (*binary < 0) + *binary = ! isatty (STDIN_FILENO); + if (*binary) + freopen (NULL, "rb", stdin); + } + } + else + { + fp = fopen (filename, (O_BINARY && *binary ? "rb" : "r")); + if (fp == NULL) + { + error (0, errno, "%s", filename); + return false; + } + } + + err = DIGEST_STREAM (fp, bin_result); + if (err) + { + error (0, errno, "%s", filename); + if (fp != stdin) + fclose (fp); + return false; + } + + if (!is_stdin && fclose (fp) != 0) + { + error (0, errno, "%s", filename); + return false; + } + + return true; +} + +static bool +digest_check (const char *checkfile_name) +{ + FILE *checkfile_stream; + uintmax_t n_properly_formatted_lines = 0; + uintmax_t n_mismatched_checksums = 0; + uintmax_t n_open_or_read_failures = 0; + unsigned char bin_buffer_unaligned[DIGEST_BIN_BYTES + DIGEST_ALIGN]; + /* Make sure bin_buffer is properly aligned. */ + unsigned char *bin_buffer = ptr_align (bin_buffer_unaligned, DIGEST_ALIGN); + uintmax_t line_number; + char *line; + size_t line_chars_allocated; + bool is_stdin = STREQ (checkfile_name, "-"); + + if (is_stdin) + { + have_read_stdin = true; + checkfile_name = _("standard input"); + checkfile_stream = stdin; + } + else + { + checkfile_stream = fopen (checkfile_name, "r"); + if (checkfile_stream == NULL) + { + error (0, errno, "%s", checkfile_name); + return false; + } + } + + line_number = 0; + line = NULL; + line_chars_allocated = 0; + do + { + char *filename; + int binary; + unsigned char *hex_digest IF_LINT (= NULL); + ssize_t line_length; + + ++line_number; + if (line_number == 0) + error (EXIT_FAILURE, 0, _("%s: too many checksum lines"), + checkfile_name); + + line_length = getline (&line, &line_chars_allocated, checkfile_stream); + if (line_length <= 0) + break; + + /* Ignore comment lines, which begin with a '#' character. */ + if (line[0] == '#') + continue; + + /* Remove any trailing newline. */ + if (line[line_length - 1] == '\n') + line[--line_length] = '\0'; + + if (! (split_3 (line, line_length, &hex_digest, &binary, &filename) + && ! (is_stdin && STREQ (filename, "-")) + && hex_digits (hex_digest))) + { + if (warn) + { + error (0, 0, + _("%s: %" PRIuMAX + ": improperly formatted %s checksum line"), + checkfile_name, line_number, + DIGEST_TYPE_STRING); + } + } + else + { + static const char bin2hex[] = { '0', '1', '2', '3', + '4', '5', '6', '7', + '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f' }; + bool ok; + + ++n_properly_formatted_lines; + + ok = digest_file (filename, &binary, bin_buffer); + + if (!ok) + { + ++n_open_or_read_failures; + if (!status_only) + { + printf (_("%s: FAILED open or read\n"), filename); + fflush (stdout); + } + } + else + { + size_t digest_bin_bytes = digest_hex_bytes / 2; + size_t cnt; + /* Compare generated binary number with text representation + in check file. Ignore case of hex digits. */ + for (cnt = 0; cnt < digest_bin_bytes; ++cnt) + { + if (tolower (hex_digest[2 * cnt]) + != bin2hex[bin_buffer[cnt] >> 4] + || (tolower (hex_digest[2 * cnt + 1]) + != (bin2hex[bin_buffer[cnt] & 0xf]))) + break; + } + if (cnt != digest_bin_bytes) + ++n_mismatched_checksums; + + if (!status_only) + { + printf ("%s: %s\n", filename, + (cnt != digest_bin_bytes ? _("FAILED") : _("OK"))); + fflush (stdout); + } + } + } + } + while (!feof (checkfile_stream) && !ferror (checkfile_stream)); + + free (line); + + if (ferror (checkfile_stream)) + { + error (0, 0, _("%s: read error"), checkfile_name); + return false; + } + + if (!is_stdin && fclose (checkfile_stream) != 0) + { + error (0, errno, "%s", checkfile_name); + return false; + } + + if (n_properly_formatted_lines == 0) + { + /* Warn if no tests are found. */ + error (0, 0, _("%s: no properly formatted %s checksum lines found"), + checkfile_name, DIGEST_TYPE_STRING); + } + else + { + if (!status_only) + { + if (n_open_or_read_failures != 0) + error (0, 0, + ngettext ("WARNING: %" PRIuMAX " of %" PRIuMAX + " listed file could not be read", + "WARNING: %" PRIuMAX " of %" PRIuMAX + " listed files could not be read", + select_plural (n_properly_formatted_lines)), + n_open_or_read_failures, n_properly_formatted_lines); + + if (n_mismatched_checksums != 0) + { + uintmax_t n_computed_checksums = + (n_properly_formatted_lines - n_open_or_read_failures); + error (0, 0, + ngettext ("WARNING: %" PRIuMAX " of %" PRIuMAX + " computed checksum did NOT match", + "WARNING: %" PRIuMAX " of %" PRIuMAX + " computed checksums did NOT match", + select_plural (n_computed_checksums)), + n_mismatched_checksums, n_computed_checksums); + } + } + } + + return (n_properly_formatted_lines != 0 + && n_mismatched_checksums == 0 + && n_open_or_read_failures == 0); +} + +int +main (int argc, char **argv) +{ + unsigned char bin_buffer_unaligned[DIGEST_BIN_BYTES + DIGEST_ALIGN]; + /* Make sure bin_buffer is properly aligned. */ + unsigned char *bin_buffer = ptr_align (bin_buffer_unaligned, DIGEST_ALIGN); + bool do_check = false; + int opt; + bool ok = true; + int binary = -1; + + /* Setting values of global variables. */ + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((opt = getopt_long (argc, argv, "bctw", long_options, NULL)) != -1) + switch (opt) + { + case 'b': + binary = 1; + break; + case 'c': + do_check = true; + break; + case STATUS_OPTION: + status_only = true; + warn = false; + break; + case 't': + binary = 0; + break; + case 'w': + status_only = false; + warn = true; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + + min_digest_line_length = MIN_DIGEST_LINE_LENGTH; + digest_hex_bytes = DIGEST_HEX_BYTES; + + if (0 <= binary && do_check) + { + error (0, 0, _("the --binary and --text options are meaningless when " + "verifying checksums")); + usage (EXIT_FAILURE); + } + + if (status_only & !do_check) + { + error (0, 0, + _("the --status option is meaningful only when verifying checksums")); + usage (EXIT_FAILURE); + } + + if (warn & !do_check) + { + error (0, 0, + _("the --warn option is meaningful only when verifying checksums")); + usage (EXIT_FAILURE); + } + + if (!O_BINARY && binary < 0) + binary = 0; + + if (optind == argc) + argv[argc++] = "-"; + + for (; optind < argc; ++optind) + { + char *file = argv[optind]; + + if (do_check) + ok &= digest_check (file); + else + { + int file_is_binary = binary; + + if (! digest_file (file, &file_is_binary, bin_buffer)) + ok = false; + else + { + size_t i; + + /* Output a leading backslash if the file name contains + a newline or backslash. */ + if (strchr (file, '\n') || strchr (file, '\\')) + putchar ('\\'); + + for (i = 0; i < (digest_hex_bytes / 2); ++i) + printf ("%02x", bin_buffer[i]); + + putchar (' '); + if (file_is_binary) + putchar ('*'); + else + putchar (' '); + + /* Translate each NEWLINE byte to the string, "\\n", + and each backslash to "\\\\". */ + for (i = 0; i < strlen (file); ++i) + { + switch (file[i]) + { + case '\n': + fputs ("\\n", stdout); + break; + + case '\\': + fputs ("\\\\", stdout); + break; + + default: + putchar (file[i]); + break; + } + } + putchar ('\n'); + } + } + } + + if (have_read_stdin && fclose (stdin) == EOF) + error (EXIT_FAILURE, errno, _("standard input")); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/mkdir.c b/src/mkdir.c new file mode 100644 index 0000000..6fa0ac2 --- /dev/null +++ b/src/mkdir.c @@ -0,0 +1,205 @@ +/* mkdir -- make directories + Copyright (C) 90, 1995-2002, 2004, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* David MacKenzie */ + +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "lchmod.h" +#include "mkdir-p.h" +#include "modechange.h" +#include "quote.h" +#include "savewd.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "mkdir" + +#define AUTHORS "David MacKenzie" + +/* The name this program was run with. */ +char *program_name; + +static struct option const longopts[] = +{ + {"mode", required_argument, NULL, 'm'}, + {"parents", no_argument, NULL, 'p'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION] DIRECTORY...\n"), program_name); + fputs (_("\ +Create the DIRECTORY(ies), if they do not already exist.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -m, --mode=MODE set file mode (as in chmod), not a=rwx - umask\n\ + -p, --parents no error if existing, make parent directories as needed\n\ + -v, --verbose print a message for each created directory\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Options passed to subsidiary functions. */ +struct mkdir_options +{ + /* Function to make an ancestor, or NULL if ancestors should not be + made. */ + int (*make_ancestor_function) (char const *, char const *, void *); + + /* Mode for ancestor directory. */ + mode_t ancestor_mode; + + /* Mode for directory itself. */ + mode_t mode; + + /* File mode bits affected by MODE. */ + mode_t mode_bits; + + /* If not null, format to use when reporting newly made directories. */ + char const *created_directory_format; +}; + +/* Report that directory DIR was made, if OPTIONS requests this. */ +static void +announce_mkdir (char const *dir, void *options) +{ + struct mkdir_options const *o = options; + if (o->created_directory_format) + error (0, 0, o->created_directory_format, quote (dir)); +} + +/* Make ancestor directory DIR, whose last component is COMPONENT, + with options OPTIONS. Assume the working directory is COMPONENT's + parent. Return 0 if successful and the resulting directory is + readable, 1 if successful but the resulting directory is not + readable, -1 (setting errno) otherwise. */ +static int +make_ancestor (char const *dir, char const *component, void *options) +{ + struct mkdir_options const *o = options; + int r = mkdir (component, o->ancestor_mode); + if (r == 0) + { + r = ! (o->ancestor_mode & S_IRUSR); + announce_mkdir (dir, options); + } + return r; +} + +/* Process a command-line file name. */ +static int +process_dir (char *dir, struct savewd *wd, void *options) +{ + struct mkdir_options const *o = options; + return (make_dir_parents (dir, wd, o->make_ancestor_function, options, + o->mode, announce_mkdir, + o->mode_bits, (uid_t) -1, (gid_t) -1, true) + ? EXIT_SUCCESS + : EXIT_FAILURE); +} + +int +main (int argc, char **argv) +{ + const char *specified_mode = NULL; + int optc; + struct mkdir_options options; + options.make_ancestor_function = NULL; + options.mode = S_IRWXUGO; + options.mode_bits = 0; + options.created_directory_format = NULL; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "pm:v", longopts, NULL)) != -1) + { + switch (optc) + { + case 'p': + options.make_ancestor_function = make_ancestor; + break; + case 'm': + specified_mode = optarg; + break; + case 'v': /* --verbose */ + options.created_directory_format = _("created directory %s"); + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + if (optind == argc) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + if (options.make_ancestor_function || specified_mode) + { + mode_t umask_value = umask (0); + + options.ancestor_mode = (S_IRWXUGO & ~umask_value) | (S_IWUSR | S_IXUSR); + + if (specified_mode) + { + struct mode_change *change = mode_compile (specified_mode); + if (!change) + error (EXIT_FAILURE, 0, _("invalid mode %s"), + quote (specified_mode)); + options.mode = mode_adjust (S_IRWXUGO, true, umask_value, change, + &options.mode_bits); + free (change); + } + else + options.mode = S_IRWXUGO & ~umask_value; + } + + exit (savewd_process_files (argc - optind, argv + optind, + process_dir, &options)); +} diff --git a/src/mkfifo.c b/src/mkfifo.c new file mode 100644 index 0000000..d329b79 --- /dev/null +++ b/src/mkfifo.c @@ -0,0 +1,129 @@ +/* mkfifo -- make fifo's (named pipes) + Copyright (C) 90, 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* David MacKenzie */ + +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "modechange.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "mkfifo" + +#define AUTHORS "David MacKenzie" + +/* The name this program was run with. */ +char *program_name; + +static struct option const longopts[] = +{ + {"mode", required_argument, NULL, 'm'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION] NAME...\n"), program_name); + fputs (_("\ +Create named pipes (FIFOs) with the given NAMEs.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -m, --mode=MODE set file permission bits to MODE, not a=rw - umask\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + mode_t newmode; + char const *specified_mode = NULL; + int exit_status = EXIT_SUCCESS; + int optc; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "m:", longopts, NULL)) != -1) + { + switch (optc) + { + case 'm': + specified_mode = optarg; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + if (optind == argc) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + newmode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (specified_mode) + { + struct mode_change *change = mode_compile (specified_mode); + if (!change) + error (EXIT_FAILURE, 0, _("invalid mode")); + newmode = mode_adjust (newmode, false, umask (0), change, NULL); + free (change); + if (newmode & ~S_IRWXUGO) + error (EXIT_FAILURE, 0, + _("mode must specify only file permission bits")); + } + + for (; optind < argc; ++optind) + if (mkfifo (argv[optind], newmode) != 0) + { + error (0, errno, _("cannot create fifo %s"), quote (argv[optind])); + exit_status = EXIT_FAILURE; + } + + exit (exit_status); +} diff --git a/src/mknod.c b/src/mknod.c new file mode 100644 index 0000000..43a1b9d --- /dev/null +++ b/src/mknod.c @@ -0,0 +1,221 @@ +/* mknod -- make special files + Copyright (C) 90, 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by David MacKenzie */ + +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "modechange.h" +#include "quote.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "mknod" + +#define AUTHORS "David MacKenzie" + +/* The name this program was run with. */ +char *program_name; + +static struct option const longopts[] = +{ + {"mode", required_argument, NULL, 'm'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... NAME TYPE [MAJOR MINOR]\n"), + program_name); + fputs (_("\ +Create the special file NAME of the given TYPE.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -m, --mode=MODE set file permission bits to MODE, not a=rw - umask\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they\n\ +must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X,\n\ +it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal;\n\ +otherwise, as decimal. TYPE may be:\n\ +"), stdout); + fputs (_("\ +\n\ + b create a block (buffered) special file\n\ + c, u create a character (unbuffered) special file\n\ + p create a FIFO\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + mode_t newmode; + char const *specified_mode = NULL; + int optc; + int expected_operands; + mode_t node_type; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "m:", longopts, NULL)) != -1) + { + switch (optc) + { + case 'm': + specified_mode = optarg; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + newmode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (specified_mode) + { + struct mode_change *change = mode_compile (specified_mode); + if (!change) + error (EXIT_FAILURE, 0, _("invalid mode")); + newmode = mode_adjust (newmode, false, umask (0), change, NULL); + free (change); + if (newmode & ~S_IRWXUGO) + error (EXIT_FAILURE, 0, + _("mode must specify only file permission bits")); + } + + /* If the number of arguments is 0 or 1, + or (if it's 2 or more and the second one starts with `p'), then there + must be exactly two operands. Otherwise, there must be four. */ + expected_operands = (argc <= optind + || (optind + 1 < argc && argv[optind + 1][0] == 'p') + ? 2 : 4); + + if (argc - optind < expected_operands) + { + if (argc <= optind) + error (0, 0, _("missing operand")); + else + error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); + if (expected_operands == 4 && argc - optind == 2) + fprintf (stderr, "%s\n", + _("Special files require major and minor device numbers.")); + usage (EXIT_FAILURE); + } + + if (expected_operands < argc - optind) + { + error (0, 0, _("extra operand %s"), + quote (argv[optind + expected_operands])); + if (expected_operands == 2 && argc - optind == 4) + fprintf (stderr, "%s\n", + _("Fifos do not have major and minor device numbers.")); + usage (EXIT_FAILURE); + } + + /* 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 (EXIT_FAILURE, 0, _("block special files not supported")); +#else + node_type = S_IFBLK; +#endif + goto block_or_character; + + case 'c': /* `character' */ + case 'u': /* `unbuffered' */ +#ifndef S_IFCHR + error (EXIT_FAILURE, 0, _("character special files not supported")); +#else + node_type = S_IFCHR; +#endif + goto block_or_character; + + block_or_character: + { + char const *s_major = argv[optind + 2]; + char const *s_minor = argv[optind + 3]; + uintmax_t i_major, i_minor; + dev_t device; + + if (xstrtoumax (s_major, NULL, 0, &i_major, NULL) != LONGINT_OK + || i_major != (major_t) i_major) + error (EXIT_FAILURE, 0, + _("invalid major device number %s"), quote (s_major)); + + if (xstrtoumax (s_minor, NULL, 0, &i_minor, NULL) != LONGINT_OK + || i_minor != (minor_t) i_minor) + error (EXIT_FAILURE, 0, + _("invalid minor device number %s"), quote (s_minor)); + + device = makedev (i_major, i_minor); +#ifdef NODEV + if (device == NODEV) + error (EXIT_FAILURE, 0, _("invalid device %s %s"), s_major, s_minor); +#endif + + if (mknod (argv[optind], newmode | node_type, device) != 0) + error (EXIT_FAILURE, errno, "%s", quote (argv[optind])); + } + break; + + case 'p': /* `pipe' */ + if (mkfifo (argv[optind], newmode) != 0) + error (EXIT_FAILURE, errno, "%s", quote (argv[optind])); + break; + + default: + error (0, 0, _("invalid device type %s"), quote (argv[optind + 1])); + usage (EXIT_FAILURE); + } + + exit (EXIT_SUCCESS); +} diff --git a/src/mv.c b/src/mv.c new file mode 100644 index 0000000..1d1ddda --- /dev/null +++ b/src/mv.c @@ -0,0 +1,486 @@ +/* mv -- move or rename files + Copyright (C) 86, 89, 90, 91, 1995-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Mike Parker, David MacKenzie, and Jim Meyering */ + +#include +#include +#include +#include +#include + +#include "system.h" +#include "argmatch.h" +#include "backupfile.h" +#include "copy.h" +#include "cp-hash.h" +#include "error.h" +#include "filenamecat.h" +#include "quote.h" +#include "remove.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "mv" + +#define AUTHORS "Mike Parker", "David MacKenzie", "Jim Meyering" + +/* 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 + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + REPLY_OPTION = CHAR_MAX + 1, + STRIP_TRAILING_SLASHES_OPTION +}; + +/* The name this program was run with. */ +char *program_name; + +/* Remove any trailing slashes from each SOURCE argument. */ +static bool remove_trailing_slashes; + +/* Valid arguments to the `--reply' option. */ +static char const* const reply_args[] = +{ + "yes", "no", "query", NULL +}; + +/* The values that correspond to the above strings. */ +static int const reply_vals[] = +{ + I_ALWAYS_YES, I_ALWAYS_NO, I_ASK_USER +}; + +static struct option const long_options[] = +{ + {"backup", optional_argument, NULL, 'b'}, + {"force", no_argument, NULL, 'f'}, + {"interactive", no_argument, NULL, 'i'}, + {"no-target-directory", no_argument, NULL, 'T'}, + {"reply", required_argument, NULL, REPLY_OPTION}, /* Deprecated 2005-07-03, + remove in 2008. */ + {"strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION}, + {"suffix", required_argument, NULL, 'S'}, + {"target-directory", required_argument, NULL, 't'}, + {"update", no_argument, NULL, 'u'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +static void +rm_option_init (struct rm_options *x) +{ + x->ignore_missing_files = false; + x->root_dev_ino = NULL; + x->recursive = true; + x->one_file_system = false; + + /* Should we prompt for removal, too? No. Prompting for the `move' + part is enough. It implies removal. */ + x->interactive = RMI_NEVER; + x->stdin_tty = false; + + x->verbose = false; + + /* Since this program may well have to process additional command + line arguments after any call to `rm', that function must preserve + the initial working directory, in case one of those is a + `.'-relative name. */ + x->require_restore_cwd = true; +} + +static void +cp_option_init (struct cp_options *x) +{ + x->copy_as_regular = false; /* FIXME: maybe make this an option */ + x->dereference = DEREF_NEVER; + x->unlink_dest_before_opening = false; + x->unlink_dest_after_failed_open = false; + x->hard_link = false; + x->interactive = I_UNSPECIFIED; + x->move_mode = true; + x->chown_privileges = chown_privileges (); + x->one_file_system = false; + x->preserve_ownership = true; + x->preserve_links = true; + x->preserve_mode = true; + x->preserve_timestamps = true; + x->require_preserve = false; /* FIXME: maybe make this an option */ + x->recursive = true; + x->sparse_mode = SPARSE_AUTO; /* FIXME: maybe make this an option */ + x->symbolic_link = false; + x->set_mode = false; + x->mode = 0; + x->stdin_tty = isatty (STDIN_FILENO); + + x->update = false; + x->verbose = false; + x->dest_info = NULL; + x->src_info = NULL; +} + +/* FILE is the last operand of this command. Return true if FILE is a + directory. But report an error if there is a problem accessing FILE, other + than nonexistence (errno == ENOENT). */ + +static bool +target_directory_operand (char const *file) +{ + struct stat st; + int err = (stat (file, &st) == 0 ? 0 : errno); + bool is_a_dir = !err && S_ISDIR (st.st_mode); + if (err && err != ENOENT) + error (EXIT_FAILURE, err, _("accessing %s"), quote (file)); + return is_a_dir; +} + +/* Move SOURCE onto DEST. Handles cross-file-system moves. + If SOURCE is a directory, DEST must not exist. + Return true if successful. */ + +static bool +do_move (const char *source, const char *dest, const struct cp_options *x) +{ + bool copy_into_self; + bool rename_succeeded; + bool ok = copy (source, dest, false, x, ©_into_self, &rename_succeeded); + + if (ok) + { + char const *dir_to_remove; + if (copy_into_self) + { + /* In general, when copy returns with copy_into_self set, SOURCE is + the same as, or a parent of DEST. In this case we know it's a + parent. It doesn't make sense to move a directory into itself, and + besides in some situations doing so would give highly nonintuitive + results. Run this `mkdir b; touch a c; mv * b' in an empty + directory. Here's the result of running echo `find b -print`: + b b/a b/b b/b/a b/c. Notice that only file `a' was copied + into b/b. Handle this by giving a diagnostic, removing the + copied-into-self directory, DEST (`b/b' in the example), + and failing. */ + + dir_to_remove = NULL; + ok = false; + } + else if (rename_succeeded) + { + /* No need to remove anything. SOURCE was successfully + renamed to DEST. Or the user declined to rename a file. */ + dir_to_remove = NULL; + } + else + { + /* This may mean SOURCE and DEST referred to different devices. + It may also conceivably mean that even though they referred + to the same device, rename wasn't implemented for that device. + + E.g., (from Joel N. Weber), + [...] there might someday be cases where you can't rename + but you can copy where the device name is the same, especially + on Hurd. Consider an ftpfs with a primitive ftp server that + supports uploading, downloading and deleting, but not renaming. + + Also, note that comparing device numbers is not a reliable + check for `can-rename'. Some systems can be set up so that + files from many different physical devices all have the same + st_dev field. This is a feature of some NFS mounting + configurations. + + We reach this point if SOURCE has been successfully copied + to DEST. Now we have to remove SOURCE. + + This function used to resort to copying only when rename + failed and set errno to EXDEV. */ + + dir_to_remove = source; + } + + if (dir_to_remove != NULL) + { + struct rm_options rm_options; + enum RM_status status; + + rm_option_init (&rm_options); + rm_options.verbose = x->verbose; + + status = rm (1, &dir_to_remove, &rm_options); + assert (VALID_STATUS (status)); + if (status == RM_ERROR) + ok = false; + } + } + + return ok; +} + +/* Move file SOURCE onto DEST. Handles the case when DEST is a directory. + Treat DEST as a directory if DEST_IS_DIR. + Return true if successful. */ + +static bool +movefile (char *source, char *dest, bool dest_is_dir, + const struct cp_options *x) +{ + bool ok; + + /* This code was introduced to handle the ambiguity in the semantics + of mv that is induced by the varying semantics of the rename function. + Some systems (e.g., Linux) have a rename function that honors a + trailing slash, while others (like Solaris 5,6,7) have a rename + function that ignores a trailing slash. I believe the Linux + rename semantics are POSIX and susv2 compliant. */ + + if (remove_trailing_slashes) + strip_trailing_slashes (source); + + if (dest_is_dir) + { + /* Treat DEST as a directory; build the full filename. */ + char const *src_basename = last_component (source); + char *new_dest = file_name_concat (dest, src_basename, NULL); + strip_trailing_slashes (new_dest); + ok = do_move (source, new_dest, x); + free (new_dest); + } + else + { + ok = do_move (source, dest, x); + } + + return ok; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [-T] SOURCE DEST\n\ + or: %s [OPTION]... SOURCE... DIRECTORY\n\ + or: %s [OPTION]... -t DIRECTORY SOURCE...\n\ +"), + program_name, program_name, program_name); + fputs (_("\ +Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + --backup[=CONTROL] make a backup of each existing destination file\n\ + -b like --backup but does not accept an argument\n\ + -f, --force do not prompt before overwriting\n\ + -i, --interactive prompt before overwrite\n\ +"), stdout); + fputs (_("\ + --strip-trailing-slashes remove any trailing slashes from each SOURCE\n\ + argument\n\ + -S, --suffix=SUFFIX override the usual backup suffix\n\ +"), stdout); + fputs (_("\ + -t, --target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY\n\ + -T, --no-target-directory treat DEST as a normal file\n\ + -u, --update move only when the SOURCE file is newer\n\ + than the destination file or when the\n\ + destination file is missing\n\ + -v, --verbose explain what is being done\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ +The version control method may be selected via the --backup option or through\n\ +the VERSION_CONTROL environment variable. Here are the values:\n\ +\n\ +"), stdout); + fputs (_("\ + none, off never make backups (even if --backup is given)\n\ + numbered, t make numbered backups\n\ + existing, nil numbered if numbered backups exist, simple otherwise\n\ + simple, never always make simple backups\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int c; + bool ok; + bool make_backups = false; + char *backup_suffix_string; + char *version_control_string = NULL; + struct cp_options x; + char *target_directory = NULL; + bool no_target_directory = false; + int n_files; + char **file; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + cp_option_init (&x); + + /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless + we'll actually use backup_suffix_string. */ + backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); + + while ((c = getopt_long (argc, argv, "bfit:uvS:T", long_options, NULL)) + != -1) + { + switch (c) + { + case 'b': + make_backups = true; + if (optarg) + version_control_string = optarg; + break; + case 'f': + x.interactive = I_ALWAYS_YES; + break; + case 'i': + x.interactive = I_ASK_USER; + break; + case REPLY_OPTION: /* Deprecated */ + x.interactive = XARGMATCH ("--reply", optarg, + reply_args, reply_vals); + error (0, 0, + _("the --reply option is deprecated; use -i or -f instead")); + break; + case STRIP_TRAILING_SLASHES_OPTION: + remove_trailing_slashes = true; + break; + case 't': + if (target_directory) + error (EXIT_FAILURE, 0, _("multiple target directories specified")); + else + { + struct stat st; + if (stat (optarg, &st) != 0) + error (EXIT_FAILURE, errno, _("accessing %s"), quote (optarg)); + if (! S_ISDIR (st.st_mode)) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (optarg)); + } + target_directory = optarg; + break; + case 'T': + no_target_directory = true; + break; + case 'u': + x.update = true; + break; + case 'v': + x.verbose = true; + break; + case 'S': + make_backups = true; + backup_suffix_string = optarg; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + n_files = argc - optind; + file = argv + optind; + + if (n_files <= !target_directory) + { + if (n_files <= 0) + error (0, 0, _("missing file operand")); + else + error (0, 0, _("missing destination file operand after %s"), + quote (file[0])); + usage (EXIT_FAILURE); + } + + if (no_target_directory) + { + if (target_directory) + error (EXIT_FAILURE, 0, + _("Cannot combine --target-directory (-t) " + "and --no-target-directory (-T)")); + if (2 < n_files) + { + error (0, 0, _("extra operand %s"), quote (file[2])); + usage (EXIT_FAILURE); + } + } + else if (!target_directory) + { + assert (2 <= n_files); + if (target_directory_operand (file[n_files - 1])) + target_directory = file[--n_files]; + else if (2 < n_files) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (file[n_files - 1])); + } + + if (backup_suffix_string) + simple_backup_suffix = xstrdup (backup_suffix_string); + + x.backup_type = (make_backups + ? xget_version (_("backup type"), + version_control_string) + : no_backups); + + hash_init (); + + if (target_directory) + { + int i; + + /* Initialize the hash table only if we'll need it. + The problem it is used to detect can arise only if there are + two or more files to move. */ + if (2 <= n_files) + dest_info_init (&x); + + ok = true; + for (i = 0; i < n_files; ++i) + ok &= movefile (file[i], target_directory, true, &x); + } + else + ok = movefile (file[0], file[1], false, &x); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/nice.c b/src/nice.c new file mode 100644 index 0000000..19b38bc --- /dev/null +++ b/src/nice.c @@ -0,0 +1,195 @@ +/* nice -- run a program with modified niceness + Copyright (C) 1990-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* David MacKenzie */ + +#include +#include +#include +#include + +#include "system.h" + +#if ! HAVE_NICE +/* Include this after "system.h" so we're sure to have definitions + (from time.h or sys/time.h) required for e.g. the ru_utime member. */ +# include +#endif + +#include "error.h" +#include "long-options.h" +#include "quote.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "nice" + +#define AUTHORS "David MacKenzie" + +#if HAVE_NICE +# define GET_NICENESS() nice (0) +#else +# define GET_NICENESS() getpriority (PRIO_PROCESS, 0) +#endif + +#ifndef NZERO +# define NZERO 20 +#endif + +/* This is required for Darwin Kernel Version 7.7.0. */ +#if NZERO == 0 +# undef NZERO +# define NZERO 20 +#endif + +/* The name this program was run with. */ +char *program_name; + +static struct option const longopts[] = +{ + {"adjustment", required_argument, NULL, 'n'}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION] [COMMAND [ARG]...]\n"), program_name); + printf (_("\ +Run COMMAND with an adjusted niceness, which affects process scheduling.\n\ +With no COMMAND, print the current niceness. Nicenesses range from\n\ +%d (most favorable scheduling) to %d (least favorable).\n\ +\n\ + -n, --adjustment=N add integer N to the niceness (default 10)\n\ +"), + - NZERO, NZERO - 1); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int current_niceness; + int adjustment = 10; + char const *adjustment_given = NULL; + bool ok; + int i; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (EXIT_FAIL); + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + + for (i = 1; i < argc; /* empty */) + { + char const *s = argv[i]; + + if (s[0] == '-' && ISDIGIT (s[1 + (s[1] == '-' || s[1] == '+')])) + { + adjustment_given = s + 1; + ++i; + } + else + { + int optc; + int fake_argc = argc - (i - 1); + char **fake_argv = argv + (i - 1); + + /* Ensure that any getopt diagnostics use the right name. */ + fake_argv[0] = program_name; + + /* Initialize getopt_long's internal state. */ + optind = 0; + + optc = getopt_long (fake_argc, fake_argv, "+n:", longopts, NULL); + i += optind - 1; + + if (optc == '?') + usage (EXIT_FAIL); + else if (optc == 'n') + adjustment_given = optarg; + else /* optc == -1 */ + break; + } + } + + if (adjustment_given) + { + /* If the requested adjustment is outside the valid range, + silently bring it to just within range; this mimics what + "setpriority" and "nice" do. */ + enum { MIN_ADJUSTMENT = 1 - 2 * NZERO, MAX_ADJUSTMENT = 2 * NZERO - 1 }; + long int tmp; + if (LONGINT_OVERFLOW < xstrtol (adjustment_given, NULL, 10, &tmp, "")) + error (EXIT_FAIL, 0, _("invalid adjustment %s"), + quote (adjustment_given)); + adjustment = MAX (MIN_ADJUSTMENT, MIN (tmp, MAX_ADJUSTMENT)); + } + + if (i == argc) + { + if (adjustment_given) + { + error (0, 0, _("a command must be given with an adjustment")); + usage (EXIT_FAIL); + } + /* No command given; print the niceness. */ + errno = 0; + current_niceness = GET_NICENESS (); + if (current_niceness == -1 && errno != 0) + error (EXIT_FAIL, errno, _("cannot get niceness")); + printf ("%d\n", current_niceness); + exit (EXIT_SUCCESS); + } + + errno = 0; +#if HAVE_NICE + ok = (nice (adjustment) != -1 || errno == 0); +#else + current_niceness = GET_NICENESS (); + if (current_niceness == -1 && errno != 0) + error (EXIT_FAIL, errno, _("cannot get niceness")); + ok = (setpriority (PRIO_PROCESS, 0, current_niceness + adjustment) == 0); +#endif + if (!ok) + error (errno == EPERM ? 0 : EXIT_FAIL, errno, _("cannot set niceness")); + + execvp (argv[i], &argv[i]); + + { + int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE); + error (0, errno, "%s", argv[i]); + exit (exit_status); + } +} diff --git a/src/nl.c b/src/nl.c new file mode 100644 index 0000000..04c8118 --- /dev/null +++ b/src/nl.c @@ -0,0 +1,617 @@ +/* nl -- number lines of files + Copyright (C) 89, 92, 1995-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Scott Bartram (nancy!scott@uunet.uu.net) + Revised by David MacKenzie (djm@gnu.ai.mit.edu) */ + +#include + +#include +#include +#include + +#include "system.h" + +#include + +#include "error.h" +#include "linebuffer.h" +#include "quote.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "nl" + +#define AUTHORS "Scott Bartram", "David MacKenzie" + +/* Line-number formats. They are given an int width, an intmax_t + value, and a string separator. */ + +/* Right justified, no leading zeroes. */ +static char const FORMAT_RIGHT_NOLZ[] = "%*" PRIdMAX "%s"; + +/* Right justified, leading zeroes. */ +static char const FORMAT_RIGHT_LZ[] = "%0*" PRIdMAX "%s"; + +/* Left justified, no leading zeroes. */ +static char const FORMAT_LEFT[] = "%-*" PRIdMAX "%s"; + +/* Default section delimiter characters. */ +static char const DEFAULT_SECTION_DELIMITERS[] = "\\:"; + +/* Types of input lines: either one of the section delimiters, + or text to output. */ +enum section +{ + Header, Body, Footer, Text +}; + +/* The name this program was run with. */ +char *program_name; + +/* Format of body lines (-b). */ +static char const *body_type = "t"; + +/* Format of header lines (-h). */ +static char const *header_type = "n"; + +/* Format of footer lines (-f). */ +static char const *footer_type = "n"; + +/* Format currently being used (body, header, or footer). */ +static char const *current_type; + +/* Regex for body lines to number (-bp). */ +static struct re_pattern_buffer body_regex; + +/* Regex for header lines to number (-hp). */ +static struct re_pattern_buffer header_regex; + +/* Regex for footer lines to number (-fp). */ +static struct re_pattern_buffer footer_regex; + +/* Fastmaps for the above. */ +static char body_fastmap[UCHAR_MAX + 1]; +static char header_fastmap[UCHAR_MAX + 1]; +static char footer_fastmap[UCHAR_MAX + 1]; + +/* Pointer to current regex, if any. */ +static struct re_pattern_buffer *current_regex = NULL; + +/* Separator string to print after line number (-s). */ +static char const *separator_str = "\t"; + +/* Input section delimiter string (-d). */ +static char const *section_del = DEFAULT_SECTION_DELIMITERS; + +/* Header delimiter string. */ +static char *header_del = NULL; + +/* Header section delimiter length. */ +static size_t header_del_len; + +/* Body delimiter string. */ +static char *body_del = NULL; + +/* Body section delimiter length. */ +static size_t body_del_len; + +/* Footer delimiter string. */ +static char *footer_del = NULL; + +/* Footer section delimiter length. */ +static size_t footer_del_len; + +/* Input buffer. */ +static struct linebuffer line_buf; + +/* printf format string for unnumbered lines. */ +static char *print_no_line_fmt = NULL; + +/* Starting line number on each page (-v). */ +static intmax_t starting_line_number = 1; + +/* Line number increment (-i). */ +static intmax_t page_incr = 1; + +/* If true, reset line number at start of each page (-p). */ +static bool reset_numbers = true; + +/* Number of blank lines to consider to be one line for numbering (-l). */ +static intmax_t blank_join = 1; + +/* Width of line numbers (-w). */ +static int lineno_width = 6; + +/* Line number format (-n). */ +static char const *lineno_format = FORMAT_RIGHT_NOLZ; + +/* Current print line number. */ +static intmax_t line_no; + +/* True if we have ever read standard input. */ +static bool have_read_stdin; + +static struct option const longopts[] = +{ + {"header-numbering", required_argument, NULL, 'h'}, + {"body-numbering", required_argument, NULL, 'b'}, + {"footer-numbering", required_argument, NULL, 'f'}, + {"starting-line-number", required_argument, NULL, 'v'}, + {"page-increment", required_argument, NULL, 'i'}, + {"no-renumber", no_argument, NULL, 'p'}, + {"join-blank-lines", required_argument, NULL, 'l'}, + {"number-separator", required_argument, NULL, 's'}, + {"number-width", required_argument, NULL, 'w'}, + {"number-format", required_argument, NULL, 'n'}, + {"section-delimiter", required_argument, NULL, 'd'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Print a usage message and quit. */ + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + fputs (_("\ +Write each FILE to standard output, with line numbers added.\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -b, --body-numbering=STYLE use STYLE for numbering body lines\n\ + -d, --section-delimiter=CC use CC for separating logical pages\n\ + -f, --footer-numbering=STYLE use STYLE for numbering footer lines\n\ +"), stdout); + fputs (_("\ + -h, --header-numbering=STYLE use STYLE for numbering header lines\n\ + -i, --page-increment=NUMBER line number increment at each line\n\ + -l, --join-blank-lines=NUMBER group of NUMBER empty lines counted as one\n\ + -n, --number-format=FORMAT insert line numbers according to FORMAT\n\ + -p, --no-renumber do not reset line numbers at logical pages\n\ + -s, --number-separator=STRING add STRING after (possible) line number\n\ +"), stdout); + fputs (_("\ + -v, --first-page=NUMBER first line number on each logical page\n\ + -w, --number-width=NUMBER use NUMBER columns for line numbers\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +By default, selects -v1 -i1 -l1 -sTAB -w6 -nrn -hn -bt -fn. CC are\n\ +two delimiter characters for separating logical pages, a missing\n\ +second character implies :. Type \\\\ for \\. STYLE is one of:\n\ +"), stdout); + fputs (_("\ +\n\ + a number all lines\n\ + t number only nonempty lines\n\ + n number no lines\n\ + pBRE number only lines that contain a match for the basic regular\n\ + expression, BRE\n\ +\n\ +FORMAT is one of:\n\ +\n\ + ln left justified, no leading zeros\n\ + rn right justified, no leading zeros\n\ + rz right justified, leading zeros\n\ +\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Set the command line flag TYPEP and possibly the regex pointer REGEXP, + according to `optarg'. */ + +static bool +build_type_arg (char const **typep, + struct re_pattern_buffer *regexp, char *fastmap) +{ + char const *errmsg; + bool rval = true; + + switch (*optarg) + { + case 'a': + case 't': + case 'n': + *typep = optarg; + break; + case 'p': + *typep = optarg++; + regexp->buffer = NULL; + regexp->allocated = 0; + regexp->fastmap = fastmap; + regexp->translate = NULL; + re_syntax_options = + RE_SYNTAX_POSIX_BASIC & ~RE_CONTEXT_INVALID_DUP & ~RE_NO_EMPTY_RANGES; + errmsg = re_compile_pattern (optarg, strlen (optarg), regexp); + if (errmsg) + error (EXIT_FAILURE, 0, "%s", errmsg); + break; + default: + rval = false; + break; + } + return rval; +} + +/* Print the line number and separator; increment the line number. */ + +static void +print_lineno (void) +{ + intmax_t next_line_no; + + printf (lineno_format, lineno_width, line_no, separator_str); + + next_line_no = line_no + page_incr; + if (next_line_no < line_no) + error (EXIT_FAILURE, 0, _("line number overflow")); + line_no = next_line_no; +} + +/* Switch to a header section. */ + +static void +proc_header (void) +{ + current_type = header_type; + current_regex = &header_regex; + if (reset_numbers) + line_no = starting_line_number; + putchar ('\n'); +} + +/* Switch to a body section. */ + +static void +proc_body (void) +{ + current_type = body_type; + current_regex = &body_regex; + putchar ('\n'); +} + +/* Switch to a footer section. */ + +static void +proc_footer (void) +{ + current_type = footer_type; + current_regex = &footer_regex; + putchar ('\n'); +} + +/* Process a regular text line in `line_buf'. */ + +static void +proc_text (void) +{ + static intmax_t blank_lines = 0; /* Consecutive blank lines so far. */ + + switch (*current_type) + { + case 'a': + if (blank_join > 1) + { + if (1 < line_buf.length || ++blank_lines == blank_join) + { + print_lineno (); + blank_lines = 0; + } + else + fputs (print_no_line_fmt, stdout); + } + else + print_lineno (); + break; + case 't': + if (1 < line_buf.length) + print_lineno (); + else + fputs (print_no_line_fmt, stdout); + break; + case 'n': + fputs (print_no_line_fmt, stdout); + break; + case 'p': + switch (re_search (current_regex, line_buf.buffer, line_buf.length - 1, + 0, line_buf.length - 1, NULL)) + { + case -2: + error (EXIT_FAILURE, errno, _("error in regular expression search")); + + case -1: + fputs (print_no_line_fmt, stdout); + break; + + default: + print_lineno (); + break; + } + } + fwrite (line_buf.buffer, sizeof (char), line_buf.length, stdout); +} + +/* Return the type of line in `line_buf'. */ + +static enum section +check_section (void) +{ + size_t len = line_buf.length - 1; + + if (len < 2 || memcmp (line_buf.buffer, section_del, 2)) + return Text; + if (len == header_del_len + && !memcmp (line_buf.buffer, header_del, header_del_len)) + return Header; + if (len == body_del_len + && !memcmp (line_buf.buffer, body_del, body_del_len)) + return Body; + if (len == footer_del_len + && !memcmp (line_buf.buffer, footer_del, footer_del_len)) + return Footer; + return Text; +} + +/* Read and process the file pointed to by FP. */ + +static void +process_file (FILE *fp) +{ + while (readlinebuffer (&line_buf, fp)) + { + switch (check_section ()) + { + case Header: + proc_header (); + break; + case Body: + proc_body (); + break; + case Footer: + proc_footer (); + break; + case Text: + proc_text (); + break; + } + } +} + +/* Process file FILE to standard output. + Return true if successful. */ + +static bool +nl_file (char const *file) +{ + FILE *stream; + + if (STREQ (file, "-")) + { + have_read_stdin = true; + stream = stdin; + } + else + { + stream = fopen (file, "r"); + if (stream == NULL) + { + error (0, errno, "%s", file); + return false; + } + } + + process_file (stream); + + if (ferror (stream)) + { + error (0, errno, "%s", file); + return false; + } + if (STREQ (file, "-")) + clearerr (stream); /* Also clear EOF. */ + else if (fclose (stream) == EOF) + { + error (0, errno, "%s", file); + return false; + } + return true; +} + +int +main (int argc, char **argv) +{ + int c; + size_t len; + bool ok = true; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + have_read_stdin = false; + + while ((c = getopt_long (argc, argv, "h:b:f:v:i:pl:s:w:n:d:", longopts, + NULL)) != -1) + { + switch (c) + { + case 'h': + if (! build_type_arg (&header_type, &header_regex, header_fastmap)) + { + error (0, 0, _("invalid header numbering style: %s"), + quote (optarg)); + ok = false; + } + break; + case 'b': + if (! build_type_arg (&body_type, &body_regex, body_fastmap)) + { + error (0, 0, _("invalid body numbering style: %s"), + quote (optarg)); + ok = false; + } + break; + case 'f': + if (! build_type_arg (&footer_type, &footer_regex, footer_fastmap)) + { + error (0, 0, _("invalid footer numbering style: %s"), + quote (optarg)); + ok = false; + } + break; + case 'v': + if (xstrtoimax (optarg, NULL, 10, &starting_line_number, "") + != LONGINT_OK) + { + error (0, 0, _("invalid starting line number: %s"), + quote (optarg)); + ok = false; + } + break; + case 'i': + if (! (xstrtoimax (optarg, NULL, 10, &page_incr, "") == LONGINT_OK + && 0 < page_incr)) + { + error (0, 0, _("invalid line number increment: %s"), + quote (optarg)); + ok = false; + } + break; + case 'p': + reset_numbers = false; + break; + case 'l': + if (! (xstrtoimax (optarg, NULL, 10, &blank_join, "") == LONGINT_OK + && 0 < blank_join)) + { + error (0, 0, _("invalid number of blank lines: %s"), + quote (optarg)); + ok = false; + } + break; + case 's': + separator_str = optarg; + break; + case 'w': + { + long int tmp_long; + if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK + || tmp_long <= 0 || tmp_long > INT_MAX) + { + error (0, 0, _("invalid line number field width: %s"), + quote (optarg)); + ok = false; + } + else + { + lineno_width = tmp_long; + } + } + break; + case 'n': + if (STREQ (optarg, "ln")) + lineno_format = FORMAT_LEFT; + else if (STREQ (optarg, "rn")) + lineno_format = FORMAT_RIGHT_NOLZ; + else if (STREQ (optarg, "rz")) + lineno_format = FORMAT_RIGHT_LZ; + else + { + error (0, 0, _("invalid line numbering format: %s"), + quote (optarg)); + ok = false; + } + break; + case 'd': + section_del = optarg; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + ok = false; + break; + } + } + + if (!ok) + usage (EXIT_FAILURE); + + /* Initialize the section delimiters. */ + len = strlen (section_del); + + header_del_len = len * 3; + header_del = xmalloc (header_del_len + 1); + strcat (strcat (strcpy (header_del, section_del), section_del), section_del); + + body_del_len = len * 2; + body_del = xmalloc (body_del_len + 1); + strcat (strcpy (body_del, section_del), section_del); + + footer_del_len = len; + footer_del = xmalloc (footer_del_len + 1); + strcpy (footer_del, section_del); + + /* Initialize the input buffer. */ + initbuffer (&line_buf); + + /* Initialize the printf format for unnumbered lines. */ + len = strlen (separator_str); + print_no_line_fmt = xmalloc (lineno_width + len + 1); + memset (print_no_line_fmt, ' ', lineno_width + len); + print_no_line_fmt[lineno_width + len] = '\0'; + + line_no = starting_line_number; + current_type = body_type; + current_regex = &body_regex; + + /* Main processing. */ + + if (optind == argc) + ok = nl_file ("-"); + else + for (; optind < argc; optind++) + ok &= nl_file (argv[optind]); + + if (have_read_stdin && fclose (stdin) == EOF) + error (EXIT_FAILURE, errno, "-"); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/nohup.c b/src/nohup.c new file mode 100644 index 0000000..1f8e62b --- /dev/null +++ b/src/nohup.c @@ -0,0 +1,216 @@ +/* nohup -- run a command immune to hangups, with output to a non-tty + Copyright (C) 2003, 2004, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Jim Meyering */ + +#include +#include +#include +#include +#include + +#include "system.h" + +#include "cloexec.h" +#include "error.h" +#include "filenamecat.h" +#include "fd-reopen.h" +#include "long-options.h" +#include "quote.h" +#include "unistd--.h" + +#define PROGRAM_NAME "nohup" + +#define AUTHORS "Jim Meyering" + +/* Exit statuses. */ +enum + { + /* `nohup' itself failed. */ + NOHUP_FAILURE = 127 + }; + +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s COMMAND [ARG]...\n\ + or: %s OPTION\n\ +"), + program_name, program_name); + + fputs (_("\ +Run COMMAND, ignoring hangup signals.\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int out_fd = STDOUT_FILENO; + int saved_stderr_fd = STDERR_FILENO; + bool ignoring_input; + bool redirecting_stdout; + bool stdout_is_closed; + bool redirecting_stderr; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (NOHUP_FAILURE); + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "+", NULL, NULL) != -1) + usage (NOHUP_FAILURE); + + if (argc <= optind) + { + error (0, 0, _("missing operand")); + usage (NOHUP_FAILURE); + } + + ignoring_input = isatty (STDIN_FILENO); + redirecting_stdout = isatty (STDOUT_FILENO); + stdout_is_closed = (!redirecting_stdout && errno == EBADF); + redirecting_stderr = isatty (STDERR_FILENO); + + /* If standard input is a tty, replace it with /dev/null if possible. + Note that it is deliberately opened for *writing*, + to ensure any read evokes an error. */ + if (ignoring_input) + { + fd_reopen (STDIN_FILENO, "/dev/null", O_WRONLY, 0); + if (!redirecting_stdout && !redirecting_stderr) + error (0, 0, _("ignoring input")); + } + + /* If standard output is a tty, redirect it (appending) to a file. + First try nohup.out, then $HOME/nohup.out. If standard error is + a tty and standard output is closed, open nohup.out or + $HOME/nohup.out without redirecting anything. */ + if (redirecting_stdout || (redirecting_stderr && stdout_is_closed)) + { + char *in_home = NULL; + char const *file = "nohup.out"; + int flags = O_CREAT | O_WRONLY | O_APPEND; + mode_t mode = S_IRUSR | S_IWUSR; + mode_t umask_value = umask (~mode); + out_fd = (redirecting_stdout + ? fd_reopen (STDOUT_FILENO, file, flags, mode) + : open (file, flags, mode)); + + if (out_fd < 0) + { + int saved_errno = errno; + char const *home = getenv ("HOME"); + if (home) + { + in_home = file_name_concat (home, file, NULL); + out_fd = (redirecting_stdout + ? fd_reopen (STDOUT_FILENO, in_home, flags, mode) + : open (in_home, flags, mode)); + } + if (out_fd < 0) + { + int saved_errno2 = errno; + error (0, saved_errno, _("failed to open %s"), quote (file)); + if (in_home) + error (0, saved_errno2, _("failed to open %s"), + quote (in_home)); + exit (NOHUP_FAILURE); + } + file = in_home; + } + + umask (umask_value); + error (0, 0, + _(ignoring_input + ? "ignoring input and appending output to %s" + : "appending output to %s"), + quote (file)); + free (in_home); + } + + /* If standard error is a tty, redirect it. */ + if (redirecting_stderr) + { + /* Save a copy of stderr before redirecting, so we can use the original + if execve fails. It's no big deal if this dup fails. It might + not change anything, and at worst, it'll lead to suppression of + the post-failed-execve diagnostic. */ + saved_stderr_fd = dup (STDERR_FILENO); + + if (0 <= saved_stderr_fd + && set_cloexec_flag (saved_stderr_fd, true) != 0) + error (NOHUP_FAILURE, errno, + _("failed to set the copy of stderr to close on exec")); + + if (!redirecting_stdout) + error (0, 0, + _(ignoring_input + ? "ignoring input and redirecting stderr to stdout" + : "redirecting stderr to stdout")); + + if (dup2 (out_fd, STDERR_FILENO) < 0) + error (NOHUP_FAILURE, errno, _("failed to redirect standard error")); + + if (stdout_is_closed) + close (out_fd); + } + + signal (SIGHUP, SIG_IGN); + + { + int exit_status; + int saved_errno; + char **cmd = argv + optind; + + execvp (*cmd, cmd); + exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE); + saved_errno = errno; + + /* The execve failed. Output a diagnostic to stderr only if: + - stderr was initially redirected to a non-tty, or + - stderr was initially directed to a tty, and we + can dup2 it to point back to that same tty. + In other words, output the diagnostic if possible, but only if + it will go to the original stderr. */ + if (dup2 (saved_stderr_fd, STDERR_FILENO) == STDERR_FILENO) + error (0, saved_errno, _("cannot run command %s"), quote (*cmd)); + + exit (exit_status); + } +} diff --git a/src/od.c b/src/od.c new file mode 100644 index 0000000..706a469 --- /dev/null +++ b/src/od.c @@ -0,0 +1,1937 @@ +/* od -- dump files in octal and other formats + Copyright (C) 92, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Jim Meyering. */ + +#include + +#include +#include +#include +#include +#include "system.h" +#include "error.h" +#include "quote.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "od" + +#define AUTHORS "Jim Meyering" + +#include + +#ifdef HAVE_LONG_DOUBLE +typedef long double LONG_DOUBLE; +#else +typedef double LONG_DOUBLE; +#endif + +/* The default number of input bytes per output line. */ +#define DEFAULT_BYTES_PER_BLOCK 16 + +/* The number of decimal digits of precision in a float. */ +#ifndef FLT_DIG +# define FLT_DIG 7 +#endif + +/* The number of decimal digits of precision in a double. */ +#ifndef DBL_DIG +# define DBL_DIG 15 +#endif + +/* The number of decimal digits of precision in a long double. */ +#ifndef LDBL_DIG +# define LDBL_DIG DBL_DIG +#endif + +#if HAVE_UNSIGNED_LONG_LONG_INT +typedef unsigned long long int unsigned_long_long_int; +#else +/* This is just a place-holder to avoid a few `#if' directives. + In this case, the type isn't actually used. */ +typedef unsigned long int unsigned_long_long_int; +#endif + +enum size_spec + { + NO_SIZE, + CHAR, + SHORT, + INT, + LONG, + LONG_LONG, + /* FIXME: add INTMAX support, too */ + FLOAT_SINGLE, + FLOAT_DOUBLE, + FLOAT_LONG_DOUBLE, + N_SIZE_SPECS + }; + +enum output_format + { + SIGNED_DECIMAL, + UNSIGNED_DECIMAL, + OCTAL, + HEXADECIMAL, + FLOATING_POINT, + NAMED_CHARACTER, + CHARACTER + }; + +/* The maximum number of bytes needed for a format string, + including the trailing null. */ +enum + { + FMT_BYTES_ALLOCATED = + MAX ((sizeof " %0" - 1 + INT_STRLEN_BOUND (int) + + MAX (sizeof "ld", + MAX (sizeof PRIdMAX, + MAX (sizeof PRIoMAX, + MAX (sizeof PRIuMAX, + sizeof PRIxMAX))))), + sizeof " %.Le" + 2 * INT_STRLEN_BOUND (int)) + }; + +/* Each output format specification (from `-t spec' or from + old-style options) is represented by one of these structures. */ +struct tspec + { + enum output_format fmt; + enum size_spec size; + void (*print_function) (size_t, void const *, char const *); + char fmt_string[FMT_BYTES_ALLOCATED]; + bool hexl_mode_trailer; + int field_width; + }; + +/* The name this program was run with. */ +char *program_name; + +/* Convert the number of 8-bit bytes of a binary representation to + the number of characters (digits + sign if the type is signed) + required to represent the same quantity in the specified base/type. + For example, a 32-bit (4-byte) quantity may require a field width + as wide as the following for these types: + 11 unsigned octal + 11 signed decimal + 10 unsigned decimal + 8 unsigned hexadecimal */ + +static unsigned int const bytes_to_oct_digits[] = +{0, 3, 6, 8, 11, 14, 16, 19, 22, 25, 27, 30, 32, 35, 38, 41, 43}; + +static unsigned int const bytes_to_signed_dec_digits[] = +{1, 4, 6, 8, 11, 13, 16, 18, 20, 23, 25, 28, 30, 33, 35, 37, 40}; + +static unsigned int const bytes_to_unsigned_dec_digits[] = +{0, 3, 5, 8, 10, 13, 15, 17, 20, 22, 25, 27, 29, 32, 34, 37, 39}; + +static unsigned int const bytes_to_hex_digits[] = +{0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32}; + +#define MAX_INTEGRAL_TYPE_SIZE sizeof (unsigned_long_long_int) + +/* It'll be a while before we see integral types wider than 16 bytes, + but if/when it happens, this check will catch it. Without this check, + a wider type would provoke a buffer overrun. */ +verify (MAX_INTEGRAL_TYPE_SIZE + < sizeof bytes_to_hex_digits / sizeof *bytes_to_hex_digits); + +/* Make sure the other arrays have the same length. */ +verify (sizeof bytes_to_oct_digits == sizeof bytes_to_signed_dec_digits); +verify (sizeof bytes_to_oct_digits == sizeof bytes_to_unsigned_dec_digits); +verify (sizeof bytes_to_oct_digits == sizeof bytes_to_hex_digits); + +/* Convert enum size_spec to the size of the named type. */ +static const int width_bytes[] = +{ + -1, + sizeof (char), + sizeof (short int), + sizeof (int), + sizeof (long int), + sizeof (unsigned_long_long_int), + sizeof (float), + sizeof (double), + sizeof (LONG_DOUBLE) +}; + +/* Ensure that for each member of `enum size_spec' there is an + initializer in the width_bytes array. */ +verify (sizeof width_bytes / sizeof width_bytes[0] == N_SIZE_SPECS); + +/* Names for some non-printing characters. */ +static const char *const charname[33] = +{ + "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel", + "bs", "ht", "nl", "vt", "ff", "cr", "so", "si", + "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb", + "can", "em", "sub", "esc", "fs", "gs", "rs", "us", + "sp" +}; + +/* Address base (8, 10 or 16). */ +static int address_base; + +/* The number of octal digits required to represent the largest + address value. */ +#define MAX_ADDRESS_LENGTH \ + ((sizeof (uintmax_t) * CHAR_BIT + CHAR_BIT - 1) / 3) + +/* Width of a normal address. */ +static int address_pad_len; + +static size_t string_min; +static bool flag_dump_strings; + +/* True if we should recognize the older non-option arguments + that specified at most one file and optional arguments specifying + offset and pseudo-start address. */ +static bool traditional; + +/* True if an old-style `pseudo-address' was specified. */ +static bool flag_pseudo_start; + +/* The difference between the old-style pseudo starting address and + the number of bytes to skip. */ +static uintmax_t pseudo_offset; + +/* Function that accepts an address and an optional following char, + and prints the address and char to stdout. */ +static void (*format_address) (uintmax_t, char); + +/* The number of input bytes to skip before formatting and writing. */ +static uintmax_t n_bytes_to_skip = 0; + +/* When false, MAX_BYTES_TO_FORMAT and END_OFFSET are ignored, and all + input is formatted. */ +static bool limit_bytes_to_format = false; + +/* The maximum number of bytes that will be formatted. */ +static uintmax_t max_bytes_to_format; + +/* The offset of the first byte after the last byte to be formatted. */ +static uintmax_t end_offset; + +/* When true and two or more consecutive blocks are equal, format + only the first block and output an asterisk alone on the following + line to indicate that identical blocks have been elided. */ +static bool abbreviate_duplicate_blocks = true; + +/* An array of specs describing how to format each input block. */ +static struct tspec *spec; + +/* The number of format specs. */ +static size_t n_specs; + +/* The allocated length of SPEC. */ +static size_t n_specs_allocated; + +/* The number of input bytes formatted per output line. It must be + a multiple of the least common multiple of the sizes associated with + the specified output types. It should be as large as possible, but + no larger than 16 -- unless specified with the -w option. */ +static size_t bytes_per_block; + +/* Human-readable representation of *file_list (for error messages). + It differs from file_list[-1] only when file_list[-1] is "-". */ +static char const *input_filename; + +/* A NULL-terminated list of the file-arguments from the command line. */ +static char const *const *file_list; + +/* Initializer for file_list if no file-arguments + were specified on the command line. */ +static char const *const default_file_list[] = {"-", NULL}; + +/* The input stream associated with the current file. */ +static FILE *in_stream; + +/* If true, at least one of the files we read was standard input. */ +static bool have_read_stdin; + +/* Map the size in bytes to a type identifier. */ +static enum size_spec integral_type_size[MAX_INTEGRAL_TYPE_SIZE + 1]; + +#define MAX_FP_TYPE_SIZE sizeof (LONG_DOUBLE) +static enum size_spec fp_type_size[MAX_FP_TYPE_SIZE + 1]; + +static char const short_options[] = "A:aBbcDdeFfHhIij:LlN:OoS:st:vw::Xx"; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + TRADITIONAL_OPTION = CHAR_MAX + 1 +}; + +static struct option const long_options[] = +{ + {"skip-bytes", required_argument, NULL, 'j'}, + {"address-radix", required_argument, NULL, 'A'}, + {"read-bytes", required_argument, NULL, 'N'}, + {"format", required_argument, NULL, 't'}, + {"output-duplicates", no_argument, NULL, 'v'}, + {"strings", optional_argument, NULL, 'S'}, + {"traditional", no_argument, NULL, TRADITIONAL_OPTION}, + {"width", optional_argument, NULL, 'w'}, + + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ + or: %s [-abcdfilosx]... [FILE] [[+]OFFSET[.][b]]\n\ + or: %s --traditional [OPTION]... [FILE] [[+]OFFSET[.][b] [+][LABEL][.][b]]\n\ +"), + program_name, program_name, program_name); + fputs (_("\n\ +Write an unambiguous representation, octal bytes by default,\n\ +of FILE to standard output. With more than one FILE argument,\n\ +concatenate them in the listed order to form the input.\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +"), stdout); + fputs (_("\ +All arguments to long options are mandatory for short options.\n\ +"), stdout); + fputs (_("\ + -A, --address-radix=RADIX decide how file offsets are printed\n\ + -j, --skip-bytes=BYTES skip BYTES input bytes first\n\ +"), stdout); + fputs (_("\ + -N, --read-bytes=BYTES limit dump to BYTES input bytes\n\ + -S, --strings[=BYTES] output strings of at least BYTES graphic chars\n\ + -t, --format=TYPE select output format or formats\n\ + -v, --output-duplicates do not use * to mark line suppression\n\ + -w, --width[=BYTES] output BYTES bytes per output line\n\ + --traditional accept arguments in traditional form\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Traditional format specifications may be intermixed; they accumulate:\n\ + -a same as -t a, select named characters, ignoring high-order bit\n\ + -b same as -t o1, select octal bytes\n\ + -c same as -t c, select ASCII characters or backslash escapes\n\ + -d same as -t u2, select unsigned decimal 2-byte units\n\ +"), stdout); + fputs (_("\ + -f same as -t fF, select floats\n\ + -i same as -t dI, select decimal ints\n\ + -l same as -t dL, select decimal longs\n\ + -o same as -t o2, select octal 2-byte units\n\ + -s same as -t d2, select decimal 2-byte units\n\ + -x same as -t x2, select hexadecimal 2-byte units\n\ +"), stdout); + fputs (_("\ +\n\ +If first and second call formats both apply, the second format is assumed\n\ +if the last operand begins with + or (if there are 2 operands) a digit.\n\ +An OFFSET operand means -j OFFSET. LABEL is the pseudo-address\n\ +at first byte printed, incremented when dump is progressing.\n\ +For OFFSET and LABEL, a 0x or 0X prefix indicates hexadecimal;\n\ +suffixes may be . for octal and b for multiply by 512.\n\ +"), stdout); + fputs (_("\ +\n\ +TYPE is made up of one or more of these specifications:\n\ +\n\ + a named character, ignoring high-order bit\n\ + c ASCII character or backslash escape\n\ +"), stdout); + fputs (_("\ + d[SIZE] signed decimal, SIZE bytes per integer\n\ + f[SIZE] floating point, SIZE bytes per integer\n\ + o[SIZE] octal, SIZE bytes per integer\n\ + u[SIZE] unsigned decimal, SIZE bytes per integer\n\ + x[SIZE] hexadecimal, SIZE bytes per integer\n\ +"), stdout); + fputs (_("\ +\n\ +SIZE is a number. For TYPE in doux, SIZE may also be C for\n\ +sizeof(char), S for sizeof(short), I for sizeof(int) or L for\n\ +sizeof(long). If TYPE is f, SIZE may also be F for sizeof(float), D\n\ +for sizeof(double) or L for sizeof(long double).\n\ +"), stdout); + fputs (_("\ +\n\ +RADIX is d for decimal, o for octal, x for hexadecimal or n for none.\n\ +BYTES is hexadecimal with 0x or 0X prefix, it is multiplied by 512\n\ +with b suffix, by 1024 with k and by 1048576 with m. Adding a z suffix to\n\ +any type adds a display of printable characters to the end of each line\n\ +of output. \ +"), stdout); + fputs (_("\ +--string without a number implies 3. --width without a number\n\ +implies 32. By default, od uses -A o -t d2 -w16.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Define the print functions. */ + +static void +print_s_char (size_t n_bytes, void const *block, char const *fmt_string) +{ + signed char const *p = block; + size_t i; + for (i = n_bytes / sizeof *p; i != 0; i--) + printf (fmt_string, *p++); +} + +static void +print_char (size_t n_bytes, void const *block, char const *fmt_string) +{ + unsigned char const *p = block; + size_t i; + for (i = n_bytes / sizeof *p; i != 0; i--) + printf (fmt_string, *p++); +} + +static void +print_s_short (size_t n_bytes, void const *block, char const *fmt_string) +{ + short int const *p = block; + size_t i; + for (i = n_bytes / sizeof *p; i != 0; i--) + printf (fmt_string, *p++); +} + +static void +print_short (size_t n_bytes, void const *block, char const *fmt_string) +{ + unsigned short int const *p = block; + size_t i; + for (i = n_bytes / sizeof *p; i != 0; i--) + printf (fmt_string, *p++); +} + +static void +print_int (size_t n_bytes, void const *block, char const *fmt_string) +{ + unsigned int const *p = block; + size_t i; + for (i = n_bytes / sizeof *p; i != 0; i--) + printf (fmt_string, *p++); +} + +static void +print_long (size_t n_bytes, void const *block, char const *fmt_string) +{ + unsigned long int const *p = block; + size_t i; + for (i = n_bytes / sizeof *p; i != 0; i--) + printf (fmt_string, *p++); +} + +static void +print_long_long (size_t n_bytes, void const *block, char const *fmt_string) +{ + unsigned_long_long_int const *p = block; + size_t i; + for (i = n_bytes / sizeof *p; i != 0; i--) + printf (fmt_string, *p++); +} + +static void +print_float (size_t n_bytes, void const *block, char const *fmt_string) +{ + float const *p = block; + size_t i; + for (i = n_bytes / sizeof *p; i != 0; i--) + printf (fmt_string, *p++); +} + +static void +print_double (size_t n_bytes, void const *block, char const *fmt_string) +{ + double const *p = block; + size_t i; + for (i = n_bytes / sizeof *p; i != 0; i--) + printf (fmt_string, *p++); +} + +#ifdef HAVE_LONG_DOUBLE +static void +print_long_double (size_t n_bytes, void const *block, char const *fmt_string) +{ + long double const *p = block; + size_t i; + for (i = n_bytes / sizeof *p; i != 0; i--) + printf (fmt_string, *p++); +} +#endif + +static void +dump_hexl_mode_trailer (size_t n_bytes, const char *block) +{ + size_t i; + fputs (" >", stdout); + for (i = n_bytes; i > 0; i--) + { + unsigned char c = *block++; + unsigned char c2 = (isprint (c) ? c : '.'); + putchar (c2); + } + putchar ('<'); +} + +static void +print_named_ascii (size_t n_bytes, void const *block, + const char *unused_fmt_string ATTRIBUTE_UNUSED) +{ + unsigned char const *p = block; + size_t i; + for (i = n_bytes; i > 0; i--) + { + int masked_c = *p++ & 0x7f; + const char *s; + char buf[5]; + + if (masked_c == 127) + s = "del"; + else if (masked_c <= 040) + s = charname[masked_c]; + else + { + sprintf (buf, " %c", masked_c); + s = buf; + } + + printf (" %3s", s); + } +} + +static void +print_ascii (size_t n_bytes, void const *block, + const char *unused_fmt_string ATTRIBUTE_UNUSED) +{ + unsigned char const *p = block; + size_t i; + for (i = n_bytes; i > 0; i--) + { + unsigned char c = *p++; + const char *s; + char buf[5]; + + switch (c) + { + case '\0': + s = " \\0"; + break; + + case '\a': + s = " \\a"; + break; + + case '\b': + s = " \\b"; + break; + + case '\f': + s = " \\f"; + break; + + case '\n': + s = " \\n"; + break; + + case '\r': + s = " \\r"; + break; + + case '\t': + s = " \\t"; + break; + + case '\v': + s = " \\v"; + break; + + default: + sprintf (buf, (isprint (c) ? " %c" : "%03o"), c); + s = buf; + } + + printf (" %3s", s); + } +} + +/* Convert a null-terminated (possibly zero-length) string S to an + unsigned long integer value. If S points to a non-digit set *P to S, + *VAL to 0, and return true. Otherwise, accumulate the integer value of + the string of digits. If the string of digits represents a value + larger than ULONG_MAX, don't modify *VAL or *P and return false. + Otherwise, advance *P to the first non-digit after S, set *VAL to + the result of the conversion and return true. */ + +static bool +simple_strtoul (const char *s, const char **p, unsigned long int *val) +{ + unsigned long int sum; + + sum = 0; + while (ISDIGIT (*s)) + { + int c = *s++ - '0'; + if (sum > (ULONG_MAX - c) / 10) + return false; + sum = sum * 10 + c; + } + *p = s; + *val = sum; + return true; +} + +/* If S points to a single valid modern od format string, put + a description of that format in *TSPEC, make *NEXT point at the + character following the just-decoded format (if *NEXT is non-NULL), + and return true. If S is not valid, don't modify *NEXT or *TSPEC, + give a diagnostic, and return false. For example, if S were + "d4afL" *NEXT would be set to "afL" and *TSPEC would be + { + fmt = SIGNED_DECIMAL; + size = INT or LONG; (whichever integral_type_size[4] resolves to) + print_function = print_int; (assuming size == INT) + fmt_string = "%011d%c"; + } + S_ORIG is solely for reporting errors. It should be the full format + string argument. + */ + +static bool +decode_one_format (const char *s_orig, const char *s, const char **next, + struct tspec *tspec) +{ + enum size_spec size_spec; + unsigned long int size; + enum output_format fmt; + const char *pre_fmt_string; + void (*print_function) (size_t, void const *, char const *); + const char *p; + char c; + int field_width; + int precision; + + assert (tspec != NULL); + + switch (*s) + { + case 'd': + case 'o': + case 'u': + case 'x': + c = *s; + ++s; + switch (*s) + { + case 'C': + ++s; + size = sizeof (char); + break; + + case 'S': + ++s; + size = sizeof (short int); + break; + + case 'I': + ++s; + size = sizeof (int); + break; + + case 'L': + ++s; + size = sizeof (long int); + break; + + default: + if (! simple_strtoul (s, &p, &size)) + { + /* The integer at P in S would overflow an unsigned long int. + A digit string that long is sufficiently odd looking + that the following diagnostic is sufficient. */ + error (0, 0, _("invalid type string %s"), quote (s_orig)); + return false; + } + if (p == s) + size = sizeof (int); + else + { + if (MAX_INTEGRAL_TYPE_SIZE < size + || integral_type_size[size] == NO_SIZE) + { + error (0, 0, _("invalid type string %s;\n\ +this system doesn't provide a %lu-byte integral type"), quote (s_orig), size); + return false; + } + s = p; + } + break; + } + +#define ISPEC_TO_FORMAT(Spec, Min_format, Long_format, Max_format) \ + ((Spec) == LONG_LONG ? (Max_format) \ + : ((Spec) == LONG ? (Long_format) \ + : (Min_format))) \ + + size_spec = integral_type_size[size]; + + switch (c) + { + case 'd': + fmt = SIGNED_DECIMAL; + sprintf (tspec->fmt_string, " %%%d%s", + (field_width = bytes_to_signed_dec_digits[size]), + ISPEC_TO_FORMAT (size_spec, "d", "ld", PRIdMAX)); + break; + + case 'o': + fmt = OCTAL; + sprintf (tspec->fmt_string, " %%0%d%s", + (field_width = bytes_to_oct_digits[size]), + ISPEC_TO_FORMAT (size_spec, "o", "lo", PRIoMAX)); + break; + + case 'u': + fmt = UNSIGNED_DECIMAL; + sprintf (tspec->fmt_string, " %%%d%s", + (field_width = bytes_to_unsigned_dec_digits[size]), + ISPEC_TO_FORMAT (size_spec, "u", "lu", PRIuMAX)); + break; + + case 'x': + fmt = HEXADECIMAL; + sprintf (tspec->fmt_string, " %%0%d%s", + (field_width = bytes_to_hex_digits[size]), + ISPEC_TO_FORMAT (size_spec, "x", "lx", PRIxMAX)); + break; + + default: + abort (); + } + + assert (strlen (tspec->fmt_string) < FMT_BYTES_ALLOCATED); + + switch (size_spec) + { + case CHAR: + print_function = (fmt == SIGNED_DECIMAL + ? print_s_char + : print_char); + break; + + case SHORT: + print_function = (fmt == SIGNED_DECIMAL + ? print_s_short + : print_short); + break; + + case INT: + print_function = print_int; + break; + + case LONG: + print_function = print_long; + break; + + case LONG_LONG: + print_function = print_long_long; + break; + + default: + abort (); + } + break; + + case 'f': + fmt = FLOATING_POINT; + ++s; + switch (*s) + { + case 'F': + ++s; + size = sizeof (float); + break; + + case 'D': + ++s; + size = sizeof (double); + break; + + case 'L': + ++s; + size = sizeof (LONG_DOUBLE); + break; + + default: + if (! simple_strtoul (s, &p, &size)) + { + /* The integer at P in S would overflow an unsigned long int. + A digit string that long is sufficiently odd looking + that the following diagnostic is sufficient. */ + error (0, 0, _("invalid type string %s"), quote (s_orig)); + return false; + } + if (p == s) + size = sizeof (double); + else + { + if (size > MAX_FP_TYPE_SIZE + || fp_type_size[size] == NO_SIZE) + { + error (0, 0, _("invalid type string %s;\n\ +this system doesn't provide a %lu-byte floating point type"), + quote (s_orig), size); + return false; + } + s = p; + } + break; + } + size_spec = fp_type_size[size]; + + switch (size_spec) + { + case FLOAT_SINGLE: + print_function = print_float; + /* Don't use %#e; not all systems support it. */ + pre_fmt_string = " %%%d.%de"; + precision = FLT_DIG; + break; + + case FLOAT_DOUBLE: + print_function = print_double; + pre_fmt_string = " %%%d.%de"; + precision = DBL_DIG; + break; + +#ifdef HAVE_LONG_DOUBLE + case FLOAT_LONG_DOUBLE: + print_function = print_long_double; + pre_fmt_string = " %%%d.%dLe"; + precision = LDBL_DIG; + break; +#endif + + default: + abort (); + } + + field_width = precision + 8; + sprintf (tspec->fmt_string, pre_fmt_string, field_width, precision); + break; + + case 'a': + ++s; + fmt = NAMED_CHARACTER; + size_spec = CHAR; + print_function = print_named_ascii; + field_width = 3; + break; + + case 'c': + ++s; + fmt = CHARACTER; + size_spec = CHAR; + print_function = print_ascii; + field_width = 3; + break; + + default: + error (0, 0, _("invalid character `%c' in type string %s"), + *s, quote (s_orig)); + return false; + } + + tspec->size = size_spec; + tspec->fmt = fmt; + tspec->print_function = print_function; + + tspec->field_width = field_width; + tspec->hexl_mode_trailer = (*s == 'z'); + if (tspec->hexl_mode_trailer) + s++; + + if (next != NULL) + *next = s; + + return true; +} + +/* Given a list of one or more input filenames FILE_LIST, set the global + file pointer IN_STREAM and the global string INPUT_FILENAME to the + first one that can be successfully opened. Modify FILE_LIST to + reference the next filename in the list. A file name of "-" is + interpreted as standard input. If any file open fails, give an error + message and return false. */ + +static bool +open_next_file (void) +{ + bool ok = true; + + do + { + input_filename = *file_list; + if (input_filename == NULL) + return ok; + ++file_list; + + if (STREQ (input_filename, "-")) + { + input_filename = _("standard input"); + in_stream = stdin; + have_read_stdin = true; + if (O_BINARY && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + } + else + { + in_stream = fopen (input_filename, (O_BINARY ? "rb" : "r")); + if (in_stream == NULL) + { + error (0, errno, "%s", input_filename); + ok = false; + } + } + } + while (in_stream == NULL); + + if (limit_bytes_to_format & !flag_dump_strings) + setvbuf (in_stream, NULL, _IONBF, 0); + + return ok; +} + +/* Test whether there have been errors on in_stream, and close it if + it is not standard input. Return false if there has been an error + on in_stream or stdout; return true otherwise. This function will + report more than one error only if both a read and a write error + have occurred. IN_ERRNO, if nonzero, is the error number + corresponding to the most recent action for IN_STREAM. */ + +static bool +check_and_close (int in_errno) +{ + bool ok = true; + + if (in_stream != NULL) + { + if (ferror (in_stream)) + { + error (0, in_errno, _("%s: read error"), input_filename); + if (! STREQ (file_list[-1], "-")) + fclose (in_stream); + ok = false; + } + else if (! STREQ (file_list[-1], "-") && fclose (in_stream) != 0) + { + error (0, errno, "%s", input_filename); + ok = false; + } + + in_stream = NULL; + } + + if (ferror (stdout)) + { + error (0, 0, _("write error")); + ok = false; + } + + return ok; +} + +/* Decode the modern od format string S. Append the decoded + representation to the global array SPEC, reallocating SPEC if + necessary. Return true if S is valid. */ + +static bool +decode_format_string (const char *s) +{ + const char *s_orig = s; + assert (s != NULL); + + while (*s != '\0') + { + const char *next; + + if (n_specs_allocated <= n_specs) + spec = X2NREALLOC (spec, &n_specs_allocated); + + if (! decode_one_format (s_orig, s, &next, &spec[n_specs])) + return false; + + assert (s != next); + s = next; + ++n_specs; + } + + return true; +} + +/* Given a list of one or more input filenames FILE_LIST, set the global + file pointer IN_STREAM to position N_SKIP in the concatenation of + those files. If any file operation fails or if there are fewer than + N_SKIP bytes in the combined input, give an error message and return + false. When possible, use seek rather than read operations to + advance IN_STREAM. */ + +static bool +skip (uintmax_t n_skip) +{ + bool ok = true; + int in_errno = 0; + + if (n_skip == 0) + return true; + + while (in_stream != NULL) /* EOF. */ + { + struct stat file_stats; + + /* First try seeking. For large offsets, this extra work is + worthwhile. If the offset is below some threshold it may be + more efficient to move the pointer by reading. There are two + issues when trying to seek: + - the file must be seekable. + - before seeking to the specified position, make sure + that the new position is in the current file. + Try to do that by getting file's size using fstat. + But that will work only for regular files. */ + + if (fstat (fileno (in_stream), &file_stats) == 0) + { + /* The st_size field is valid only for regular files + (and for symbolic links, which cannot occur here). + If the number of bytes left to skip is at least + as large as the size of the current file, we can + decrement n_skip and go on to the next file. */ + + if (S_ISREG (file_stats.st_mode) && 0 <= file_stats.st_size) + { + if ((uintmax_t) file_stats.st_size <= n_skip) + n_skip -= file_stats.st_size; + else + { + if (fseeko (in_stream, n_skip, SEEK_CUR) != 0) + { + in_errno = errno; + ok = false; + } + n_skip = 0; + } + } + + /* If it's not a regular file with nonnegative size, + position the file pointer by reading. */ + + else + { + char buf[BUFSIZ]; + size_t n_bytes_read, n_bytes_to_read = BUFSIZ; + + while (0 < n_skip) + { + if (n_skip < n_bytes_to_read) + n_bytes_to_read = n_skip; + n_bytes_read = fread (buf, 1, n_bytes_to_read, in_stream); + n_skip -= n_bytes_read; + if (n_bytes_read != n_bytes_to_read) + { + in_errno = errno; + ok = false; + n_skip = 0; + break; + } + } + } + + if (n_skip == 0) + break; + } + + else /* cannot fstat() file */ + { + error (0, errno, "%s", input_filename); + ok = false; + } + + ok &= check_and_close (in_errno); + + ok &= open_next_file (); + } + + if (n_skip != 0) + error (EXIT_FAILURE, 0, _("cannot skip past end of combined input")); + + return ok; +} + +static void +format_address_none (uintmax_t address ATTRIBUTE_UNUSED, char c ATTRIBUTE_UNUSED) +{ +} + +static void +format_address_std (uintmax_t address, char c) +{ + char buf[MAX_ADDRESS_LENGTH + 2]; + char *p = buf + sizeof buf; + char const *pbound; + + *--p = '\0'; + *--p = c; + pbound = p - address_pad_len; + + /* Use a special case of the code for each base. This is measurably + faster than generic code. */ + switch (address_base) + { + case 8: + do + *--p = '0' + (address & 7); + while ((address >>= 3) != 0); + break; + + case 10: + do + *--p = '0' + (address % 10); + while ((address /= 10) != 0); + break; + + case 16: + do + *--p = "0123456789abcdef"[address & 15]; + while ((address >>= 4) != 0); + break; + } + + while (pbound < p) + *--p = '0'; + + fputs (p, stdout); +} + +static void +format_address_paren (uintmax_t address, char c) +{ + putchar ('('); + format_address_std (address, ')'); + if (c) + putchar (c); +} + +static void +format_address_label (uintmax_t address, char c) +{ + format_address_std (address, ' '); + format_address_paren (address + pseudo_offset, c); +} + +/* Write N_BYTES bytes from CURR_BLOCK to standard output once for each + of the N_SPEC format specs. CURRENT_OFFSET is the byte address of + CURR_BLOCK in the concatenation of input files, and it is printed + (optionally) only before the output line associated with the first + format spec. When duplicate blocks are being abbreviated, the output + for a sequence of identical input blocks is the output for the first + block followed by an asterisk alone on a line. It is valid to compare + the blocks PREV_BLOCK and CURR_BLOCK only when N_BYTES == BYTES_PER_BLOCK. + That condition may be false only for the last input block -- and then + only when it has not been padded to length BYTES_PER_BLOCK. */ + +static void +write_block (uintmax_t current_offset, size_t n_bytes, + const char *prev_block, const char *curr_block) +{ + static bool first = true; + static bool prev_pair_equal = false; + +#define EQUAL_BLOCKS(b1, b2) (memcmp (b1, b2, bytes_per_block) == 0) + + if (abbreviate_duplicate_blocks + && !first && n_bytes == bytes_per_block + && EQUAL_BLOCKS (prev_block, curr_block)) + { + if (prev_pair_equal) + { + /* The two preceding blocks were equal, and the current + block is the same as the last one, so print nothing. */ + } + else + { + printf ("*\n"); + prev_pair_equal = true; + } + } + else + { + size_t i; + + prev_pair_equal = false; + for (i = 0; i < n_specs; i++) + { + if (i == 0) + format_address (current_offset, '\0'); + else + printf ("%*s", address_pad_len, ""); + (*spec[i].print_function) (n_bytes, curr_block, spec[i].fmt_string); + if (spec[i].hexl_mode_trailer) + { + /* space-pad out to full line width, then dump the trailer */ + int datum_width = width_bytes[spec[i].size]; + int blank_fields = (bytes_per_block - n_bytes) / datum_width; + int field_width = spec[i].field_width + 1; + printf ("%*s", blank_fields * field_width, ""); + dump_hexl_mode_trailer (n_bytes, curr_block); + } + putchar ('\n'); + } + } + first = false; +} + +/* Read a single byte into *C from the concatenation of the input files + named in the global array FILE_LIST. On the first call to this + function, the global variable IN_STREAM is expected to be an open + stream associated with the input file INPUT_FILENAME. If IN_STREAM + is at end-of-file, close it and update the global variables IN_STREAM + and INPUT_FILENAME so they correspond to the next file in the list. + Then try to read a byte from the newly opened file. Repeat if + necessary until EOF is reached for the last file in FILE_LIST, then + set *C to EOF and return. Subsequent calls do likewise. Return + true if successful. */ + +static bool +read_char (int *c) +{ + bool ok = true; + + *c = EOF; + + while (in_stream != NULL) /* EOF. */ + { + *c = fgetc (in_stream); + + if (*c != EOF) + break; + + ok &= check_and_close (errno); + + ok &= open_next_file (); + } + + return ok; +} + +/* Read N bytes into BLOCK from the concatenation of the input files + named in the global array FILE_LIST. On the first call to this + function, the global variable IN_STREAM is expected to be an open + stream associated with the input file INPUT_FILENAME. If all N + bytes cannot be read from IN_STREAM, close IN_STREAM and update + the global variables IN_STREAM and INPUT_FILENAME. Then try to + read the remaining bytes from the newly opened file. Repeat if + necessary until EOF is reached for the last file in FILE_LIST. + On subsequent calls, don't modify BLOCK and return true. Set + *N_BYTES_IN_BUFFER to the number of bytes read. If an error occurs, + it will be detected through ferror when the stream is about to be + closed. If there is an error, give a message but continue reading + as usual and return false. Otherwise return true. */ + +static bool +read_block (size_t n, char *block, size_t *n_bytes_in_buffer) +{ + bool ok = true; + + assert (0 < n && n <= bytes_per_block); + + *n_bytes_in_buffer = 0; + + if (n == 0) + return true; + + while (in_stream != NULL) /* EOF. */ + { + size_t n_needed; + size_t n_read; + + n_needed = n - *n_bytes_in_buffer; + n_read = fread (block + *n_bytes_in_buffer, 1, n_needed, in_stream); + + *n_bytes_in_buffer += n_read; + + if (n_read == n_needed) + break; + + ok &= check_and_close (errno); + + ok &= open_next_file (); + } + + return ok; +} + +/* Return the least common multiple of the sizes associated + with the format specs. */ + +static int +get_lcm (void) +{ + size_t i; + int l_c_m = 1; + + for (i = 0; i < n_specs; i++) + l_c_m = lcm (l_c_m, width_bytes[spec[i].size]); + return l_c_m; +} + +/* If S is a valid traditional offset specification with an optional + leading '+' return true and set *OFFSET to the offset it denotes. */ + +static bool +parse_old_offset (const char *s, uintmax_t *offset) +{ + int radix; + + if (*s == '\0') + return false; + + /* Skip over any leading '+'. */ + if (s[0] == '+') + ++s; + + /* Determine the radix we'll use to interpret S. If there is a `.', + it's decimal, otherwise, if the string begins with `0X'or `0x', + it's hexadecimal, else octal. */ + if (strchr (s, '.') != NULL) + radix = 10; + else + { + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) + radix = 16; + else + radix = 8; + } + + return xstrtoumax (s, NULL, radix, offset, "Bb") == LONGINT_OK; +} + +/* Read a chunk of size BYTES_PER_BLOCK from the input files, write the + formatted block to standard output, and repeat until the specified + maximum number of bytes has been read or until all input has been + processed. If the last block read is smaller than BYTES_PER_BLOCK + and its size is not a multiple of the size associated with a format + spec, extend the input block with zero bytes until its length is a + multiple of all format spec sizes. Write the final block. Finally, + write on a line by itself the offset of the byte after the last byte + read. Accumulate return values from calls to read_block and + check_and_close, and if any was false, return false. + Otherwise, return true. */ + +static bool +dump (void) +{ + char *block[2]; + uintmax_t current_offset; + bool idx = false; + bool ok = true; + size_t n_bytes_read; + + block[0] = xnmalloc (2, bytes_per_block); + block[1] = block[0] + bytes_per_block; + + current_offset = n_bytes_to_skip; + + if (limit_bytes_to_format) + { + while (1) + { + size_t n_needed; + if (current_offset >= end_offset) + { + n_bytes_read = 0; + break; + } + n_needed = MIN (end_offset - current_offset, + (uintmax_t) bytes_per_block); + ok &= read_block (n_needed, block[idx], &n_bytes_read); + if (n_bytes_read < bytes_per_block) + break; + assert (n_bytes_read == bytes_per_block); + write_block (current_offset, n_bytes_read, + block[!idx], block[idx]); + current_offset += n_bytes_read; + idx = !idx; + } + } + else + { + while (1) + { + ok &= read_block (bytes_per_block, block[idx], &n_bytes_read); + if (n_bytes_read < bytes_per_block) + break; + assert (n_bytes_read == bytes_per_block); + write_block (current_offset, n_bytes_read, + block[!idx], block[idx]); + current_offset += n_bytes_read; + idx = !idx; + } + } + + if (n_bytes_read > 0) + { + int l_c_m; + size_t bytes_to_write; + + l_c_m = get_lcm (); + + /* Make bytes_to_write the smallest multiple of l_c_m that + is at least as large as n_bytes_read. */ + bytes_to_write = l_c_m * ((n_bytes_read + l_c_m - 1) / l_c_m); + + memset (block[idx] + n_bytes_read, 0, bytes_to_write - n_bytes_read); + write_block (current_offset, bytes_to_write, + block[!idx], block[idx]); + current_offset += n_bytes_read; + } + + format_address (current_offset, '\n'); + + if (limit_bytes_to_format && current_offset >= end_offset) + ok &= check_and_close (0); + + free (block[0]); + + return ok; +} + +/* STRINGS mode. Find each "string constant" in the input. + A string constant is a run of at least `string_min' ASCII + graphic (or formatting) characters terminated by a null. + Based on a function written by Richard Stallman for a + traditional version of od. Return true if successful. */ + +static bool +dump_strings (void) +{ + size_t bufsize = MAX (100, string_min); + char *buf = xmalloc (bufsize); + uintmax_t address = n_bytes_to_skip; + bool ok = true; + + while (1) + { + size_t i; + int c; + + /* See if the next `string_min' chars are all printing chars. */ + tryline: + + if (limit_bytes_to_format + && (end_offset < string_min || end_offset - string_min <= address)) + break; + + for (i = 0; i < string_min; i++) + { + ok &= read_char (&c); + address++; + if (c < 0) + { + free (buf); + return ok; + } + if (! isprint (c)) + /* Found a non-printing. Try again starting with next char. */ + goto tryline; + buf[i] = c; + } + + /* We found a run of `string_min' printable characters. + Now see if it is terminated with a null byte. */ + while (!limit_bytes_to_format || address < end_offset) + { + if (i == bufsize) + { + buf = X2REALLOC (buf, &bufsize); + } + ok &= read_char (&c); + address++; + if (c < 0) + { + free (buf); + return ok; + } + if (c == '\0') + break; /* It is; print this string. */ + if (! isprint (c)) + goto tryline; /* It isn't; give up on this string. */ + buf[i++] = c; /* String continues; store it all. */ + } + + /* If we get here, the string is all printable and null-terminated, + so print it. It is all in `buf' and `i' is its length. */ + buf[i] = 0; + format_address (address - i - 1, ' '); + + for (i = 0; (c = buf[i]); i++) + { + switch (c) + { + case '\a': + fputs ("\\a", stdout); + break; + + case '\b': + fputs ("\\b", stdout); + break; + + case '\f': + fputs ("\\f", stdout); + break; + + case '\n': + fputs ("\\n", stdout); + break; + + case '\r': + fputs ("\\r", stdout); + break; + + case '\t': + fputs ("\\t", stdout); + break; + + case '\v': + fputs ("\\v", stdout); + break; + + default: + putc (c, stdout); + } + } + putchar ('\n'); + } + + /* We reach this point only if we search through + (max_bytes_to_format - string_min) bytes before reaching EOF. */ + + free (buf); + + ok &= check_and_close (0); + return ok; +} + +int +main (int argc, char **argv) +{ + int c; + int n_files; + size_t i; + int l_c_m; + size_t desired_width IF_LINT (= 0); + bool modern = false; + bool width_specified = false; + bool ok = true; + + /* The old-style `pseudo starting address' to be printed in parentheses + after any true address. */ + uintmax_t pseudo_start IF_LINT (= 0); + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + for (i = 0; i <= MAX_INTEGRAL_TYPE_SIZE; i++) + integral_type_size[i] = NO_SIZE; + + integral_type_size[sizeof (char)] = CHAR; + integral_type_size[sizeof (short int)] = SHORT; + integral_type_size[sizeof (int)] = INT; + integral_type_size[sizeof (long int)] = LONG; +#if HAVE_UNSIGNED_LONG_LONG_INT + /* If `long int' and `long long int' have the same size, it's fine + to overwrite the entry for `long' with this one. */ + integral_type_size[sizeof (unsigned_long_long_int)] = LONG_LONG; +#endif + + for (i = 0; i <= MAX_FP_TYPE_SIZE; i++) + fp_type_size[i] = NO_SIZE; + + fp_type_size[sizeof (float)] = FLOAT_SINGLE; + /* The array entry for `double' is filled in after that for LONG_DOUBLE + so that if `long double' is the same type or if long double isn't + supported FLOAT_LONG_DOUBLE will never be used. */ + fp_type_size[sizeof (LONG_DOUBLE)] = FLOAT_LONG_DOUBLE; + fp_type_size[sizeof (double)] = FLOAT_DOUBLE; + + n_specs = 0; + n_specs_allocated = 0; + spec = NULL; + + format_address = format_address_std; + address_base = 8; + address_pad_len = 7; + flag_dump_strings = false; + + while ((c = getopt_long (argc, argv, short_options, long_options, NULL)) + != -1) + { + uintmax_t tmp; + enum strtol_error s_err; + + switch (c) + { + case 'A': + modern = true; + switch (optarg[0]) + { + case 'd': + format_address = format_address_std; + address_base = 10; + address_pad_len = 7; + break; + case 'o': + format_address = format_address_std; + address_base = 8; + address_pad_len = 7; + break; + case 'x': + format_address = format_address_std; + address_base = 16; + address_pad_len = 6; + break; + case 'n': + format_address = format_address_none; + address_pad_len = 0; + break; + default: + error (EXIT_FAILURE, 0, + _("invalid output address radix `%c'; \ +it must be one character from [doxn]"), + optarg[0]); + break; + } + break; + + case 'j': + modern = true; + s_err = xstrtoumax (optarg, NULL, 0, &n_bytes_to_skip, "bkm"); + if (s_err != LONGINT_OK) + STRTOL_FATAL_ERROR (optarg, _("skip argument"), s_err); + break; + + case 'N': + modern = true; + limit_bytes_to_format = true; + + s_err = xstrtoumax (optarg, NULL, 0, &max_bytes_to_format, "bkm"); + if (s_err != LONGINT_OK) + STRTOL_FATAL_ERROR (optarg, _("limit argument"), s_err); + break; + + case 'S': + modern = true; + if (optarg == NULL) + string_min = 3; + else + { + s_err = xstrtoumax (optarg, NULL, 0, &tmp, "bkm"); + if (s_err != LONGINT_OK) + STRTOL_FATAL_ERROR (optarg, _("minimum string length"), s_err); + + /* The minimum string length may be no larger than SIZE_MAX, + since we may allocate a buffer of this size. */ + if (SIZE_MAX < tmp) + error (EXIT_FAILURE, 0, _("%s is too large"), optarg); + + string_min = tmp; + } + flag_dump_strings = true; + break; + + case 't': + modern = true; + ok &= decode_format_string (optarg); + break; + + case 'v': + modern = true; + abbreviate_duplicate_blocks = false; + break; + + case TRADITIONAL_OPTION: + traditional = true; + break; + + /* The next several cases map the traditional format + specification options to the corresponding modern format + specs. GNU od accepts any combination of old- and + new-style options. Format specification options accumulate. + The obsolescent and undocumented formats are compatible + with FreeBSD 4.10 od. */ + +#define CASE_OLD_ARG(old_char,new_string) \ + case old_char: \ + ok &= decode_format_string (new_string); \ + break + + CASE_OLD_ARG ('a', "a"); + CASE_OLD_ARG ('b', "o1"); + CASE_OLD_ARG ('c', "c"); + CASE_OLD_ARG ('D', "u4"); /* obsolescent and undocumented */ + CASE_OLD_ARG ('d', "u2"); + case 'F': /* obsolescent and undocumented alias */ + CASE_OLD_ARG ('e', "fD"); /* obsolescent and undocumented */ + CASE_OLD_ARG ('f', "fF"); + case 'X': /* obsolescent and undocumented alias */ + CASE_OLD_ARG ('H', "x4"); /* obsolescent and undocumented */ + CASE_OLD_ARG ('i', "dI"); + case 'I': case 'L': /* obsolescent and undocumented aliases */ + CASE_OLD_ARG ('l', "dL"); + CASE_OLD_ARG ('O', "o4"); /* obsolesent and undocumented */ + case 'B': /* obsolescent and undocumented alias */ + CASE_OLD_ARG ('o', "o2"); + CASE_OLD_ARG ('s', "d2"); + case 'h': /* obsolescent and undocumented alias */ + CASE_OLD_ARG ('x', "x2"); + +#undef CASE_OLD_ARG + + case 'w': + modern = true; + width_specified = true; + if (optarg == NULL) + { + desired_width = 32; + } + else + { + uintmax_t w_tmp; + s_err = xstrtoumax (optarg, NULL, 10, &w_tmp, ""); + if (s_err != LONGINT_OK) + STRTOL_FATAL_ERROR (optarg, _("width specification"), s_err); + if (SIZE_MAX < w_tmp) + error (EXIT_FAILURE, 0, _("%s is too large"), optarg); + desired_width = w_tmp; + } + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + break; + } + } + + if (!ok) + exit (EXIT_FAILURE); + + if (flag_dump_strings && n_specs > 0) + error (EXIT_FAILURE, 0, + _("no type may be specified when dumping strings")); + + n_files = argc - optind; + + /* If the --traditional option is used, there may be from + 0 to 3 remaining command line arguments; handle each case + separately. + od [file] [[+]offset[.][b] [[+]label[.][b]]] + The offset and label have the same syntax. + + If --traditional is not given, and if no modern options are + given, and if the offset begins with + or (if there are two + operands) a digit, accept only this form, as per POSIX: + od [file] [[+]offset[.][b]] + */ + + if (!modern | traditional) + { + uintmax_t o1; + uintmax_t o2; + + switch (n_files) + { + case 1: + if ((traditional || argv[optind][0] == '+') + && parse_old_offset (argv[optind], &o1)) + { + n_bytes_to_skip = o1; + --n_files; + ++argv; + } + break; + + case 2: + if ((traditional || argv[optind + 1][0] == '+' + || ISDIGIT (argv[optind + 1][0])) + && parse_old_offset (argv[optind + 1], &o2)) + { + if (traditional && parse_old_offset (argv[optind], &o1)) + { + n_bytes_to_skip = o1; + flag_pseudo_start = true; + pseudo_start = o2; + argv += 2; + n_files -= 2; + } + else + { + n_bytes_to_skip = o2; + --n_files; + argv[optind + 1] = argv[optind]; + ++argv; + } + } + break; + + case 3: + if (traditional + && parse_old_offset (argv[optind + 1], &o1) + && parse_old_offset (argv[optind + 2], &o2)) + { + n_bytes_to_skip = o1; + flag_pseudo_start = true; + pseudo_start = o2; + argv[optind + 2] = argv[optind]; + argv += 2; + n_files -= 2; + } + break; + } + + if (traditional && 1 < n_files) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); + error (0, 0, "%s\n", + _("Compatibility mode supports at most one file.")); + usage (EXIT_FAILURE); + } + } + + if (flag_pseudo_start) + { + if (format_address == format_address_none) + { + address_base = 8; + address_pad_len = 7; + format_address = format_address_paren; + } + else + format_address = format_address_label; + } + + if (limit_bytes_to_format) + { + end_offset = n_bytes_to_skip + max_bytes_to_format; + if (end_offset < n_bytes_to_skip) + error (EXIT_FAILURE, 0, _("skip-bytes + read-bytes is too large")); + } + + if (n_specs == 0) + decode_format_string ("oS"); + + if (n_files > 0) + { + /* Set the global pointer FILE_LIST so that it + references the first file-argument on the command-line. */ + + file_list = (char const *const *) &argv[optind]; + } + else + { + /* No files were listed on the command line. + Set the global pointer FILE_LIST so that it + references the null-terminated list of one name: "-". */ + + file_list = default_file_list; + } + + /* open the first input file */ + ok = open_next_file (); + if (in_stream == NULL) + goto cleanup; + + /* skip over any unwanted header bytes */ + ok &= skip (n_bytes_to_skip); + if (in_stream == NULL) + goto cleanup; + + pseudo_offset = (flag_pseudo_start ? pseudo_start - n_bytes_to_skip : 0); + + /* Compute output block length. */ + l_c_m = get_lcm (); + + if (width_specified) + { + if (desired_width != 0 && desired_width % l_c_m == 0) + bytes_per_block = desired_width; + else + { + error (0, 0, _("warning: invalid width %lu; using %d instead"), + (unsigned long int) desired_width, l_c_m); + bytes_per_block = l_c_m; + } + } + else + { + if (l_c_m < DEFAULT_BYTES_PER_BLOCK) + bytes_per_block = l_c_m * (DEFAULT_BYTES_PER_BLOCK / l_c_m); + else + bytes_per_block = l_c_m; + } + +#ifdef DEBUG + for (i = 0; i < n_specs; i++) + { + printf (_("%d: fmt=\"%s\" width=%d\n"), + i, spec[i].fmt_string, width_bytes[spec[i].size]); + } +#endif + + ok &= (flag_dump_strings ? dump_strings () : dump ()); + +cleanup:; + + if (have_read_stdin && fclose (stdin) == EOF) + error (EXIT_FAILURE, errno, _("standard input")); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/paste.c b/src/paste.c new file mode 100644 index 0000000..414fb88 --- /dev/null +++ b/src/paste.c @@ -0,0 +1,497 @@ +/* paste - merge lines of files + Copyright (C) 1997-2005 Free Software Foundation, Inc. + Copyright (C) 1984 David M. Ihnat + + 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by David Ihnat. */ + +/* The list of valid escape sequences has been expanded over the Unix + version, to include \b, \f, \r, and \v. + + POSIX changes, bug fixes, long-named options, and cleanup + by David MacKenzie . + + Options: + --serial + -s Paste one file at a time rather than + one line from each file. + --delimiters=delim-list + -d delim-list Consecutively use the characters in + DELIM-LIST instead of tab to separate + merged lines. When DELIM-LIST is exhausted, + start again at its beginning. + A FILE of `-' means standard input. + If no FILEs are given, standard input is used. */ + +#include + +#include +#include +#include +#include "system.h" +#include "error.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "paste" + +#define AUTHORS "David M. Ihnat", "David MacKenzie" + +/* Indicates that no delimiter should be added in the current position. */ +#define EMPTY_DELIM '\0' + +/* Name this program was run with. */ +char *program_name; + +/* If nonzero, we have read standard input at some point. */ +static bool have_read_stdin; + +/* If nonzero, merge subsequent lines of each file rather than + corresponding lines from each file in parallel. */ +static bool serial_merge; + +/* The delimeters between lines of input files (used cyclically). */ +static char *delims; + +/* A pointer to the character after the end of `delims'. */ +static char const *delim_end; + +static struct option const longopts[] = +{ + {"serial", no_argument, NULL, 's'}, + {"delimiters", required_argument, NULL, 'd'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Set globals delims and delim_end. Copy STRPTR to DELIMS, converting + backslash representations of special characters in STRPTR to their actual + values. The set of possible backslash characters has been expanded beyond + that recognized by the Unix version. */ + +static void +collapse_escapes (char const *strptr) +{ + char *strout = xstrdup (strptr); + delims = strout; + + while (*strptr) + { + if (*strptr != '\\') /* Is it an escape character? */ + *strout++ = *strptr++; /* No, just transfer it. */ + else + { + switch (*++strptr) + { + case '0': + *strout++ = EMPTY_DELIM; + break; + + case 'b': + *strout++ = '\b'; + break; + + case 'f': + *strout++ = '\f'; + break; + + case 'n': + *strout++ = '\n'; + break; + + case 'r': + *strout++ = '\r'; + break; + + case 't': + *strout++ = '\t'; + break; + + case 'v': + *strout++ = '\v'; + break; + + default: + *strout++ = *strptr; + break; + } + strptr++; + } + } + delim_end = strout; +} + +/* Report a write error and exit. */ + +static void write_error (void) ATTRIBUTE_NORETURN; +static void +write_error (void) +{ + error (EXIT_FAILURE, errno, _("write error")); + abort (); +} + +/* Output a single byte, reporting any write errors. */ + +static inline void +xputchar (char c) +{ + if (putchar (c) < 0) + write_error (); +} + +/* Perform column paste on the NFILES files named in FNAMPTR. + Return true if successful, false if one or more files could not be + opened or read. */ + +static bool +paste_parallel (size_t nfiles, char **fnamptr) +{ + bool ok = true; + /* If all files are just ready to be closed, or will be on this + round, the string of delimiters must be preserved. + delbuf[0] through delbuf[nfiles] + store the delimiters for closed files. */ + char *delbuf = xmalloc (nfiles + 2); + + /* Streams open to the files to process; NULL if the corresponding + stream is closed. */ + FILE **fileptr = xnmalloc (nfiles + 1, sizeof *fileptr); + + /* Number of files still open to process. */ + size_t files_open; + + /* True if any fopen got fd == STDIN_FILENO. */ + bool opened_stdin = false; + + /* Attempt to open all files. This could be expanded to an infinite + number of files, but at the (considerable) expense of remembering + each file and its current offset, then opening/reading/closing. */ + + for (files_open = 0; files_open < nfiles; ++files_open) + { + if (STREQ (fnamptr[files_open], "-")) + { + have_read_stdin = true; + fileptr[files_open] = stdin; + } + else + { + fileptr[files_open] = fopen (fnamptr[files_open], "r"); + if (fileptr[files_open] == NULL) + error (EXIT_FAILURE, errno, "%s", fnamptr[files_open]); + else if (fileno (fileptr[files_open]) == STDIN_FILENO) + opened_stdin = true; + } + } + + if (opened_stdin && have_read_stdin) + error (EXIT_FAILURE, 0, _("standard input is closed")); + + /* Read a line from each file and output it to stdout separated by a + delimiter, until we go through the loop without successfully + reading from any of the files. */ + + while (files_open) + { + /* Set up for the next line. */ + bool somedone = false; + char const *delimptr = delims; + size_t delims_saved = 0; /* Number of delims saved in `delbuf'. */ + size_t i; + + for (i = 0; i < nfiles && files_open; i++) + { + int chr IF_LINT (= 0); /* Input character. */ + int err IF_LINT (= 0); /* Input errno value. */ + size_t line_length = 0; /* Number of chars in line. */ + + if (fileptr[i]) + { + chr = getc (fileptr[i]); + err = errno; + if (chr != EOF && delims_saved) + { + if (fwrite (delbuf, 1, delims_saved, stdout) != delims_saved) + write_error (); + delims_saved = 0; + } + + while (chr != EOF) + { + line_length++; + if (chr == '\n') + break; + xputchar (chr); + chr = getc (fileptr[i]); + err = errno; + } + } + + if (line_length == 0) + { + /* EOF, read error, or closed file. + If an EOF or error, close the file. */ + if (fileptr[i]) + { + if (ferror (fileptr[i])) + { + error (0, err, "%s", fnamptr[i]); + ok = false; + } + if (fileptr[i] == stdin) + clearerr (fileptr[i]); /* Also clear EOF. */ + else if (fclose (fileptr[i]) == EOF) + { + error (0, errno, "%s", fnamptr[i]); + ok = false; + } + + fileptr[i] = NULL; + files_open--; + } + + if (i + 1 == nfiles) + { + /* End of this output line. + Is this the end of the whole thing? */ + if (somedone) + { + /* No. Some files were not closed for this line. */ + if (delims_saved) + { + if (fwrite (delbuf, 1, delims_saved, stdout) + != delims_saved) + write_error (); + delims_saved = 0; + } + xputchar ('\n'); + } + continue; /* Next read of files, or exit. */ + } + else + { + /* Closed file; add delimiter to `delbuf'. */ + if (*delimptr != EMPTY_DELIM) + delbuf[delims_saved++] = *delimptr; + if (++delimptr == delim_end) + delimptr = delims; + } + } + else + { + /* Some data read. */ + somedone = true; + + /* Except for last file, replace last newline with delim. */ + if (i + 1 != nfiles) + { + if (chr != '\n' && chr != EOF) + xputchar (chr); + if (*delimptr != EMPTY_DELIM) + xputchar (*delimptr); + if (++delimptr == delim_end) + delimptr = delims; + } + else + { + /* If the last line of the last file lacks a newline, + print one anyhow. POSIX requires this. */ + char c = (chr == EOF ? '\n' : chr); + xputchar (c); + } + } + } + } + free (fileptr); + free (delbuf); + return ok; +} + +/* Perform serial paste on the NFILES files named in FNAMPTR. + Return true if no errors, false if one or more files could not be + opened or read. */ + +static bool +paste_serial (size_t nfiles, char **fnamptr) +{ + bool ok = true; /* false if open or read errors occur. */ + int charnew, charold; /* Current and previous char read. */ + char const *delimptr; /* Current delimiter char. */ + FILE *fileptr; /* Open for reading current file. */ + + for (; nfiles; nfiles--, fnamptr++) + { + int saved_errno; + bool is_stdin = STREQ (*fnamptr, "-"); + if (is_stdin) + { + have_read_stdin = true; + fileptr = stdin; + } + else + { + fileptr = fopen (*fnamptr, "r"); + if (fileptr == NULL) + { + error (0, errno, "%s", *fnamptr); + ok = false; + continue; + } + } + + delimptr = delims; /* Set up for delimiter string. */ + + charold = getc (fileptr); + saved_errno = errno; + if (charold != EOF) + { + /* `charold' is set up. Hit it! + Keep reading characters, stashing them in `charnew'; + output `charold', converting to the appropriate delimiter + character if needed. After the EOF, output `charold' + if it's a newline; otherwise, output it and then a newline. */ + + while ((charnew = getc (fileptr)) != EOF) + { + /* Process the old character. */ + if (charold == '\n') + { + if (*delimptr != EMPTY_DELIM) + xputchar (*delimptr); + + if (++delimptr == delim_end) + delimptr = delims; + } + else + xputchar (charold); + + charold = charnew; + } + saved_errno = errno; + + /* Hit EOF. Process that last character. */ + xputchar (charold); + } + + if (charold != '\n') + xputchar ('\n'); + + if (ferror (fileptr)) + { + error (0, saved_errno, "%s", *fnamptr); + ok = false; + } + if (is_stdin) + clearerr (fileptr); /* Also clear EOF. */ + else if (fclose (fileptr) == EOF) + { + error (0, errno, "%s", *fnamptr); + ok = false; + } + } + return ok; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + fputs (_("\ +Write lines consisting of the sequentially corresponding lines from\n\ +each FILE, separated by TABs, to standard output.\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -d, --delimiters=LIST reuse characters from LIST instead of TABs\n\ + -s, --serial paste one file at a time instead of in parallel\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + /* FIXME: add a couple of examples. */ + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int optc; + bool ok; + char const *delim_arg = "\t"; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + have_read_stdin = false; + serial_merge = false; + + while ((optc = getopt_long (argc, argv, "d:s", longopts, NULL)) != -1) + { + switch (optc) + { + case 'd': + /* Delimiter character(s). */ + delim_arg = (optarg[0] == '\0' ? "\\0" : optarg); + break; + + case 's': + serial_merge = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (optind == argc) + argv[argc++] = "-"; + + collapse_escapes (delim_arg); + + if (!serial_merge) + ok = paste_parallel (argc - optind, &argv[optind]); + else + ok = paste_serial (argc - optind, &argv[optind]); + + free (delims); + + if (have_read_stdin && fclose (stdin) == EOF) + error (EXIT_FAILURE, errno, "-"); + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/pathchk.c b/src/pathchk.c new file mode 100644 index 0000000..2fc55d3 --- /dev/null +++ b/src/pathchk.c @@ -0,0 +1,433 @@ +/* pathchk -- check whether file names are valid or portable + Copyright (C) 1991-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include +#include +#include +#include +#if HAVE_WCHAR_H +# include +#endif + +#include "system.h" +#include "error.h" +#include "euidaccess.h" +#include "quote.h" +#include "quotearg.h" + +#if ! (HAVE_MBRLEN && HAVE_MBSTATE_T) +# define mbrlen(s, n, ps) 1 +# define mbstate_t int +#endif + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "pathchk" + +#define AUTHORS "Paul Eggert", "David MacKenzie", "Jim Meyering" + +#ifndef _POSIX_PATH_MAX +# define _POSIX_PATH_MAX 256 +#endif +#ifndef _POSIX_NAME_MAX +# define _POSIX_NAME_MAX 14 +#endif + +#ifdef _XOPEN_NAME_MAX +# define NAME_MAX_MINIMUM _XOPEN_NAME_MAX +#else +# define NAME_MAX_MINIMUM _POSIX_NAME_MAX +#endif +#ifdef _XOPEN_PATH_MAX +# define PATH_MAX_MINIMUM _XOPEN_PATH_MAX +#else +# define PATH_MAX_MINIMUM _POSIX_PATH_MAX +#endif + +#if ! (HAVE_PATHCONF && defined _PC_NAME_MAX && defined _PC_PATH_MAX) +# ifndef _PC_NAME_MAX +# define _PC_NAME_MAX 0 +# define _PC_PATH_MAX 1 +# endif +# ifndef pathconf +# define pathconf(file, flag) \ + (flag == _PC_NAME_MAX ? NAME_MAX_MINIMUM : PATH_MAX_MINIMUM) +# endif +#endif + +static bool validate_file_name (char *, bool, bool); + +/* The name this program was run with. */ +char *program_name; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + PORTABILITY_OPTION = CHAR_MAX + 1 +}; + +static struct option const longopts[] = +{ + {"portability", no_argument, NULL, PORTABILITY_OPTION}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... NAME...\n"), program_name); + fputs (_("\ +Diagnose unportable constructs in NAME.\n\ +\n\ + -p check for most POSIX systems\n\ + -P check for empty names and leading \"-\"\n\ + --portability check for all POSIX systems (equivalent to -p -P)\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + bool ok = true; + bool check_basic_portability = false; + bool check_extra_portability = false; + int optc; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "+pP", longopts, NULL)) != -1) + { + switch (optc) + { + case PORTABILITY_OPTION: + check_basic_portability = true; + check_extra_portability = true; + break; + + case 'p': + check_basic_portability = true; + break; + + case 'P': + check_extra_portability = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (optind == argc) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + for (; optind < argc; ++optind) + ok &= validate_file_name (argv[optind], + check_basic_portability, check_extra_portability); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} + +/* If FILE contains a component with a leading "-", report an error + and return false; otherwise, return true. */ + +static bool +no_leading_hyphen (char const *file) +{ + char const *p; + + for (p = file; (p = strchr (p, '-')); p++) + if (p == file || p[-1] == '/') + { + error (0, 0, _("leading `-' in a component of file name %s"), + quote (file)); + return false; + } + + return true; +} + +/* If FILE (of length FILELEN) contains only portable characters, + return true, else report an error and return false. */ + +static bool +portable_chars_only (char const *file, size_t filelen) +{ + size_t validlen = strspn (file, + ("/" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789._-")); + char const *invalid = file + validlen; + + if (*invalid) + { + mbstate_t mbstate = { 0, }; + size_t charlen = mbrlen (invalid, filelen - validlen, &mbstate); + error (0, 0, + _("nonportable character %s in file name %s"), + quotearg_n_style_mem (1, locale_quoting_style, invalid, + (charlen <= MB_LEN_MAX ? charlen : 1)), + quote_n (0, file)); + return false; + } + + return true; +} + +/* Return the address of the start of the next file name component in F. */ + +static char * +component_start (char *f) +{ + while (*f == '/') + f++; + return f; +} + +/* Return the size of the file name component F. F must be nonempty. */ + +static size_t +component_len (char const *f) +{ + size_t len; + for (len = 1; f[len] != '/' && f[len]; len++) + continue; + return len; +} + +/* Make sure that + strlen (FILE) <= PATH_MAX + && strlen (each-existing-directory-in-FILE) <= NAME_MAX + + If CHECK_BASIC_PORTABILITY is true, compare against _POSIX_PATH_MAX and + _POSIX_NAME_MAX instead, and make sure that FILE contains no + characters not in the POSIX portable filename character set, which + consists of A-Z, a-z, 0-9, ., _, - (plus / for separators). + + If CHECK_BASIC_PORTABILITY is false, make sure that all leading directories + along FILE that exist are searchable. + + If CHECK_EXTRA_PORTABILITY is true, check that file name components do not + begin with "-". + + If either CHECK_BASIC_PORTABILITY or CHECK_EXTRA_PORTABILITY is true, + check that the file name is not empty. + + Return true if all of these tests are successful, false if any fail. */ + +static bool +validate_file_name (char *file, bool check_basic_portability, + bool check_extra_portability) +{ + size_t filelen = strlen (file); + + /* Start of file name component being checked. */ + char *start; + + /* True if component lengths need to be checked. */ + bool check_component_lengths; + + /* True if the file is known to exist. */ + bool file_exists = false; + + if (check_extra_portability && ! no_leading_hyphen (file)) + return false; + + if ((check_basic_portability | check_extra_portability) + && filelen == 0) + { + /* Fail, since empty names are not portable. As of + 2005-01-06 POSIX does not address whether "pathchk -p ''" + should (or is allowed to) fail, so this is not a + conformance violation. */ + error (0, 0, _("empty file name")); + return false; + } + + if (check_basic_portability) + { + if (! portable_chars_only (file, filelen)) + return false; + } + else + { + /* Check whether a file name component is in a directory that + is not searchable, or has some other serious problem. + POSIX does not allow "" as a file name, but some non-POSIX + hosts do (as an alias for "."), so allow "" if lstat does. */ + + struct stat st; + if (lstat (file, &st) == 0) + file_exists = true; + else if (errno != ENOENT || filelen == 0) + { + error (0, errno, "%s", file); + return false; + } + } + + if (check_basic_portability + || (! file_exists && PATH_MAX_MINIMUM <= filelen)) + { + size_t maxsize; + + if (check_basic_portability) + maxsize = _POSIX_PATH_MAX; + else + { + long int size; + char const *dir = (*file == '/' ? "/" : "."); + errno = 0; + size = pathconf (dir, _PC_PATH_MAX); + if (size < 0 && errno != 0) + { + error (0, errno, + _("%s: unable to determine maximum file name length"), + dir); + return false; + } + maxsize = MIN (size, SIZE_MAX); + } + + if (maxsize <= filelen) + { + unsigned long int len = filelen; + unsigned long int maxlen = maxsize - 1; + error (0, 0, _("limit %lu exceeded by length %lu of file name %s"), + maxlen, len, quote (file)); + return false; + } + } + + /* Check whether pathconf (..., _PC_NAME_MAX) can be avoided, i.e., + whether all file name components are so short that they are valid + in any file system on this platform. If CHECK_BASIC_PORTABILITY, though, + it's more convenient to check component lengths below. */ + + check_component_lengths = check_basic_portability; + if (! check_component_lengths && ! file_exists) + { + for (start = file; *(start = component_start (start)); ) + { + size_t length = component_len (start); + + if (NAME_MAX_MINIMUM < length) + { + check_component_lengths = true; + break; + } + + start += length; + } + } + + if (check_component_lengths) + { + /* The limit on file name components for the current component. + This defaults to NAME_MAX_MINIMUM, for the sake of non-POSIX + systems (NFS, say?) where pathconf fails on "." or "/" with + errno == ENOENT. */ + size_t name_max = NAME_MAX_MINIMUM; + + /* If nonzero, the known limit on file name components. */ + size_t known_name_max = (check_basic_portability ? _POSIX_NAME_MAX : 0); + + for (start = file; *(start = component_start (start)); ) + { + size_t length; + + if (known_name_max) + name_max = known_name_max; + else + { + long int len; + char const *dir = (start == file ? "." : file); + char c = *start; + errno = 0; + *start = '\0'; + len = pathconf (dir, _PC_NAME_MAX); + *start = c; + if (0 <= len) + name_max = MIN (len, SIZE_MAX); + else + switch (errno) + { + case 0: + /* There is no limit. */ + name_max = SIZE_MAX; + break; + + case ENOENT: + /* DIR does not exist; use its parent's maximum. */ + known_name_max = name_max; + break; + + default: + *start = '\0'; + error (0, errno, "%s", dir); + *start = c; + return false; + } + } + + length = component_len (start); + + if (name_max < length) + { + unsigned long int len = length; + unsigned long int maxlen = name_max; + char c = start[len]; + start[len] = '\0'; + error (0, 0, + _("limit %lu exceeded by length %lu " + "of file name component %s"), + maxlen, len, quote (start)); + start[len] = c; + return false; + } + + start += length; + } + } + + return true; +} diff --git a/src/pinky.c b/src/pinky.c new file mode 100644 index 0000000..885012b --- /dev/null +++ b/src/pinky.c @@ -0,0 +1,623 @@ +/* GNU's pinky. + Copyright (C) 1992-1997, 1999-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Created by hacking who.c by Kaveh Ghazi ghazi@caip.rutgers.edu */ + +#include +#include +#include +#include + +#include +#include "system.h" + +#include "canon-host.h" +#include "error.h" +#include "hard-locale.h" +#include "inttostr.h" +#include "readutmp.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "pinky" + +#define AUTHORS "Joseph Arceneaux", "David MacKenzie", "Kaveh Ghazi" + +#ifndef MAXHOSTNAMELEN +# define MAXHOSTNAMELEN 64 +#endif + +char *ttyname (); + +/* The name this program was run with. */ +const char *program_name; + +/* If true, display the hours:minutes since each user has touched + the keyboard, or blank if within the last minute, or days followed + by a 'd' if not within the last day. */ +static bool include_idle = true; + +/* If true, display a line at the top describing each field. */ +static bool include_heading = true; + +/* if true, display the user's full name from pw_gecos. */ +static bool include_fullname = true; + +/* if true, display the user's ~/.project file when doing long format. */ +static bool include_project = true; + +/* if true, display the user's ~/.plan file when doing long format. */ +static bool include_plan = true; + +/* if true, display the user's home directory and shell + when doing long format. */ +static bool include_home_and_shell = true; + +/* if true, use the "short" output format. */ +static bool do_short_format = true; + +/* if true, display the ut_host field. */ +#ifdef HAVE_UT_HOST +static bool include_where = true; +#endif + +/* The strftime format to use for login times, and its expected + output width. */ +static char const *time_format; +static int time_format_width; + +static struct option const longopts[] = +{ + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Count and return the number of ampersands in STR. */ + +static size_t +count_ampersands (const char *str) +{ + size_t count = 0; + do + { + if (*str == '&') + count++; + } while (*str++); + return count; +} + +/* Create a string (via xmalloc) which contains a full name by substituting + for each ampersand in GECOS_NAME the USER_NAME string with its first + character capitalized. The caller must ensure that GECOS_NAME contains + no `,'s. The caller also is responsible for free'ing the return value of + this function. */ + +static char * +create_fullname (const char *gecos_name, const char *user_name) +{ + size_t rsize = strlen (gecos_name) + 1; + char *result; + char *r; + size_t ampersands = count_ampersands (gecos_name); + + if (ampersands != 0) + { + size_t ulen = strlen (user_name); + size_t product = ampersands * ulen; + rsize += product - ampersands; + if (xalloc_oversized (ulen, ampersands) || rsize < product) + xalloc_die (); + } + + r = result = xmalloc (rsize); + + while (*gecos_name) + { + if (*gecos_name == '&') + { + const char *uname = user_name; + if (islower (to_uchar (*uname))) + *r++ = toupper (to_uchar (*uname++)); + while (*uname) + *r++ = *uname++; + } + else + { + *r++ = *gecos_name; + } + + gecos_name++; + } + *r = 0; + + return result; +} + +/* Return a string representing the time between WHEN and the time + that this function is first run. */ + +static const char * +idle_string (time_t when) +{ + static time_t now = 0; + static char buf[INT_STRLEN_BOUND (long int) + 2]; + time_t seconds_idle; + + if (now == 0) + time (&now); + + seconds_idle = now - when; + if (seconds_idle < 60) /* One minute. */ + return " "; + if (seconds_idle < (24 * 60 * 60)) /* One day. */ + { + int hours = seconds_idle / (60 * 60); + int minutes = (seconds_idle % (60 * 60)) / 60; + sprintf (buf, "%02d:%02d", hours, minutes); + } + else + { + unsigned long int days = seconds_idle / (24 * 60 * 60); + sprintf (buf, "%lud", days); + } + return buf; +} + +/* Return a time string. */ +static const char * +time_string (const STRUCT_UTMP *utmp_ent) +{ + static char buf[INT_STRLEN_BOUND (intmax_t) + sizeof "-%m-%d %H:%M"]; + + /* Don't take the address of UT_TIME_MEMBER directly. + Ulrich Drepper wrote: + ``... GNU libc (and perhaps other libcs as well) have extended + utmp file formats which do not use a simple time_t ut_time field. + In glibc, ut_time is a macro which selects for backward compatibility + the tv_sec member of a struct timeval value.'' */ + time_t t = UT_TIME_MEMBER (utmp_ent); + struct tm *tmp = localtime (&t); + + if (tmp) + { + strftime (buf, sizeof buf, time_format, tmp); + return buf; + } + else + return TYPE_SIGNED (time_t) ? imaxtostr (t, buf) : umaxtostr (t, buf); +} + +/* Display a line of information about UTMP_ENT. */ + +static void +print_entry (const STRUCT_UTMP *utmp_ent) +{ + struct stat stats; + time_t last_change; + char mesg; + +#define DEV_DIR_WITH_TRAILING_SLASH "/dev/" +#define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1) + + char line[sizeof (utmp_ent->ut_line) + DEV_DIR_LEN + 1]; + + /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not + already an absolute file name. Some system may put the full, + absolute file name in ut_line. */ + if (utmp_ent->ut_line[0] == '/') + { + strncpy (line, utmp_ent->ut_line, sizeof (utmp_ent->ut_line)); + line[sizeof (utmp_ent->ut_line)] = '\0'; + } + else + { + strcpy (line, DEV_DIR_WITH_TRAILING_SLASH); + strncpy (line + DEV_DIR_LEN, utmp_ent->ut_line, sizeof (utmp_ent->ut_line)); + line[DEV_DIR_LEN + sizeof (utmp_ent->ut_line)] = '\0'; + } + + if (stat (line, &stats) == 0) + { + mesg = (stats.st_mode & S_IWGRP) ? ' ' : '*'; + last_change = stats.st_atime; + } + else + { + mesg = '?'; + last_change = 0; + } + + printf ("%-8.*s", UT_USER_SIZE, UT_USER (utmp_ent)); + + if (include_fullname) + { + struct passwd *pw; + char name[UT_USER_SIZE + 1]; + + strncpy (name, UT_USER (utmp_ent), UT_USER_SIZE); + name[UT_USER_SIZE] = '\0'; + pw = getpwnam (name); + if (pw == NULL) + printf (" %19s", " ???"); + else + { + char *const comma = strchr (pw->pw_gecos, ','); + char *result; + + if (comma) + *comma = '\0'; + + result = create_fullname (pw->pw_gecos, pw->pw_name); + printf (" %-19.19s", result); + free (result); + } + } + + printf (" %c%-8.*s", + mesg, (int) sizeof (utmp_ent->ut_line), utmp_ent->ut_line); + + if (include_idle) + { + if (last_change) + printf (" %-6s", idle_string (last_change)); + else + printf (" %-6s", "???"); + } + + printf (" %s", time_string (utmp_ent)); + +#ifdef HAVE_UT_HOST + if (include_where && utmp_ent->ut_host[0]) + { + char ut_host[sizeof (utmp_ent->ut_host) + 1]; + char *host = NULL; + char *display = NULL; + + /* Copy the host name into UT_HOST, and ensure it's nul terminated. */ + strncpy (ut_host, utmp_ent->ut_host, (int) sizeof (utmp_ent->ut_host)); + ut_host[sizeof (utmp_ent->ut_host)] = '\0'; + + /* Look for an X display. */ + display = strchr (ut_host, ':'); + if (display) + *display++ = '\0'; + + if (*ut_host) + /* See if we can canonicalize it. */ + host = canon_host (ut_host); + if ( ! host) + host = ut_host; + + if (display) + printf (" %s:%s", host, display); + else + printf (" %s", host); + + if (host != ut_host) + free (host); + } +#endif + + putchar ('\n'); +} + +/* Display a verbose line of information about UTMP_ENT. */ + +static void +print_long_entry (const char name[]) +{ + struct passwd *pw; + + pw = getpwnam (name); + + printf (_("Login name: ")); + printf ("%-28s", name); + + printf (_("In real life: ")); + if (pw == NULL) + { + printf (" %s", _("???\n")); + return; + } + else + { + char *const comma = strchr (pw->pw_gecos, ','); + char *result; + + if (comma) + *comma = '\0'; + + result = create_fullname (pw->pw_gecos, pw->pw_name); + printf (" %s", result); + free (result); + } + + putchar ('\n'); + + if (include_home_and_shell) + { + printf (_("Directory: ")); + printf ("%-29s", pw->pw_dir); + printf (_("Shell: ")); + printf (" %s", pw->pw_shell); + putchar ('\n'); + } + + if (include_project) + { + FILE *stream; + char buf[1024]; + const char *const baseproject = "/.project"; + char *const project = + xmalloc (strlen (pw->pw_dir) + strlen (baseproject) + 1); + + strcpy (project, pw->pw_dir); + strcat (project, baseproject); + + stream = fopen (project, "r"); + if (stream) + { + size_t bytes; + + printf (_("Project: ")); + + while ((bytes = fread (buf, 1, sizeof (buf), stream)) > 0) + fwrite (buf, 1, bytes, stdout); + fclose (stream); + } + + free (project); + } + + if (include_plan) + { + FILE *stream; + char buf[1024]; + const char *const baseplan = "/.plan"; + char *const plan = + xmalloc (strlen (pw->pw_dir) + strlen (baseplan) + 1); + + strcpy (plan, pw->pw_dir); + strcat (plan, baseplan); + + stream = fopen (plan, "r"); + if (stream) + { + size_t bytes; + + printf (_("Plan:\n")); + + while ((bytes = fread (buf, 1, sizeof (buf), stream)) > 0) + fwrite (buf, 1, bytes, stdout); + fclose (stream); + } + + free (plan); + } + + putchar ('\n'); +} + +/* Print the username of each valid entry and the number of valid entries + in UTMP_BUF, which should have N elements. */ + +static void +print_heading (void) +{ + printf ("%-8s", _("Login")); + if (include_fullname) + printf (" %-19s", _("Name")); + printf (" %-9s", _(" TTY")); + if (include_idle) + printf (" %-6s", _("Idle")); + printf (" %-*s", time_format_width, _("When")); +#ifdef HAVE_UT_HOST + if (include_where) + printf (" %s", _("Where")); +#endif + putchar ('\n'); +} + +/* Display UTMP_BUF, which should have N entries. */ + +static void +scan_entries (size_t n, const STRUCT_UTMP *utmp_buf, + const int argc_names, char *const argv_names[]) +{ + if (hard_locale (LC_TIME)) + { + time_format = "%Y-%m-%d %H:%M"; + time_format_width = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2; + } + else + { + time_format = "%b %e %H:%M"; + time_format_width = 3 + 1 + 2 + 1 + 2 + 1 + 2; + } + + if (include_heading) + print_heading (); + + while (n--) + { + if (IS_USER_PROCESS (utmp_buf)) + { + if (argc_names) + { + int i; + + for (i = 0; i < argc_names; i++) + if (strncmp (UT_USER (utmp_buf), argv_names[i], UT_USER_SIZE) + == 0) + { + print_entry (utmp_buf); + break; + } + } + else + print_entry (utmp_buf); + } + utmp_buf++; + } +} + +/* Display a list of who is on the system, according to utmp file FILENAME. */ + +static void +short_pinky (const char *filename, + const int argc_names, char *const argv_names[]) +{ + size_t n_users; + STRUCT_UTMP *utmp_buf; + + if (read_utmp (filename, &n_users, &utmp_buf, 0) != 0) + error (EXIT_FAILURE, errno, "%s", filename); + + scan_entries (n_users, utmp_buf, argc_names, argv_names); +} + +static void +long_pinky (const int argc_names, char *const argv_names[]) +{ + int i; + + for (i = 0; i < argc_names; i++) + print_long_entry (argv_names[i]); +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... [USER]...\n"), program_name); + fputs (_("\ +\n\ + -l produce long format output for the specified USERs\n\ + -b omit the user's home directory and shell in long format\n\ + -h omit the user's project file in long format\n\ + -p omit the user's plan file in long format\n\ + -s do short format output, this is the default\n\ +"), stdout); + fputs (_("\ + -f omit the line of column headings in short format\n\ + -w omit the user's full name in short format\n\ + -i omit the user's full name and remote host in short format\n\ + -q omit the user's full name, remote host and idle time\n\ + in short format\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\ +\n\ +A lightweight `finger' program; print user information.\n\ +The utmp file will be %s.\n\ +"), UTMP_FILE); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int optc; + int n_users; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "sfwiqbhlp", longopts, NULL)) != -1) + { + switch (optc) + { + case 's': + do_short_format = true; + break; + + case 'l': + do_short_format = false; + break; + + case 'f': + include_heading = false; + break; + + case 'w': + include_fullname = false; + break; + + case 'i': + include_fullname = false; +#ifdef HAVE_UT_HOST + include_where = false; +#endif + break; + + case 'q': + include_fullname = false; +#ifdef HAVE_UT_HOST + include_where = false; +#endif + include_idle = false; + break; + + case 'h': + include_project = false; + break; + + case 'p': + include_plan = false; + break; + + case 'b': + include_home_and_shell = false; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + n_users = argc - optind; + + if (!do_short_format && n_users == 0) + { + error (0, 0, _("no username specified; at least one must be\ + specified when using -l")); + usage (EXIT_FAILURE); + } + + if (do_short_format) + short_pinky (UTMP_FILE, n_users, argv + optind); + else + long_pinky (n_users, argv + optind); + + exit (EXIT_SUCCESS); +} diff --git a/src/pr.c b/src/pr.c new file mode 100644 index 0000000..e0aea22 --- /dev/null +++ b/src/pr.c @@ -0,0 +1,2878 @@ +/* pr -- convert text files for printing. + Copyright (C) 88, 91, 1995-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* By Pete TerMaat, with considerable refinement by Roland Huebner. */ + +/* Things to watch: Sys V screws up on ... + pr -n -3 -s: /usr/dict/words + pr -m -o10 -n /usr/dict/words{,,,} + pr -6 -a -n -o5 /usr/dict/words + + Ideas: + + Keep a things_to_do list of functions to call when we know we have + something to print. Cleaner than current series of checks. + + Improve the printing of control prefixes. + + Expand the file name in the centered header line to a full file name. + + + Concept: + + If the input_tab_char differs from the default value TAB + (`-e[CHAR[...]]' is used), any input text tab is expanded to the + default width of 8 spaces (compare char_to_clump). - Same as SunOS + does. + + The treatment of the number_separator (compare add_line_number): + The default value TAB of the number_separator (`-n[SEP[...]]') doesn't + be thought to be an input character. An optional `-e'-input has no + effect. + - With single column output + only one POSIX requirement has to be met: + The default n-separator should be a TAB. The consequence is a + different width between the number and the text if the output position + of the separator changes, i.e. it depends upon the left margin used. + That's not nice but easy-to-use together with the defaults of other + utilities, e.g. sort or cut. - Same as SunOS does. + - With multicolumn output + two conflicting POSIX requirements exist: + First `default n-separator is TAB', second `output text columns shall + be of equal width'. Moreover POSIX specifies the number+separator a + part of the column, together with `-COLUMN' and `-a -COLUMN'. + (With -m output the number shall occupy each line only once. Exactly + the same situation as single column output exists.) + GNU pr gives priority to the 2nd requirement and observes POSIX + column definition. The n-separator TAB is expanded to the same number + of spaces in each column using the default value 8. Tabification is + only performed if it is compatible with the output position. + Consequence: The output text columns are of equal width. The layout + of a page does not change if the left margin varies. - Looks better + than the SunOS approach. + SunOS pr gives priority to the 1st requirement. n-separator TAB + width varies with each column. Only the width of text part of the + column is fixed. + Consequence: The output text columns don't have equal width. The + widths and the layout of the whole page varies with the left margin. + An overflow of the line length (without margin) over the input value + PAGE_WIDTH may occur. + + The interference of the POSIX-compliant small letter options -w and -s: + (`interference' means `setting a _separator_ with -s switches off the + column sturctur and the default - not generally - page_width, + acts on -w option') + options: text form / separator: equivalent new options: + -w l -s[x] + -------------------------------------------------------------------- + 1. -- -- columns / space -- + trunc. to page_width = 72 + 2. -- -s[:] full lines / TAB[:] -J --sep-string[=""|:] + no truncation + 3. -w l -- columns / space -W l + trunc. to page_width = l + 4. -w l -s[:] columns / no sep.[:] -W l --sep-string[=:] + trunc. to page_width = l + -------------------------------------------------------------------- + + + Options: + + Including version 1.22i: + Some SMALL LETTER options has been redefined with the object of a + better POSIX compliance. The output of some further cases has been + adapted to other UNIXes. A violation of downward compatibility has to + be accepted. + Some NEW CAPITAL LETTER options ( -J, -S, -W) has been introduced to + turn off unexpected interferences of small letter options (-s and -w + together with the three column options). + -N option and the second argument LAST_PAGE of +FIRST_PAGE offer more + flexibility; The detailed handling of form feeds set in the input + files requires -T option. + + Capital letter options dominate small letter ones. + + Some of the option-arguments cannot be specified as separate arguments + from the preceding option letter (already stated in POSIX specification). + + Form feeds in the input cause page breaks in the output. Multiple + form feeds produce empty pages. + + +FIRST_PAGE[:LAST_PAGE], --pages=FIRST_PAGE[:LAST_PAGE] + begin [stop] printing with page FIRST_[LAST_]PAGE + + -COLUMN, --columns=COLUMN + Produce output that is COLUMN columns wide and + print columns down, unless -a is used. Balance number of + lines in the columns on each page. + + -a, --across Print columns across rather than down, used + together with -COLUMN. The input + one + two + three + four + will be printed with `-a -3' as + one two three + four + + -b Balance columns on the last page. + -b is no longer an independent option. It's always used + together with -COLUMN (unless -a is used) to get a + consistent formulation with "FF set by hand" in input + files. Each formfeed found terminates the number of lines + to be read with the actual page. The situation for + printing columns down is equivalent to that on the last + page. So we need a balancing. + + Keeping -b as an underground option guarantees some + downward compatibility. Utilities using pr with -b + (a most frequently used form) still work as usual. + + -c, --show-control-chars + Print unprintable characters as control prefixes. + Control-g is printed as ^G (use hat notation) and + octal backslash notation. + + -d, --double-space Double space the output. + + -D FORMAT, --date-format=FORMAT Use FORMAT for the header date. + + -e[CHAR[WIDTH]], --expand-tabs[=CHAR[WIDTH]] + Expand tabs to spaces on input. Optional argument CHAR + is the input TAB character. (Default is TAB). Optional + argument WIDTH is the input TAB character's width. + (Default is 8.) + + -F, -f, --form-feed Use formfeeds instead of newlines to separate + pages. A three line HEADER is used, no TRAILER with -F, + without -F both HEADER and TRAILER are made of five lines. + + -h HEADER, --header=HEADER + Replace the filename in the header with the string HEADER. + A centered header is used. + + -i[CHAR[WIDTH]], --output-tabs[=CHAR[WIDTH]] + Replace spaces with tabs on output. Optional argument + CHAR is the output TAB character. (Default is TAB). + Optional argument WIDTH is the output TAB character's + width. (Default is 8) + + -J, --join-lines Merge lines of full length, turns off -W/-w + line truncation, no column alignment, --sep-string[=STRING] + sets separators, works with all column options + (-COLUMN | -a -COLUMN | -m). + -J has been introduced (together with -W and --sep-string) to + disentangle the old (POSIX compliant) options -w, -s + along with the 3 column options. + + -l PAGE_LENGTH, --length=PAGE_LENGTH + Set the page length to PAGE_LENGTH lines. Default is 66, + including 5 lines of HEADER and 5 lines of TRAILER + without -F, but only 3 lines of HEADER and no TRAILER + with -F (i.e the number of text lines defaults to 56 or + 63 respectively). + + -m, --merge Print files in parallel; pad_across_to align + columns; truncate lines and print separator strings; + Do it also with empty columns to get a continuous line + numbering and column marking by separators throughout + the whole merged file. + + Empty pages in some input files produce empty columns + [marked by separators] in the merged pages. Completely + empty merged pages show no column separators at all. + + The layout of a merged page is ruled by the largest form + feed distance of the single pages at that page. Shorter + columns will be filled up with empty lines. + + Together with -J option join lines of full length and + set separators when -S option is used. + + -n[SEP[DIGITS]], --number-lines[=SEP[DIGITS]] + Provide DIGITS digit line numbering (default for DIGITS + is 5). With multicolumn output the number occupies the + first DIGITS column positions of each text column or only + each line of -m output. + With single column output the number precedes each line + just as -m output. + Optional argument SEP is the character appended to the + line number to separate it from the text followed. + The default separator is a TAB. In a strict sense a TAB + is always printed with single column output only. The + TAB-width varies with the TAB-position, e.g. with the + left margin specified by -o option. + With multicolumn output priority is given to `equal width + of output columns' (a POSIX specification). The TAB-width + is fixed to the value of the 1st column and does not + change with different values of left margin. That means a + fixed number of spaces is always printed in the place of + a TAB. The tabification depends upon the output + position. + + Default counting of the line numbers starts with 1st + line of the input file (not the 1st line printed, + compare the --page option and -N option). + + -N NUMBER, --first-line-number=NUMBER + Start line counting with the number NUMBER at the 1st + line of first page printed (mostly not the 1st line of + the input file). + + -o MARGIN, --indent=MARGIN + Offset each line with a margin MARGIN spaces wide. + Total page width is the size of the margin plus the + PAGE_WIDTH set with -W/-w option. + + -r, --no-file-warnings + Omit warning when a file cannot be opened. + + -s[CHAR], --separator[=CHAR] + Separate columns by a single character CHAR, default for + CHAR is the TAB character without -w and 'no char' with -w. + Without `-s' default separator `space' is set. + -s[CHAR] turns off line truncation of all 3 column options + (-COLUMN|-a -COLUMN|-m) except -w is set. That is a POSIX + compliant formulation. The source code translates -s into + the new options -S and -J, also -W if required. + + -S STRING, --sep-string[=STRING] + Separate columns by any string STRING. The -S option + doesn't react upon the -W/-w option (unlike -s option + does). It defines a separator nothing else. + Without -S: Default separator TAB is used with -J and + `space' otherwise (same as -S" "). + With -S "": No separator is used. + Quotes should be used with blanks and some shell active + characters. + -S is problematic because in its obsolete form you + cannot use -S "STRING", but in its standard form you + must use -S "STRING" if STRING is empty. Use + --sep-string to avoid the ambiguity. + + -t, --omit-header Do not print headers or footers but retain form + feeds set in the input files. + + -T, --omit-pagination + Do not print headers or footers, eliminate any pagination + by form feeds set in the input files. + + -v, --show-nonprinting + Print unprintable characters as escape sequences. Use + octal backslash notation. Control-G becomes \007. + + -w PAGE_WIDTH, --width=PAGE_WIDTH + Set page width to PAGE_WIDTH characters for multiple + text-column output only (default for PAGE_WIDTH is 72). + -s[CHAR] turns off the default page width and any line + truncation. Lines of full length will be merged, + regardless of the column options set. A POSIX compliant + formulation. + + -W PAGE_WIDTH, --page-width=PAGE_WIDTH + Set the page width to PAGE_WIDTH characters. That's valid + with and without a column option. Text lines will be + truncated, unless -J is used. Together with one of the + column options (-COLUMN| -a -COLUMN| -m) column alignment + is always used. + Default is 72 characters. + Without -W PAGE_WIDTH + - but with one of the column options default truncation of + 72 characters is used (to keep downward compatibility + and to simplify most frequently met column tasks). + Column alignment and column separators are used. + - and without any of the column options NO line truncation + is used (to keep downward compatibility and to meet most + frequent tasks). That's equivalent to -W 72 -J . + + With/without -W PAGE_WIDTH the header line is always + truncated to avoid line overflow. + + (In pr versions newer than 1.14 -S option does no longer + affect -W option.) + +*/ + + +#include + +#include +#include +#include "system.h" +#include "error.h" +#include "hard-locale.h" +#include "inttostr.h" +#include "mbswidth.h" +#include "quote.h" +#include "stat-time.h" +#include "stdio--.h" +#include "strftime.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "pr" + +#define AUTHORS "Pete TerMaat", "Roland Huebner" + +/* Used with start_position in the struct COLUMN described below. + If start_position == ANYWHERE, we aren't truncating columns and + can begin printing a column anywhere. Otherwise we must pad to + the horizontal position start_position. */ +#define ANYWHERE 0 + +/* Each column has one of these structures allocated for it. + If we're only dealing with one file, fp is the same for all + columns. + + The general strategy is to spend time setting up these column + structures (storing columns if necessary), after which printing + is a matter of flitting from column to column and calling + print_func. + + Parallel files, single files printing across in multiple + columns, and single files printing down in multiple columns all + fit the same printing loop. + + print_func Function used to print lines in this column. + If we're storing this column it will be + print_stored(), Otherwise it will be read_line(). + + char_func Function used to process characters in this column. + If we're storing this column it will be store_char(), + otherwise it will be print_char(). + + current_line Index of the current entry in line_vector, which + contains the index of the first character of the + current line in buff[]. + + lines_stored Number of lines in this column which are stored in + buff. + + lines_to_print If we're storing this column, lines_to_print is + the number of stored_lines which remain to be + printed. Otherwise it is the number of lines + we can print without exceeding lines_per_body. + + start_position The horizontal position we want to be in before we + print the first character in this column. + + numbered True means precede this column with a line number. */ + +/* FIXME: There are many unchecked integer overflows in this file, + that will cause this command to misbehave given large inputs or + options. Many of the "int" values below should be "size_t" or + something else like that. */ + +struct COLUMN; +struct COLUMN + { + FILE *fp; /* Input stream for this column. */ + char const *name; /* File name. */ + enum + { + OPEN, + FF_FOUND, /* used with -b option, set with \f, changed + to ON_HOLD after print_header */ + ON_HOLD, /* Hit a form feed. */ + CLOSED + } + status; /* Status of the file pointer. */ + + /* Func to print lines in this col. */ + bool (*print_func) (struct COLUMN *); + + /* Func to print/store chars in this col. */ + void (*char_func) (char); + + int current_line; /* Index of current place in line_vector. */ + int lines_stored; /* Number of lines stored in buff. */ + int lines_to_print; /* No. lines stored or space left on page. */ + int start_position; /* Horizontal position of first char. */ + bool numbered; + bool full_page_printed; /* True means printed without a FF found. */ + + /* p->full_page_printed controls a special case of "FF set by hand": + True means a full page has been printed without FF found. To avoid an + additional empty page we have to ignore a FF immediately following in + the next line. */ + }; + +typedef struct COLUMN COLUMN; + +#define NULLCOL (COLUMN *)0 + +static int char_to_clump (char c); +static bool read_line (COLUMN *p); +static bool print_page (void); +static bool print_stored (COLUMN *p); +static bool open_file (char *name, COLUMN *p); +static bool skip_to_page (uintmax_t page); +static void print_header (void); +static void pad_across_to (int position); +static void add_line_number (COLUMN *p); +static void getoptarg (char *arg, char switch_char, char *character, + int *number); +void usage (int status); +static void print_files (int number_of_files, char **av); +static void init_parameters (int number_of_files); +static void init_header (char const *filename, int desc); +static bool init_fps (int number_of_files, char **av); +static void init_funcs (void); +static void init_store_cols (void); +static void store_columns (void); +static void balance (int total_stored); +static void store_char (char c); +static void pad_down (int lines); +static void read_rest_of_line (COLUMN *p); +static void skip_read (COLUMN *p, int column_number); +static void print_char (char c); +static void cleanup (void); +static void print_sep_string (void); +static void separator_string (const char *optarg_S); + +/* The name under which this program was invoked. */ +char *program_name; + +/* All of the columns to print. */ +static COLUMN *column_vector; + +/* When printing a single file in multiple downward columns, + we store the leftmost columns contiguously in buff. + To print a line from buff, get the index of the first character + from line_vector[i], and print up to line_vector[i + 1]. */ +static char *buff; + +/* Index of the position in buff where the next character + will be stored. */ +static int buff_current; + +/* The number of characters in buff. + Used for allocation of buff and to detect overflow of buff. */ +static size_t buff_allocated; + +/* Array of indices into buff. + Each entry is an index of the first character of a line. + This is used when storing lines to facilitate shuffling when + we do column balancing on the last page. */ +static int *line_vector; + +/* Array of horizonal positions. + For each line in line_vector, end_vector[line] is the horizontal + position we are in after printing that line. We keep track of this + so that we know how much we need to pad to prepare for the next + column. */ +static int *end_vector; + +/* (-m) True means we're printing multiple files in parallel. */ +static bool parallel_files = false; + +/* (-m) True means a line starts with some empty columns (some files + already CLOSED or ON_HOLD) which we have to align. */ +static bool align_empty_cols; + +/* (-m) True means we have not yet found any printable column in a line. + align_empty_cols = true has to be maintained. */ +static bool empty_line; + +/* (-m) False means printable column output precedes a form feed found. + Column alignment is done only once. No additional action with that form + feed. + True means we found only a form feed in a column. Maybe we have to do + some column alignment with that form feed. */ +static bool FF_only; + +/* (-[0-9]+) True means we're given an option explicitly specifying + number of columns. Used to detect when this option is used with -m + and when translating old options to new/long options. */ +static bool explicit_columns = false; + +/* (-t|-T) False means we aren't printing headers and footers. */ +static bool extremities = true; + +/* (-t) True means we retain all FF set by hand in input files. + False is set with -T option. */ +static bool keep_FF = false; +static bool print_a_FF = false; + +/* True means we need to print a header as soon as we know we've got input + to print after it. */ +static bool print_a_header; + +/* (-f) True means use formfeeds instead of newlines to separate pages. */ +static bool use_form_feed = false; + +/* True means we have read the standard input. */ +static bool have_read_stdin = false; + +/* True means the -a flag has been given. */ +static bool print_across_flag = false; + +/* True means we're printing one file in multiple (>1) downward columns. */ +static bool storing_columns = true; + +/* (-b) True means balance columns on the last page as Sys V does. */ +/* That's no longer an independent option. With storing_columns = true + balance_columns = true is used too (s. function init_parameters). + We get a consistent formulation with "FF set by hand" in input files. */ +static bool balance_columns = false; + +/* (-l) Number of lines on a page, including header and footer lines. */ +static int lines_per_page = 66; + +/* Number of lines in the header and footer can be reset to 0 using + the -t flag. */ +static int lines_per_header = 5; +static int lines_per_body; +static int lines_per_footer = 5; + +/* (-w|-W) Width in characters of the page. Does not include the width of + the margin. */ +static int chars_per_line = 72; + +/* (-w|W) True means we truncate lines longer than chars_per_column. */ +static bool truncate_lines = false; + +/* (-J) True means we join lines without any line truncation. -J + dominates -w option. */ +static bool join_lines = false; + +/* Number of characters in a column. Based on col_sep_length and + page width. */ +static int chars_per_column; + +/* (-e) True means convert tabs to spaces on input. */ +static bool untabify_input = false; + +/* (-e) The input tab character. */ +static char input_tab_char = '\t'; + +/* (-e) Tabstops are at chars_per_tab, 2*chars_per_tab, 3*chars_per_tab, ... + where the leftmost column is 1. */ +static int chars_per_input_tab = 8; + +/* (-i) True means convert spaces to tabs on output. */ +static bool tabify_output = false; + +/* (-i) The output tab character. */ +static char output_tab_char = '\t'; + +/* (-i) The width of the output tab. */ +static int chars_per_output_tab = 8; + +/* Keeps track of pending white space. When we hit a nonspace + character after some whitespace, we print whitespace, tabbing + if necessary to get to output_position + spaces_not_printed. */ +static int spaces_not_printed; + +/* (-o) Number of spaces in the left margin (tabs used when possible). */ +static int chars_per_margin = 0; + +/* Position where the next character will fall. + Leftmost position is 0 + chars_per_margin. + Rightmost position is chars_per_margin + chars_per_line - 1. + This is important for converting spaces to tabs on output. */ +static int output_position; + +/* Horizontal position relative to the current file. + (output_position depends on where we are on the page; + input_position depends on where we are in the file.) + Important for converting tabs to spaces on input. */ +static int input_position; + +/* True if there were any failed opens so we can exit with nonzero + status. */ +static bool failed_opens = false; + +/* The number of spaces taken up if we print a tab character with width + c_ from position h_. */ +#define TAB_WIDTH(c_, h_) ((c_) - ((h_) % (c_))) + +/* The horizontal position we'll be at after printing a tab character + of width c_ from the position h_. */ +#define POS_AFTER_TAB(c_, h_) ((h_) + TAB_WIDTH (c_, h_)) + +/* (-NNN) Number of columns of text to print. */ +static int columns = 1; + +/* (+NNN:MMM) Page numbers on which to begin and stop printing. + first_page_number = 0 will be used to check input only. */ +static uintmax_t first_page_number = 0; +static uintmax_t last_page_number = UINTMAX_MAX; + +/* Number of files open (not closed, not on hold). */ +static int files_ready_to_read = 0; + +/* Current page number. Displayed in header. */ +static uintmax_t page_number; + +/* Current line number. Displayed when -n flag is specified. + + When printing files in parallel (-m flag), line numbering is as follows: + 1 foo goo moo + 2 hoo too zoo + + When printing files across (-a flag), ... + 1 foo 2 moo 3 goo + 4 hoo 5 too 6 zoo + + Otherwise, line numbering is as follows: + 1 foo 3 goo 5 too + 2 moo 4 hoo 6 zoo */ +static int line_number; + +/* With line_number overflow, we use power_10 to cut off the higher-order + digits of the line_number */ +static int power_10; + +/* (-n) True means lines should be preceded by numbers. */ +static bool numbered_lines = false; + +/* (-n) Character which follows each line number. */ +static char number_separator = '\t'; + +/* (-n) line counting starts with 1st line of input file (not with 1st + line of 1st page printed). */ +static int line_count = 1; + +/* (-n) True means counting of skipped lines starts with 1st line of + input file. False means -N option is used in addition, counting of + skipped lines not required. */ +static bool skip_count = true; + +/* (-N) Counting starts with start_line_number = NUMBER at 1st line of + first page printed, usually not 1st page of input file. */ +static int start_line_num = 1; + +/* (-n) Width in characters of a line number. */ +static int chars_per_number = 5; + +/* Used when widening the first column to accommodate numbers -- only + needed when printing files in parallel. Includes width of both the + number and the number_separator. */ +static int number_width; + +/* Buffer sprintf uses to format a line number. */ +static char *number_buff; + +/* (-v) True means unprintable characters are printed as escape sequences. + control-g becomes \007. */ +static bool use_esc_sequence = false; + +/* (-c) True means unprintable characters are printed as control prefixes. + control-g becomes ^G. */ +static bool use_cntrl_prefix = false; + +/* (-d) True means output is double spaced. */ +static bool double_space = false; + +/* Number of files opened initially in init_files. Should be 1 + unless we're printing multiple files in parallel. */ +static int total_files = 0; + +/* (-r) True means don't complain if we can't open a file. */ +static bool ignore_failed_opens = false; + +/* (-S) True means we separate columns with a specified string. + -S option does not affect line truncation nor column alignment. */ +static bool use_col_separator = false; + +/* String used to separate columns if the -S option has been specified. + Default without -S but together with one of the column options + -a|COLUMN|-m is a `space' and with the -J option a `tab'. */ +static char *col_sep_string = ""; +static int col_sep_length = 0; +static char *column_separator = " "; +static char *line_separator = "\t"; + +/* Number of separator characters waiting to be printed as soon as we + know that we have any input remaining to be printed. */ +static int separators_not_printed; + +/* Position we need to pad to, as soon as we know that we have input + remaining to be printed. */ +static int padding_not_printed; + +/* True means we should pad the end of the page. Remains false until we + know we have a page to print. */ +static bool pad_vertically; + +/* (-h) String of characters used in place of the filename in the header. */ +static char *custom_header; + +/* (-D) Date format for the header. */ +static char const *date_format; + +/* Date and file name for the header. */ +static char *date_text; +static char const *file_text; + +/* Output columns available, not counting the date and file name. */ +static int header_width_available; + +static char *clump_buff; + +/* True means we read the line no. lines_per_body in skip_read + called by skip_to_page. That variable controls the coincidence of a + "FF set by hand" and "full_page_printed", see above the definition of + structure COLUMN. */ +static bool last_line = false; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + COLUMNS_OPTION = CHAR_MAX + 1, + PAGES_OPTION +}; + +static char const short_options[] = + "-0123456789D:FJN:S::TW:abcde::fh:i::l:mn::o:rs::tvw:"; + +static struct option const long_options[] = +{ + {"pages", required_argument, NULL, PAGES_OPTION}, + {"columns", required_argument, NULL, COLUMNS_OPTION}, + {"across", no_argument, NULL, 'a'}, + {"show-control-chars", no_argument, NULL, 'c'}, + {"double-space", no_argument, NULL, 'd'}, + {"date-format", required_argument, NULL, 'D'}, + {"expand-tabs", optional_argument, NULL, 'e'}, + {"form-feed", no_argument, NULL, 'f'}, + {"header", required_argument, NULL, 'h'}, + {"output-tabs", optional_argument, NULL, 'i'}, + {"join-lines", no_argument, NULL, 'J'}, + {"length", required_argument, NULL, 'l'}, + {"merge", no_argument, NULL, 'm'}, + {"number-lines", optional_argument, NULL, 'n'}, + {"first-line-number", required_argument, NULL, 'N'}, + {"indent", required_argument, NULL, 'o'}, + {"no-file-warnings", no_argument, NULL, 'r'}, + {"separator", optional_argument, NULL, 's'}, + {"sep-string", optional_argument, NULL, 'S'}, + {"omit-header", no_argument, NULL, 't'}, + {"omit-pagination", no_argument, NULL, 'T'}, + {"show-nonprinting", no_argument, NULL, 'v'}, + {"width", required_argument, NULL, 'w'}, + {"page-width", required_argument, NULL, 'W'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Return the number of columns that have either an open file or + stored lines. */ + +static int +cols_ready_to_print (void) +{ + COLUMN *q; + int i; + int n; + + n = 0; + for (q = column_vector, i = 0; i < columns; ++q, ++i) + if (q->status == OPEN || + q->status == FF_FOUND || /* With -b: To print a header only */ + (storing_columns && q->lines_stored > 0 && q->lines_to_print > 0)) + ++n; + return n; +} + +/* Estimate first_ / last_page_number + using option +FIRST_PAGE:LAST_PAGE */ + +static bool +first_last_page (char const *pages) +{ + char *p; + uintmax_t first; + uintmax_t last = UINTMAX_MAX; + strtol_error err = xstrtoumax (pages, &p, 10, &first, ""); + if (err != LONGINT_OK && err != LONGINT_INVALID_SUFFIX_CHAR) + _STRTOL_ERROR (EXIT_FAILURE, pages, _("page range"), err); + + if (p == pages || !first) + return false; + + if (*p == ':') + { + char const *p1 = p + 1; + err = xstrtoumax (p1, &p, 10, &last, ""); + if (err != LONGINT_OK) + _STRTOL_ERROR (EXIT_FAILURE, pages, _("page range"), err); + if (p1 == p || last < first) + return false; + } + + if (*p) + return false; + + first_page_number = first; + last_page_number = last; + return true; +} + +/* Parse column count string S, and if it's valid (1 or larger and + within range of the type of `columns') set the global variables + columns and explicit_columns and return true. + Otherwise, exit with a diagnostic. */ +static void +parse_column_count (char const *s) +{ + long int tmp_long; + if (xstrtol (s, NULL, 10, &tmp_long, "") != LONGINT_OK + || !(1 <= tmp_long && tmp_long <= INT_MAX)) + error (EXIT_FAILURE, 0, + _("invalid number of columns: %s"), quote (s)); + + columns = tmp_long; + explicit_columns = true; +} + +/* Estimate length of col_sep_string with option -S. */ + +static void +separator_string (const char *optarg_S) +{ + col_sep_length = (int) strlen (optarg_S); + col_sep_string = xmalloc (col_sep_length + 1); + strcpy (col_sep_string, optarg_S); +} + +int +main (int argc, char **argv) +{ + int c; + int n_files; + bool old_options = false; + bool old_w = false; + bool old_s = false; + char **file_names; + + /* Accumulate the digits of old-style options like -99. */ + char *column_count_string = NULL; + size_t n_digits = 0; + size_t n_alloc = 0; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + n_files = 0; + file_names = (argc > 1 + ? xmalloc ((argc - 1) * sizeof (char *)) + : NULL); + + while ((c = getopt_long (argc, argv, short_options, long_options, NULL)) + != -1) + { + if (ISDIGIT (c)) + { + /* Accumulate column-count digits specified via old-style options. */ + if (n_digits + 1 >= n_alloc) + column_count_string + = X2REALLOC (column_count_string, &n_alloc); + column_count_string[n_digits++] = c; + column_count_string[n_digits] = '\0'; + continue; + } + + n_digits = 0; + + switch (c) + { + case 1: /* Non-option argument. */ + /* long option --page dominates old `+FIRST_PAGE ...'. */ + if (! (first_page_number == 0 + && *optarg == '+' && first_last_page (optarg + 1))) + file_names[n_files++] = optarg; + break; + + case PAGES_OPTION: /* --pages=FIRST_PAGE[:LAST_PAGE] */ + { /* dominates old opt +... */ + if (! optarg) + error (EXIT_FAILURE, 0, + _("`--pages=FIRST_PAGE[:LAST_PAGE]' missing argument")); + else if (! first_last_page (optarg)) + error (EXIT_FAILURE, 0, _("Invalid page range %s"), + quote (optarg)); + break; + } + + case COLUMNS_OPTION: /* --columns=COLUMN */ + { + parse_column_count (optarg); + + /* If there was a prior column count specified via the + short-named option syntax, e.g., -9, ensure that this + long-name-specified value overrides it. */ + free (column_count_string); + column_count_string = NULL; + n_alloc = 0; + break; + } + + case 'a': + print_across_flag = true; + storing_columns = false; + break; + case 'b': + balance_columns = true; + break; + case 'c': + use_cntrl_prefix = true; + break; + case 'd': + double_space = true; + break; + case 'D': + date_format = optarg; + break; + case 'e': + if (optarg) + getoptarg (optarg, 'e', &input_tab_char, + &chars_per_input_tab); + /* Could check tab width > 0. */ + untabify_input = true; + break; + case 'f': + case 'F': + use_form_feed = true; + break; + case 'h': + custom_header = optarg; + break; + case 'i': + if (optarg) + getoptarg (optarg, 'i', &output_tab_char, + &chars_per_output_tab); + /* Could check tab width > 0. */ + tabify_output = true; + break; + case 'J': + join_lines = true; + break; + case 'l': + { + long int tmp_long; + if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK + || tmp_long <= 0 || tmp_long > INT_MAX) + { + error (EXIT_FAILURE, 0, + _("`-l PAGE_LENGTH' invalid number of lines: %s"), + quote (optarg)); + } + lines_per_page = tmp_long; + break; + } + case 'm': + parallel_files = true; + storing_columns = false; + break; + case 'n': + numbered_lines = true; + if (optarg) + getoptarg (optarg, 'n', &number_separator, + &chars_per_number); + break; + case 'N': + skip_count = false; + { + long int tmp_long; + if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK + || tmp_long > INT_MAX) + { + error (EXIT_FAILURE, 0, + _("`-N NUMBER' invalid starting line number: %s"), + quote (optarg)); + } + start_line_num = tmp_long; + break; + } + case 'o': + { + long int tmp_long; + if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK + || tmp_long < 0 || tmp_long > INT_MAX) + error (EXIT_FAILURE, 0, + _("`-o MARGIN' invalid line offset: %s"), quote (optarg)); + chars_per_margin = tmp_long; + break; + } + case 'r': + ignore_failed_opens = true; + break; + case 's': + old_options = true; + old_s = true; + if (!use_col_separator && optarg) + separator_string (optarg); + break; + case 'S': + old_s = false; + /* Reset an additional input of -s, -S dominates -s */ + col_sep_string = ""; + col_sep_length = 0; + use_col_separator = true; + if (optarg) + separator_string (optarg); + break; + case 't': + extremities = false; + keep_FF = true; + break; + case 'T': + extremities = false; + keep_FF = false; + break; + case 'v': + use_esc_sequence = true; + break; + case 'w': + old_options = true; + old_w = true; + { + long int tmp_long; + if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK + || tmp_long <= 0 || tmp_long > INT_MAX) + error (EXIT_FAILURE, 0, + _("`-w PAGE_WIDTH' invalid number of characters: %s"), + quote (optarg)); + if (!truncate_lines) + chars_per_line = tmp_long; + break; + } + case 'W': + old_w = false; /* dominates -w */ + truncate_lines = true; + { + long int tmp_long; + if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK + || tmp_long <= 0 || tmp_long > INT_MAX) + error (EXIT_FAILURE, 0, + _("`-W PAGE_WIDTH' invalid number of characters: %s"), + quote (optarg)); + chars_per_line = tmp_long; + break; + } + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + break; + } + } + + if (column_count_string) + { + parse_column_count (column_count_string); + free (column_count_string); + } + + if (! date_format) + date_format = (getenv ("POSIXLY_CORRECT") && !hard_locale (LC_TIME) + ? "%b %e %H:%M %Y" + : "%Y-%m-%d %H:%M"); + + /* Now we can set a reasonable initial value: */ + if (first_page_number == 0) + first_page_number = 1; + + if (parallel_files & explicit_columns) + error (EXIT_FAILURE, 0, + _("Cannot specify number of columns when printing in parallel.")); + + if (parallel_files & print_across_flag) + error (EXIT_FAILURE, 0, + _("Cannot specify both printing across and printing in parallel.")); + +/* Translate some old short options to new/long options. + To meet downward compatibility with other UNIX pr utilities + and some POSIX specifications. */ + + if (old_options) + { + if (old_w) + { + if (parallel_files | explicit_columns) + { + /* activate -W */ + truncate_lines = true; + if (old_s) + /* adapt HP-UX and SunOS: -s = no separator; + activate -S */ + use_col_separator = true; + } + else + /* old -w sets width with columns only + activate -J */ + join_lines = true; + } + else if (!use_col_separator) + { + /* No -S option read */ + if (old_s & (parallel_files | explicit_columns)) + { + if (!truncate_lines) + { + /* old -s (without -w and -W) annuls column alignment, + uses fields, activate -J */ + join_lines = true; + if (col_sep_length > 0) + /* activate -S */ + use_col_separator = true; + } + else + /* with -W */ + /* adapt HP-UX and SunOS: -s = no separator; + activate -S */ + use_col_separator = true; + } + } + } + + for (; optind < argc; optind++) + { + file_names[n_files++] = argv[optind]; + } + + if (n_files == 0) + { + /* No file arguments specified; read from standard input. */ + print_files (0, NULL); + } + else + { + if (parallel_files) + print_files (n_files, file_names); + else + { + int i; + for (i = 0; i < n_files; i++) + print_files (1, &file_names[i]); + } + } + + cleanup (); + + if (have_read_stdin && fclose (stdin) == EOF) + error (EXIT_FAILURE, errno, _("standard input")); + if (failed_opens) + exit (EXIT_FAILURE); + exit (EXIT_SUCCESS); +} + +/* Parse options of the form -scNNN. + + Example: -nck, where 'n' is the option, c is the optional number + separator, and k is the optional width of the field used when printing + a number. */ + +static void +getoptarg (char *arg, char switch_char, char *character, int *number) +{ + if (!ISDIGIT (*arg)) + *character = *arg++; + if (*arg) + { + long int tmp_long; + if (xstrtol (arg, NULL, 10, &tmp_long, "") != LONGINT_OK + || tmp_long <= 0 || tmp_long > INT_MAX) + { + error (0, 0, + _("`-%c' extra characters or invalid number in the argument: %s"), + switch_char, quote (arg)); + usage (EXIT_FAILURE); + } + *number = tmp_long; + } +} + +/* Set parameters related to formatting. */ + +static void +init_parameters (int number_of_files) +{ + int chars_used_by_number = 0; + + if (use_form_feed) + { + lines_per_header = 3; + lines_per_footer = 0; + } + + lines_per_body = lines_per_page - lines_per_header - lines_per_footer; + if (lines_per_body <= 0) + { + extremities = false; + keep_FF = true; + } + if (extremities == false) + lines_per_body = lines_per_page; + + if (double_space) + lines_per_body = lines_per_body / 2; + + /* If input is stdin, cannot print parallel files. BSD dumps core + on this. */ + if (number_of_files == 0) + parallel_files = false; + + if (parallel_files) + columns = number_of_files; + + /* One file, multi columns down: -b option is set to get a consistent + formulation with "FF set by hand" in input files. */ + if (storing_columns) + balance_columns = true; + + /* Tabification is assumed for multiple columns. */ + if (columns > 1) + { + if (!use_col_separator) + { + /* Use default separator */ + if (join_lines) + col_sep_string = line_separator; + else + col_sep_string = column_separator; + + col_sep_length = 1; + use_col_separator = true; + } + /* It's rather pointless to define a TAB separator with column + alignment */ + else if (!join_lines && *col_sep_string == '\t') + col_sep_string = column_separator; + + truncate_lines = true; + tabify_output = true; + } + else + storing_columns = false; + + /* -J dominates -w in any case */ + if (join_lines) + truncate_lines = false; + + if (numbered_lines) + { + int tmp_i; + int chars_per_default_tab = 8; + + line_count = start_line_num; + + /* To allow input tab-expansion (-e sensitive) use: + if (number_separator == input_tab_char) + number_width = chars_per_number + + TAB_WIDTH (chars_per_input_tab, chars_per_number); */ + + /* Estimate chars_per_text without any margin and keep it constant. */ + if (number_separator == '\t') + number_width = chars_per_number + + TAB_WIDTH (chars_per_default_tab, chars_per_number); + else + number_width = chars_per_number + 1; + + /* The number is part of the column width unless we are + printing files in parallel. */ + if (parallel_files) + chars_used_by_number = number_width; + + /* We use power_10 to cut off the higher-order digits of the + line_number in function add_line_number */ + tmp_i = chars_per_number; + for (power_10 = 1; tmp_i > 0; --tmp_i) + power_10 = 10 * power_10; + } + + chars_per_column = (chars_per_line - chars_used_by_number - + (columns - 1) * col_sep_length) / columns; + + if (chars_per_column < 1) + error (EXIT_FAILURE, 0, _("page width too narrow")); + + if (numbered_lines) + { + free (number_buff); + number_buff = xmalloc (2 * chars_per_number); + } + + /* Pick the maximum between the tab width and the width of an + escape sequence. + The width of an escape sequence (4) isn't the lower limit any longer. + We've to use 8 as the lower limit, if we use chars_per_default_tab = 8 + to expand a tab which is not an input_tab-char. */ + free (clump_buff); + clump_buff = xmalloc (MAX (8, chars_per_input_tab)); +} + +/* Open the necessary files, + maintaining a COLUMN structure for each column. + + With multiple files, each column p has a different p->fp. + With single files, each column p has the same p->fp. + Return false if (number_of_files > 0) and no files can be opened, + true otherwise. + + With each column/file p, p->full_page_printed is initialized, + see also open_file. */ + +static bool +init_fps (int number_of_files, char **av) +{ + int i, files_left; + COLUMN *p; + FILE *firstfp; + char const *firstname; + + total_files = 0; + + free (column_vector); + column_vector = xnmalloc (columns, sizeof (COLUMN)); + + if (parallel_files) + { + files_left = number_of_files; + for (p = column_vector; files_left--; ++p, ++av) + { + if (! open_file (*av, p)) + { + --p; + --columns; + } + } + if (columns == 0) + return false; + init_header ("", -1); + } + else + { + p = column_vector; + if (number_of_files > 0) + { + if (! open_file (*av, p)) + return false; + init_header (*av, fileno (p->fp)); + p->lines_stored = 0; + } + else + { + p->name = _("standard input"); + p->fp = stdin; + have_read_stdin = true; + p->status = OPEN; + p->full_page_printed = false; + ++total_files; + init_header ("", -1); + p->lines_stored = 0; + } + + firstname = p->name; + firstfp = p->fp; + for (i = columns - 1, ++p; i; --i, ++p) + { + p->name = firstname; + p->fp = firstfp; + p->status = OPEN; + p->full_page_printed = false; + p->lines_stored = 0; + } + } + files_ready_to_read = total_files; + return true; +} + +/* Determine print_func and char_func, the functions + used by each column for printing and/or storing. + + Determine the horizontal position desired when we begin + printing a column (p->start_position). */ + +static void +init_funcs (void) +{ + int i, h, h_next; + COLUMN *p; + + h = chars_per_margin; + + if (!truncate_lines) + h_next = ANYWHERE; + else + { + /* When numbering lines of parallel files, we enlarge the + first column to accomodate the number. Looks better than + the Sys V approach. */ + if (parallel_files & numbered_lines) + h_next = h + chars_per_column + number_width; + else + h_next = h + chars_per_column; + } + + /* Enlarge p->start_position of first column to use the same form of + padding_not_printed with all columns. */ + h = h + col_sep_length; + + /* This loop takes care of all but the rightmost column. */ + + for (p = column_vector, i = 1; i < columns; ++p, ++i) + { + if (storing_columns) /* One file, multi columns down. */ + { + p->char_func = store_char; + p->print_func = print_stored; + } + else + /* One file, multi columns across; or parallel files. */ + { + p->char_func = print_char; + p->print_func = read_line; + } + + /* Number only the first column when printing files in + parallel. */ + p->numbered = numbered_lines && (!parallel_files || i == 1); + p->start_position = h; + + /* If we don't truncate lines, all start_positions are + ANYWHERE, except the first column's start_position when + using a margin. */ + + if (!truncate_lines) + { + h = ANYWHERE; + h_next = ANYWHERE; + } + else + { + h = h_next + col_sep_length; + h_next = h + chars_per_column; + } + } + + /* The rightmost column. + + Doesn't need to be stored unless we intend to balance + columns on the last page. */ + if (storing_columns & balance_columns) + { + p->char_func = store_char; + p->print_func = print_stored; + } + else + { + p->char_func = print_char; + p->print_func = read_line; + } + + p->numbered = numbered_lines && (!parallel_files || i == 1); + p->start_position = h; +} + +/* Open a file. Return true if successful. + + With each file p, p->full_page_printed is initialized, + see also init_fps. */ + +static bool +open_file (char *name, COLUMN *p) +{ + if (STREQ (name, "-")) + { + p->name = _("standard input"); + p->fp = stdin; + have_read_stdin = true; + } + else + { + p->name = name; + p->fp = fopen (name, "r"); + } + if (p->fp == NULL) + { + failed_opens = true; + if (!ignore_failed_opens) + error (0, errno, "%s", name); + return false; + } + p->status = OPEN; + p->full_page_printed = false; + ++total_files; + return true; +} + +/* Close the file in P. + + If we aren't dealing with multiple files in parallel, we change + the status of all columns in the column list to reflect the close. */ + +static void +close_file (COLUMN *p) +{ + COLUMN *q; + int i; + + if (p->status == CLOSED) + return; + if (ferror (p->fp)) + error (EXIT_FAILURE, errno, "%s", p->name); + if (fileno (p->fp) != STDIN_FILENO && fclose (p->fp) != 0) + error (EXIT_FAILURE, errno, "%s", p->name); + + if (!parallel_files) + { + for (q = column_vector, i = columns; i; ++q, --i) + { + q->status = CLOSED; + if (q->lines_stored == 0) + { + q->lines_to_print = 0; + } + } + } + else + { + p->status = CLOSED; + p->lines_to_print = 0; + } + + --files_ready_to_read; +} + +/* Put a file on hold until we start a new page, + since we've hit a form feed. + + If we aren't dealing with parallel files, we must change the + status of all columns in the column list. */ + +static void +hold_file (COLUMN *p) +{ + COLUMN *q; + int i; + + if (!parallel_files) + for (q = column_vector, i = columns; i; ++q, --i) + { + if (storing_columns) + q->status = FF_FOUND; + else + q->status = ON_HOLD; + } + else + p->status = ON_HOLD; + + p->lines_to_print = 0; + --files_ready_to_read; +} + +/* Undo hold_file -- go through the column list and change any + ON_HOLD columns to OPEN. Used at the end of each page. */ + +static void +reset_status (void) +{ + int i = columns; + COLUMN *p; + + for (p = column_vector; i; --i, ++p) + if (p->status == ON_HOLD) + { + p->status = OPEN; + files_ready_to_read++; + } + + if (storing_columns) + { + if (column_vector->status == CLOSED) + /* We use the info to output an error message in skip_to_page. */ + files_ready_to_read = 0; + else + files_ready_to_read = 1; + } +} + +/* Print a single file, or multiple files in parallel. + + Set up the list of columns, opening the necessary files. + Allocate space for storing columns, if necessary. + Skip to first_page_number, if user has asked to skip leading pages. + Determine which functions are appropriate to store/print lines + in each column. + Print the file(s). */ + +static void +print_files (int number_of_files, char **av) +{ + init_parameters (number_of_files); + if (! init_fps (number_of_files, av)) + return; + if (storing_columns) + init_store_cols (); + + if (first_page_number > 1) + { + if (!skip_to_page (first_page_number)) + return; + else + page_number = first_page_number; + } + else + page_number = 1; + + init_funcs (); + + line_number = line_count; + while (print_page ()) + ; +} + +/* Initialize header information. + If DESC is non-negative, it is a file descriptor open to + FILENAME for reading. */ + +static void +init_header (char const *filename, int desc) +{ + char *buf = NULL; + struct stat st; + struct timespec t; + int ns; + struct tm *tm; + + /* If parallel files or standard input, use current date. */ + if (STREQ (filename, "-")) + desc = -1; + if (0 <= desc && fstat (desc, &st) == 0) + t = get_stat_mtime (&st); + else + { + static struct timespec timespec; + if (! timespec.tv_sec) + gettime (×pec); + t = timespec; + } + + ns = t.tv_nsec; + tm = localtime (&t.tv_sec); + if (tm == NULL) + { + buf = xmalloc (INT_BUFSIZE_BOUND (long int) + + MAX (10, INT_BUFSIZE_BOUND (int))); + sprintf (buf, "%ld.%09d", (long int) t.tv_sec, ns); + } + else + { + size_t bufsize = nstrftime (NULL, SIZE_MAX, date_format, tm, 0, ns) + 1; + buf = xmalloc (bufsize); + nstrftime (buf, bufsize, date_format, tm, 0, ns); + } + + free (date_text); + date_text = buf; + file_text = custom_header ? custom_header : desc < 0 ? "" : filename; + header_width_available = (chars_per_line + - mbswidth (date_text, 0) + - mbswidth (file_text, 0)); +} + +/* Set things up for printing a page + + Scan through the columns ... + Determine which are ready to print + (i.e., which have lines stored or open files) + Set p->lines_to_print appropriately + (to p->lines_stored if we're storing, or lines_per_body + if we're reading straight from the file) + Keep track of this total so we know when to stop printing */ + +static void +init_page (void) +{ + int j; + COLUMN *p; + + if (storing_columns) + { + store_columns (); + for (j = columns - 1, p = column_vector; j; --j, ++p) + { + p->lines_to_print = p->lines_stored; + } + + /* Last column. */ + if (balance_columns) + { + p->lines_to_print = p->lines_stored; + } + /* Since we're not balancing columns, we don't need to store + the rightmost column. Read it straight from the file. */ + else + { + if (p->status == OPEN) + { + p->lines_to_print = lines_per_body; + } + else + p->lines_to_print = 0; + } + } + else + for (j = columns, p = column_vector; j; --j, ++p) + if (p->status == OPEN) + { + p->lines_to_print = lines_per_body; + } + else + p->lines_to_print = 0; +} + +/* Align empty columns and print separators. + Empty columns will be formed by files with status ON_HOLD or CLOSED + when printing multiple files in parallel. */ + +static void +align_column (COLUMN *p) +{ + padding_not_printed = p->start_position; + if (padding_not_printed - col_sep_length > 0) + { + pad_across_to (padding_not_printed - col_sep_length); + padding_not_printed = ANYWHERE; + } + + if (use_col_separator) + print_sep_string (); + + if (p->numbered) + add_line_number (p); +} + +/* Print one page. + + As long as there are lines left on the page and columns ready to print, + Scan across the column list + if the column has stored lines or the file is open + pad to the appropriate spot + print the column + pad the remainder of the page with \n or \f as requested + reset the status of all files -- any files which where on hold because + of formfeeds are now put back into the lineup. */ + +static bool +print_page (void) +{ + int j; + int lines_left_on_page; + COLUMN *p; + + /* Used as an accumulator (with | operator) of successive values of + pad_vertically. The trick is to set pad_vertically + to false before each run through the inner loop, then after that + loop, it tells us whether a line was actually printed (whether a + newline needs to be output -- or two for double spacing). But those + values have to be accumulated (in pv) so we can invoke pad_down + properly after the outer loop completes. */ + bool pv; + + init_page (); + + if (cols_ready_to_print () == 0) + return false; + + if (extremities) + print_a_header = true; + + /* Don't pad unless we know a page was printed. */ + pad_vertically = false; + pv = false; + + lines_left_on_page = lines_per_body; + if (double_space) + lines_left_on_page *= 2; + + while (lines_left_on_page > 0 && cols_ready_to_print () > 0) + { + output_position = 0; + spaces_not_printed = 0; + separators_not_printed = 0; + pad_vertically = false; + align_empty_cols = false; + empty_line = true; + + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + { + input_position = 0; + if (p->lines_to_print > 0 || p->status == FF_FOUND) + { + FF_only = false; + padding_not_printed = p->start_position; + if (!(p->print_func) (p)) + read_rest_of_line (p); + pv |= pad_vertically; + + --p->lines_to_print; + if (p->lines_to_print <= 0) + { + if (cols_ready_to_print () <= 0) + break; + } + + /* File p changed its status to ON_HOLD or CLOSED */ + if (parallel_files && p->status != OPEN) + { + if (empty_line) + align_empty_cols = true; + else if (p->status == CLOSED || + (p->status == ON_HOLD && FF_only)) + align_column (p); + } + } + else if (parallel_files) + { + /* File status ON_HOLD or CLOSED */ + if (empty_line) + align_empty_cols = true; + else + align_column (p); + } + + /* We need it also with an empty column */ + if (use_col_separator) + ++separators_not_printed; + } + + if (pad_vertically) + { + putchar ('\n'); + --lines_left_on_page; + } + + if (cols_ready_to_print () <= 0 && !extremities) + break; + + if (double_space & pv) + { + putchar ('\n'); + --lines_left_on_page; + } + } + + if (lines_left_on_page == 0) + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + if (p->status == OPEN) + p->full_page_printed = true; + + pad_vertically = pv; + + if (pad_vertically & extremities) + pad_down (lines_left_on_page + lines_per_footer); + else if (keep_FF & print_a_FF) + { + putchar ('\f'); + print_a_FF = false; + } + + if (last_page_number < page_number) + return false; /* Stop printing with LAST_PAGE */ + + reset_status (); /* Change ON_HOLD to OPEN. */ + + return true; /* More pages to go. */ +} + +/* Allocate space for storing columns. + + This is necessary when printing multiple columns from a single file. + Lines are stored consecutively in buff, separated by '\0'. + + The following doesn't apply any longer - any tuning possible? + (We can't use a fixed offset since with the '-s' flag lines aren't + truncated.) + + We maintain a list (line_vector) of pointers to the beginnings + of lines in buff. We allocate one more than the number of lines + because the last entry tells us the index of the last character, + which we need to know in order to print the last line in buff. */ + +static void +init_store_cols (void) +{ + int total_lines = lines_per_body * columns; + int chars_if_truncate = total_lines * (chars_per_column + 1); + + free (line_vector); + /* FIXME: here's where it was allocated. */ + line_vector = xmalloc ((total_lines + 1) * sizeof (int *)); + + free (end_vector); + end_vector = xmalloc (total_lines * sizeof (int *)); + + free (buff); + buff_allocated = (use_col_separator + ? 2 * chars_if_truncate + : chars_if_truncate); /* Tune this. */ + buff = xmalloc (buff_allocated); +} + +/* Store all but the rightmost column. + (Used when printing a single file in multiple downward columns) + + For each column + set p->current_line to be the index in line_vector of the + first line in the column + For each line in the column + store the line in buff + add to line_vector the index of the line's first char + buff_start is the index in buff of the first character in the + current line. */ + +static void +store_columns (void) +{ + int i, j; + int line = 0; + int buff_start; + int last_col; /* The rightmost column which will be saved in buff */ + COLUMN *p; + + buff_current = 0; + buff_start = 0; + + if (balance_columns) + last_col = columns; + else + last_col = columns - 1; + + for (i = 1, p = column_vector; i <= last_col; ++i, ++p) + p->lines_stored = 0; + + for (i = 1, p = column_vector; i <= last_col && files_ready_to_read; + ++i, ++p) + { + p->current_line = line; + for (j = lines_per_body; j && files_ready_to_read; --j) + + if (p->status == OPEN) /* Redundant. Clean up. */ + { + input_position = 0; + + if (!read_line (p)) + read_rest_of_line (p); + + if (p->status == OPEN + || buff_start != buff_current) + { + ++p->lines_stored; + line_vector[line] = buff_start; + end_vector[line++] = input_position; + buff_start = buff_current; + } + } + } + + /* Keep track of the location of the last char in buff. */ + line_vector[line] = buff_start; + + if (balance_columns) + balance (line); +} + +static void +balance (int total_stored) +{ + COLUMN *p; + int i, lines; + int first_line = 0; + + for (i = 1, p = column_vector; i <= columns; ++i, ++p) + { + lines = total_stored / columns; + if (i <= total_stored % columns) + ++lines; + + p->lines_stored = lines; + p->current_line = first_line; + + first_line += lines; + } +} + +/* Store a character in the buffer. */ + +static void +store_char (char c) +{ + if (buff_current >= buff_allocated) + { + /* May be too generous. */ + buff = X2REALLOC (buff, &buff_allocated); + } + buff[buff_current++] = c; +} + +static void +add_line_number (COLUMN *p) +{ + int i; + char *s; + int left_cut; + + /* Cutting off the higher-order digits is more informative than + lower-order cut off*/ + if (line_number < power_10) + sprintf (number_buff, "%*d", chars_per_number, line_number); + else + { + left_cut = line_number % power_10; + sprintf (number_buff, "%0*d", chars_per_number, left_cut); + } + line_number++; + s = number_buff; + for (i = chars_per_number; i > 0; i--) + (p->char_func) (*s++); + + if (columns > 1) + { + /* Tabification is assumed for multiple columns, also for n-separators, + but `default n-separator = TAB' hasn't been given priority over + equal column_width also specified by POSIX. */ + if (number_separator == '\t') + { + i = number_width - chars_per_number; + while (i-- > 0) + (p->char_func) (' '); + } + else + (p->char_func) (number_separator); + } + else + /* To comply with POSIX, we avoid any expansion of default TAB + separator with a single column output. No column_width requirement + has to be considered. */ + { + (p->char_func) (number_separator); + if (number_separator == '\t') + output_position = POS_AFTER_TAB (chars_per_output_tab, + output_position); + } + + if (truncate_lines & !parallel_files) + input_position += number_width; +} + +/* Print (or store) padding until the current horizontal position + is position. */ + +static void +pad_across_to (int position) +{ + int h = output_position; + + if (tabify_output) + spaces_not_printed = position - output_position; + else + { + while (++h <= position) + putchar (' '); + output_position = position; + } +} + +/* Pad to the bottom of the page. + + If the user has requested a formfeed, use one. + Otherwise, use newlines. */ + +static void +pad_down (int lines) +{ + int i; + + if (use_form_feed) + putchar ('\f'); + else + for (i = lines; i; --i) + putchar ('\n'); +} + +/* Read the rest of the line. + + Read from the current column's file until an end of line is + hit. Used when we've truncated a line and we no longer need + to print or store its characters. */ + +static void +read_rest_of_line (COLUMN *p) +{ + int c; + FILE *f = p->fp; + + while ((c = getc (f)) != '\n') + { + if (c == '\f') + { + if ((c = getc (f)) != '\n') + ungetc (c, f); + if (keep_FF) + print_a_FF = true; + hold_file (p); + break; + } + else if (c == EOF) + { + close_file (p); + break; + } + } +} + +/* Read a line with skip_to_page. + + Read from the current column's file until an end of line is + hit. Used when we read full lines to skip pages. + With skip_to_page we have to check for FF-coincidence which is done + in function read_line otherwise. + Count lines of skipped pages to find the line number of 1st page + printed relative to 1st line of input file (start_line_num). */ + +static void +skip_read (COLUMN *p, int column_number) +{ + int c; + FILE *f = p->fp; + int i; + bool single_ff = false; + COLUMN *q; + + /* Read 1st character in a line or any character succeeding a FF */ + if ((c = getc (f)) == '\f' && p->full_page_printed) + /* A FF-coincidence with a previous full_page_printed. + To avoid an additional empty page, eliminate the FF */ + if ((c = getc (f)) == '\n') + c = getc (f); + + p->full_page_printed = false; + + /* 1st character a FF means a single FF without any printable + characters. Don't count it as a line with -n option. */ + if (c == '\f') + single_ff = true; + + /* Preparing for a FF-coincidence: Maybe we finish that page + without a FF found */ + if (last_line) + p->full_page_printed = true; + + while (c != '\n') + { + if (c == '\f') + { + /* No FF-coincidence possible, + no catching up of a FF-coincidence with next page */ + if (last_line) + { + if (!parallel_files) + for (q = column_vector, i = columns; i; ++q, --i) + q->full_page_printed = false; + else + p->full_page_printed = false; + } + + if ((c = getc (f)) != '\n') + ungetc (c, f); + hold_file (p); + break; + } + else if (c == EOF) + { + close_file (p); + break; + } + c = getc (f); + } + + if (skip_count) + if ((!parallel_files || column_number == 1) && !single_ff) + ++line_count; +} + +/* If we're tabifying output, + + When print_char encounters white space it keeps track + of our desired horizontal position and delays printing + until this function is called. */ + +static void +print_white_space (void) +{ + int h_new; + int h_old = output_position; + int goal = h_old + spaces_not_printed; + + while (goal - h_old > 1 + && (h_new = POS_AFTER_TAB (chars_per_output_tab, h_old)) <= goal) + { + putchar (output_tab_char); + h_old = h_new; + } + while (++h_old <= goal) + putchar (' '); + + output_position = goal; + spaces_not_printed = 0; +} + +/* Print column separators. + + We keep a count until we know that we'll be printing a line, + then print_sep_string() is called. */ + +static void +print_sep_string (void) +{ + char *s; + int l = col_sep_length; + + s = col_sep_string; + + if (separators_not_printed <= 0) + { + /* We'll be starting a line with chars_per_margin, anything else? */ + if (spaces_not_printed > 0) + print_white_space (); + } + else + { + for (; separators_not_printed > 0; --separators_not_printed) + { + while (l-- > 0) + { + /* 3 types of sep_strings: spaces only, spaces and chars, + chars only */ + if (*s == ' ') + { + /* We're tabifying output; consecutive spaces in + sep_string may have to be converted to tabs */ + s++; + ++spaces_not_printed; + } + else + { + if (spaces_not_printed > 0) + print_white_space (); + putchar (*s++); + ++output_position; + } + } + /* sep_string ends with some spaces */ + if (spaces_not_printed > 0) + print_white_space (); + } + } +} + +/* Print (or store, depending on p->char_func) a clump of N + characters. */ + +static void +print_clump (COLUMN *p, int n, char *clump) +{ + while (n--) + (p->char_func) (*clump++); +} + +/* Print a character. + + Update the following comment: process-char hasn't been used any + longer. + If we're tabifying, all tabs have been converted to spaces by + process_char(). Keep a count of consecutive spaces, and when + a nonspace is encountered, call print_white_space() to print the + required number of tabs and spaces. */ + +static void +print_char (char c) +{ + if (tabify_output) + { + if (c == ' ') + { + ++spaces_not_printed; + return; + } + else if (spaces_not_printed > 0) + print_white_space (); + + /* Nonprintables are assumed to have width 0, except '\b'. */ + if (! isprint (to_uchar (c))) + { + if (c == '\b') + --output_position; + } + else + ++output_position; + } + putchar (c); +} + +/* Skip to page PAGE before printing. + PAGE may be larger than total number of pages. */ + +static bool +skip_to_page (uintmax_t page) +{ + uintmax_t n; + int i; + int j; + COLUMN *p; + + for (n = 1; n < page; ++n) + { + for (i = 1; i < lines_per_body; ++i) + { + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + if (p->status == OPEN) + skip_read (p, j); + } + last_line = true; + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + if (p->status == OPEN) + skip_read (p, j); + + if (storing_columns) /* change FF_FOUND to ON_HOLD */ + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + if (p->status != CLOSED) + p->status = ON_HOLD; + + reset_status (); + last_line = false; + + if (files_ready_to_read < 1) + { + /* It's very helpful, normally the total number of pages is + not known in advance. */ + error (0, 0, + _("starting page number %"PRIuMAX + " exceeds page count %"PRIuMAX), + page, n); + break; + } + } + return files_ready_to_read > 0; +} + +/* Print a header. + + Formfeeds are assumed to use up two lines at the beginning of + the page. */ + +static void +print_header (void) +{ + char page_text[256 + INT_STRLEN_BOUND (page_number)]; + int available_width; + int lhs_spaces; + int rhs_spaces; + + if (!use_form_feed) + printf ("\n\n"); + + output_position = 0; + pad_across_to (chars_per_margin); + print_white_space (); + + if (page_number == 0) + error (EXIT_FAILURE, 0, _("Page number overflow")); + + /* The translator must ensure that formatting the translation of + "Page %"PRIuMAX does not generate more than (sizeof page_text - 1) + bytes. */ + sprintf (page_text, _("Page %"PRIuMAX), page_number++); + available_width = header_width_available - mbswidth (page_text, 0); + available_width = MAX (0, available_width); + lhs_spaces = available_width >> 1; + rhs_spaces = available_width - lhs_spaces; + + printf ("%s%*s%s%*s%s\n\n\n", + date_text, lhs_spaces, " ", file_text, rhs_spaces, " ", page_text); + + print_a_header = false; + output_position = 0; +} + +/* Print (or store, if p->char_func is store_char()) a line. + + Read a character to determine whether we have a line or not. + (We may hit EOF, \n, or \f) + + Once we know we have a line, + set pad_vertically = true, meaning it's safe + to pad down at the end of the page, since we do have a page. + print a header if needed. + pad across to padding_not_printed if needed. + print any separators which need to be printed. + print a line number if it needs to be printed. + + Print the clump which corresponds to the first character. + + Enter a loop and keep printing until an end of line condition + exists, or until we exceed chars_per_column. + + Return false if we exceed chars_per_column before reading + an end of line character, true otherwise. */ + +static bool +read_line (COLUMN *p) +{ + int c; + int chars IF_LINT (= 0); + int last_input_position; + int j, k; + COLUMN *q; + + /* read 1st character in each line or any character succeeding a FF: */ + c = getc (p->fp); + + last_input_position = input_position; + + if (c == '\f' && p->full_page_printed) + if ((c = getc (p->fp)) == '\n') + c = getc (p->fp); + p->full_page_printed = false; + + switch (c) + { + case '\f': + if ((c = getc (p->fp)) != '\n') + ungetc (c, p->fp); + FF_only = true; + if (print_a_header & !storing_columns) + { + pad_vertically = true; + print_header (); + } + else if (keep_FF) + print_a_FF = true; + hold_file (p); + return true; + case EOF: + close_file (p); + return true; + case '\n': + break; + default: + chars = char_to_clump (c); + } + + if (truncate_lines && input_position > chars_per_column) + { + input_position = last_input_position; + return false; + } + + if (p->char_func != store_char) + { + pad_vertically = true; + + if (print_a_header & !storing_columns) + print_header (); + + if (parallel_files & align_empty_cols) + { + /* We have to align empty columns at the beginning of a line. */ + k = separators_not_printed; + separators_not_printed = 0; + for (j = 1, q = column_vector; j <= k; ++j, ++q) + { + align_column (q); + separators_not_printed += 1; + } + padding_not_printed = p->start_position; + if (truncate_lines) + spaces_not_printed = chars_per_column; + else + spaces_not_printed = 0; + align_empty_cols = false; + } + + if (padding_not_printed - col_sep_length > 0) + { + pad_across_to (padding_not_printed - col_sep_length); + padding_not_printed = ANYWHERE; + } + + if (use_col_separator) + print_sep_string (); + } + + if (p->numbered) + add_line_number (p); + + empty_line = false; + if (c == '\n') + return true; + + print_clump (p, chars, clump_buff); + + for (;;) + { + c = getc (p->fp); + + switch (c) + { + case '\n': + return true; + case '\f': + if ((c = getc (p->fp)) != '\n') + ungetc (c, p->fp); + if (keep_FF) + print_a_FF = true; + hold_file (p); + return true; + case EOF: + close_file (p); + return true; + } + + last_input_position = input_position; + chars = char_to_clump (c); + if (truncate_lines && input_position > chars_per_column) + { + input_position = last_input_position; + return false; + } + + print_clump (p, chars, clump_buff); + } +} + +/* Print a line from buff. + + If this function has been called, we know we have "something to + print". But it remains to be seen whether we have a real text page + or an empty page (a single form feed) with/without a header only. + Therefore first we set pad_vertically to true and print a header + if necessary. + If FF_FOUND and we are using -t|-T option we omit any newline by + setting pad_vertically to false (see print_page). + Otherwise we pad across if necessary, print separators if necessary + and text of COLUMN *p. + + Return true, meaning there is no need to call read_rest_of_line. */ + +static bool +print_stored (COLUMN *p) +{ + COLUMN *q; + int i; + + int line = p->current_line++; + char *first = &buff[line_vector[line]]; + /* FIXME + UMR: Uninitialized memory read: + * This is occurring while in: + print_stored [pr.c:2239] + * Reading 4 bytes from 0x5148c in the heap. + * Address 0x5148c is 4 bytes into a malloc'd block at 0x51488 of 676 bytes + * This block was allocated from: + malloc [rtlib.o] + xmalloc [xmalloc.c:94] + init_store_cols [pr.c:1648] + */ + char *last = &buff[line_vector[line + 1]]; + + pad_vertically = true; + + if (print_a_header) + print_header (); + + if (p->status == FF_FOUND) + { + for (i = 1, q = column_vector; i <= columns; ++i, ++q) + q->status = ON_HOLD; + if (column_vector->lines_to_print <= 0) + { + if (!extremities) + pad_vertically = false; + return true; /* print a header only */ + } + } + + if (padding_not_printed - col_sep_length > 0) + { + pad_across_to (padding_not_printed - col_sep_length); + padding_not_printed = ANYWHERE; + } + + if (use_col_separator) + print_sep_string (); + + while (first != last) + print_char (*first++); + + if (spaces_not_printed == 0) + { + output_position = p->start_position + end_vector[line]; + if (p->start_position - col_sep_length == chars_per_margin) + output_position -= col_sep_length; + } + + return true; +} + +/* Convert a character to the proper format and return the number of + characters in the resulting clump. Increment input_position by + the width of the clump. + + Tabs are converted to clumps of spaces. + Nonprintable characters may be converted to clumps of escape + sequences or control prefixes. + + Note: the width of a clump is not necessarily equal to the number of + characters in clump_buff. (e.g, the width of '\b' is -1, while the + number of characters is 1.) */ + +static int +char_to_clump (char c) +{ + unsigned char uc = c; + char *s = clump_buff; + int i; + char esc_buff[4]; + int width; + int chars; + int chars_per_c = 8; + + if (c == input_tab_char) + chars_per_c = chars_per_input_tab; + + if (c == input_tab_char || c == '\t') + { + width = TAB_WIDTH (chars_per_c, input_position); + + if (untabify_input) + { + for (i = width; i; --i) + *s++ = ' '; + chars = width; + } + else + { + *s = c; + chars = 1; + } + + } + else if (! isprint (uc)) + { + if (use_esc_sequence) + { + width = 4; + chars = 4; + *s++ = '\\'; + sprintf (esc_buff, "%03o", uc); + for (i = 0; i <= 2; ++i) + *s++ = esc_buff[i]; + } + else if (use_cntrl_prefix) + { + if (uc < 0200) + { + width = 2; + chars = 2; + *s++ = '^'; + *s++ = c ^ 0100; + } + else + { + width = 4; + chars = 4; + *s++ = '\\'; + sprintf (esc_buff, "%03o", uc); + for (i = 0; i <= 2; ++i) + *s++ = esc_buff[i]; + } + } + else if (c == '\b') + { + width = -1; + chars = 1; + *s = c; + } + else + { + width = 0; + chars = 1; + *s = c; + } + } + else + { + width = 1; + chars = 1; + *s = c; + } + + input_position += width; + return chars; +} + +/* We've just printed some files and need to clean up things before + looking for more options and printing the next batch of files. + + Free everything we've xmalloc'ed, except `header'. */ + +static void +cleanup (void) +{ + free (number_buff); + free (clump_buff); + free (column_vector); + free (line_vector); + free (end_vector); + free (buff); +} + +/* Complain, print a usage message, and die. */ + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + + fputs (_("\ +Paginate or columnate FILE(s) for printing.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + +FIRST_PAGE[:LAST_PAGE], --pages=FIRST_PAGE[:LAST_PAGE]\n\ + begin [stop] printing with page FIRST_[LAST_]PAGE\n\ + -COLUMN, --columns=COLUMN\n\ + output COLUMN columns and print columns down,\n\ + unless -a is used. Balance number of lines in the\n\ + columns on each page.\n\ +"), stdout); + fputs (_("\ + -a, --across print columns across rather than down, used together\n\ + with -COLUMN\n\ + -c, --show-control-chars\n\ + use hat notation (^G) and octal backslash notation\n\ + -d, --double-space\n\ + double space the output\n\ +"), stdout); + fputs (_("\ + -D, --date-format=FORMAT\n\ + use FORMAT for the header date\n\ + -e[CHAR[WIDTH]], --expand-tabs[=CHAR[WIDTH]]\n\ + expand input CHARs (TABs) to tab WIDTH (8)\n\ + -F, -f, --form-feed\n\ + use form feeds instead of newlines to separate pages\n\ + (by a 3-line page header with -F or a 5-line header\n\ + and trailer without -F)\n\ +"), stdout); + fputs (_("\ + -h HEADER, --header=HEADER\n\ + use a centered HEADER instead of filename in page header,\n\ + -h \"\" prints a blank line, don't use -h\"\"\n\ + -i[CHAR[WIDTH]], --output-tabs[=CHAR[WIDTH]]\n\ + replace spaces with CHARs (TABs) to tab WIDTH (8)\n\ + -J, --join-lines merge full lines, turns off -W line truncation, no column\n\ + alignment, --sep-string[=STRING] sets separators\n\ +"), stdout); + fputs (_("\ + -l PAGE_LENGTH, --length=PAGE_LENGTH\n\ + set the page length to PAGE_LENGTH (66) lines\n\ + (default number of lines of text 56, and with -F 63)\n\ + -m, --merge print all files in parallel, one in each column,\n\ + truncate lines, but join lines of full length with -J\n\ +"), stdout); + fputs (_("\ + -n[SEP[DIGITS]], --number-lines[=SEP[DIGITS]]\n\ + number lines, use DIGITS (5) digits, then SEP (TAB),\n\ + default counting starts with 1st line of input file\n\ + -N NUMBER, --first-line-number=NUMBER\n\ + start counting with NUMBER at 1st line of first\n\ + page printed (see +FIRST_PAGE)\n\ +"), stdout); + fputs (_("\ + -o MARGIN, --indent=MARGIN\n\ + offset each line with MARGIN (zero) spaces, do not\n\ + affect -w or -W, MARGIN will be added to PAGE_WIDTH\n\ + -r, --no-file-warnings\n\ + omit warning when a file cannot be opened\n\ +"), stdout); + fputs (_("\ + -s[CHAR],--separator[=CHAR]\n\ + separate columns by a single character, default for CHAR\n\ + is the character without -w and \'no char\' with -w\n\ + -s[CHAR] turns off line truncation of all 3 column\n\ + options (-COLUMN|-a -COLUMN|-m) except -w is set\n\ +"), stdout); + fputs (_("\ + -SSTRING, --sep-string[=STRING]\n\ +"), stdout); + fputs (_("\ + separate columns by STRING,\n\ + without -S: Default separator with -J and \n\ + otherwise (same as -S\" \"), no effect on column options\n\ + -t, --omit-header omit page headers and trailers\n\ +"), stdout); + fputs (_("\ + -T, --omit-pagination\n\ + omit page headers and trailers, eliminate any pagination\n\ + by form feeds set in input files\n\ + -v, --show-nonprinting\n\ + use octal backslash notation\n\ + -w PAGE_WIDTH, --width=PAGE_WIDTH\n\ + set page width to PAGE_WIDTH (72) characters for\n\ + multiple text-column output only, -s[char] turns off (72)\n\ +"), stdout); + fputs (_("\ + -W PAGE_WIDTH, --page-width=PAGE_WIDTH\n\ + set page width to PAGE_WIDTH (72) characters always,\n\ + truncate lines, except -J option is set, no interference\n\ + with -S or -s\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +-T implied by -l nn when nn <= 10 or <= 3 with -F. With no FILE, or when\n\ +FILE is -, read standard input.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} diff --git a/src/printenv.c b/src/printenv.c new file mode 100644 index 0000000..e06b704 --- /dev/null +++ b/src/printenv.c @@ -0,0 +1,134 @@ +/* printenv -- print all or part of environment + Copyright (C) 1989-1997, 1999-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Usage: printenv [variable...] + + If no arguments are given, print the entire environment. + If one or more variable names are given, print the value of + each one that is set, and nothing for ones that are not set. + + Exit status: + 0 if all variables specified were found + 1 if not + 2 if some other error occurred + + David MacKenzie and Richard Mlynarik */ + +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "long-options.h" + +/* Exit status for syntax errors, etc. */ +enum { PRINTENV_FAILURE = 2 }; + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "printenv" + +#define AUTHORS "David MacKenzie", "Richard Mlynarik" + +/* The name this program was run with. */ +char *program_name; + +extern char **environ; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [VARIABLE]...\n\ + or: %s OPTION\n\ +If no environment VARIABLE specified, print them all.\n\ +\n\ +"), + program_name, program_name); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + char **env; + char *ep, *ap; + int i; + bool ok; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (PRINTENV_FAILURE); + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "+", NULL, NULL) != -1) + usage (PRINTENV_FAILURE); + + if (optind >= argc) + { + for (env = environ; *env != NULL; ++env) + puts (*env); + ok = true; + } + else + { + int matches = 0; + + for (i = optind; i < argc; ++i) + { + bool matched = false; + + for (env = environ; *env; ++env) + { + ep = *env; + ap = argv[i]; + while (*ep != '\0' && *ap != '\0' && *ep++ == *ap++) + { + if (*ep == '=' && *ap == '\0') + { + puts (ep + 1); + matched = true; + break; + } + } + } + + matches += matched; + } + + ok = (matches == argc - optind); + } + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/printf.c b/src/printf.c new file mode 100644 index 0000000..6205153 --- /dev/null +++ b/src/printf.c @@ -0,0 +1,687 @@ +/* printf - format and print data + Copyright (C) 1990-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Usage: printf format [argument...] + + A front end to the printf function that lets it be used from the shell. + + Backslash escapes: + + \" = double quote + \\ = backslash + \a = alert (bell) + \b = backspace + \c = produce no further output + \f = form feed + \n = new line + \r = carriage return + \t = horizontal tab + \v = vertical tab + \ooo = octal number (ooo is 1 to 3 digits) + \xhh = hexadecimal number (hhh is 1 to 2 digits) + \uhhhh = 16-bit Unicode character (hhhh is 4 digits) + \Uhhhhhhhh = 32-bit Unicode character (hhhhhhhh is 8 digits) + + Additional directive: + + %b = print an argument string, interpreting backslash escapes, + except that octal escapes are of the form \0 or \0ooo. + + The `format' argument is re-used as many times as necessary + to convert all of the given arguments. + + David MacKenzie */ + +#include +#include +#include +#include + +#include "system.h" +#include "c-strtod.h" +#include "error.h" +#include "long-options.h" +#include "quote.h" +#include "unicodeio.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "printf" + +#define AUTHORS "David MacKenzie" + +#define isodigit(c) ((c) >= '0' && (c) <= '7') +#define hextobin(c) ((c) >= 'a' && (c) <= 'f' ? (c) - 'a' + 10 : \ + (c) >= 'A' && (c) <= 'F' ? (c) - 'A' + 10 : (c) - '0') +#define octtobin(c) ((c) - '0') + +/* The value to return to the calling program. */ +static int exit_status; + +/* True if the POSIXLY_CORRECT environment variable is set. */ +static bool posixly_correct; + +/* This message appears in N_() here rather than just in _() below because + the sole use would have been in a #define. */ +static char const *const cfcc_msg = + N_("warning: %s: character(s) following character constant have been ignored"); + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s FORMAT [ARGUMENT]...\n\ + or: %s OPTION\n\ +"), + program_name, program_name); + fputs (_("\ +Print ARGUMENT(s) according to FORMAT.\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +FORMAT controls the output as in C printf. Interpreted sequences are:\n\ +\n\ + \\\" double quote\n\ + \\NNN character with octal value NNN (1 to 3 digits)\n\ + \\\\ backslash\n\ +"), stdout); + fputs (_("\ + \\a alert (BEL)\n\ + \\b backspace\n\ + \\c produce no further output\n\ + \\f form feed\n\ +"), stdout); + fputs (_("\ + \\n new line\n\ + \\r carriage return\n\ + \\t horizontal tab\n\ + \\v vertical tab\n\ +"), stdout); + fputs (_("\ + \\xHH byte with hexadecimal value HH (1 to 2 digits)\n\ + \\uHHHH Unicode (ISO/IEC 10646) character with hex value HHHH (4 digits)\n\ + \\UHHHHHHHH Unicode character with hex value HHHHHHHH (8 digits)\n\ +"), stdout); + fputs (_("\ + %% a single %\n\ + %b ARGUMENT as a string with `\\' escapes interpreted,\n\ + except that octal escapes are of the form \\0 or \\0NNN\n\ +\n\ +and all C format specifications ending with one of diouxXfeEgGcs, with\n\ +ARGUMENTs converted to proper type first. Variable widths are handled.\n\ +"), stdout); + printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +static void +verify_numeric (const char *s, const char *end) +{ + if (errno) + { + error (0, errno, "%s", s); + exit_status = EXIT_FAILURE; + } + else if (*end) + { + if (s == end) + error (0, 0, _("%s: expected a numeric value"), s); + else + error (0, 0, _("%s: value not completely converted"), s); + exit_status = EXIT_FAILURE; + } +} + +#define STRTOX(TYPE, FUNC_NAME, LIB_FUNC_EXPR) \ +static TYPE \ +FUNC_NAME (char const *s) \ +{ \ + char *end; \ + TYPE val; \ + \ + if (*s == '\"' || *s == '\'') \ + { \ + unsigned char ch = *++s; \ + val = ch; \ + /* If POSIXLY_CORRECT is not set, then give a warning that there \ + are characters following the character constant and that GNU \ + printf is ignoring those characters. If POSIXLY_CORRECT *is* \ + set, then don't give the warning. */ \ + if (*++s != 0 && !posixly_correct) \ + error (0, 0, _(cfcc_msg), s); \ + } \ + else \ + { \ + errno = 0; \ + val = (LIB_FUNC_EXPR); \ + verify_numeric (s, end); \ + } \ + return val; \ +} \ + +STRTOX (intmax_t, vstrtoimax, strtoimax (s, &end, 0)) +STRTOX (uintmax_t, vstrtoumax, strtoumax (s, &end, 0)) +STRTOX (long double, vstrtold, c_strtold (s, &end)) + +/* Output a single-character \ escape. */ + +static void +print_esc_char (char c) +{ + switch (c) + { + case 'a': /* Alert. */ + putchar ('\a'); + break; + case 'b': /* Backspace. */ + putchar ('\b'); + break; + case 'c': /* Cancel the rest of the output. */ + exit (EXIT_SUCCESS); + break; + case 'f': /* Form feed. */ + putchar ('\f'); + break; + case 'n': /* New line. */ + putchar ('\n'); + break; + case 'r': /* Carriage return. */ + putchar ('\r'); + break; + case 't': /* Horizontal tab. */ + putchar ('\t'); + break; + case 'v': /* Vertical tab. */ + putchar ('\v'); + break; + default: + putchar (c); + break; + } +} + +/* Print a \ escape sequence starting at ESCSTART. + Return the number of characters in the escape sequence + besides the backslash. + If OCTAL_0 is nonzero, octal escapes are of the form \0ooo, where o + is an octal digit; otherwise they are of the form \ooo. */ + +static int +print_esc (const char *escstart, bool octal_0) +{ + const char *p = escstart + 1; + int esc_value = 0; /* Value of \nnn escape. */ + int esc_length; /* Length of \nnn escape. */ + + if (*p == 'x') + { + /* A hexadecimal \xhh escape sequence must have 1 or 2 hex. digits. */ + for (esc_length = 0, ++p; + esc_length < 2 && isxdigit (to_uchar (*p)); + ++esc_length, ++p) + esc_value = esc_value * 16 + hextobin (*p); + if (esc_length == 0) + error (EXIT_FAILURE, 0, _("missing hexadecimal number in escape")); + putchar (esc_value); + } + else if (isodigit (*p)) + { + /* Parse \0ooo (if octal_0 && *p == '0') or \ooo (otherwise). + Allow \ooo if octal_0 && *p != '0'; this is an undocumented + extension to POSIX that is compatible with Bash 2.05b. */ + for (esc_length = 0, p += octal_0 && *p == '0'; + esc_length < 3 && isodigit (*p); + ++esc_length, ++p) + esc_value = esc_value * 8 + octtobin (*p); + putchar (esc_value); + } + else if (*p && strchr ("\"\\abcfnrtv", *p)) + print_esc_char (*p++); + else if (*p == 'u' || *p == 'U') + { + char esc_char = *p; + unsigned int uni_value; + + uni_value = 0; + for (esc_length = (esc_char == 'u' ? 4 : 8), ++p; + esc_length > 0; + --esc_length, ++p) + { + if (! isxdigit (to_uchar (*p))) + error (EXIT_FAILURE, 0, _("missing hexadecimal number in escape")); + uni_value = uni_value * 16 + hextobin (*p); + } + + /* A universal character name shall not specify a character short + identifier in the range 00000000 through 00000020, 0000007F through + 0000009F, or 0000D800 through 0000DFFF inclusive. A universal + character name shall not designate a character in the required + character set. */ + if ((uni_value <= 0x9f + && uni_value != 0x24 && uni_value != 0x40 && uni_value != 0x60) + || (uni_value >= 0xd800 && uni_value <= 0xdfff)) + error (EXIT_FAILURE, 0, _("invalid universal character name \\%c%0*x"), + esc_char, (esc_char == 'u' ? 4 : 8), uni_value); + + print_unicode_char (stdout, uni_value, 0); + } + else + { + putchar ('\\'); + if (*p) + { + putchar (*p); + p++; + } + } + return p - escstart - 1; +} + +/* Print string STR, evaluating \ escapes. */ + +static void +print_esc_string (const char *str) +{ + for (; *str; str++) + if (*str == '\\') + str += print_esc (str, true); + else + putchar (*str); +} + +/* Evaluate a printf conversion specification. START is the start of + the directive, LENGTH is its length, and CONVERSION specifies the + type of conversion. LENGTH does not include any length modifier or + the conversion specifier itself. FIELD_WIDTH and PRECISION are the + field width and precision for '*' values, if HAVE_FIELD_WIDTH and + HAVE_PRECISION are true, respectively. ARGUMENT is the argument to + be formatted. */ + +static void +print_direc (const char *start, size_t length, char conversion, + bool have_field_width, int field_width, + bool have_precision, int precision, + char const *argument) +{ + char *p; /* Null-terminated copy of % directive. */ + + /* Create a null-terminated copy of the % directive, with an + intmax_t-wide length modifier substituted for any existing + integer length modifier. */ + { + char *q; + char const *length_modifier; + size_t length_modifier_len; + + switch (conversion) + { + case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': + length_modifier = PRIdMAX; + length_modifier_len = sizeof PRIdMAX - 2; + break; + + case 'a': case 'e': case 'f': case 'g': + case 'A': case 'E': case 'F': case 'G': + length_modifier = "L"; + length_modifier_len = 1; + break; + + default: + length_modifier = start; /* Any valid pointer will do. */ + length_modifier_len = 0; + break; + } + + p = xmalloc (length + length_modifier_len + 2); + q = mempcpy (p, start, length); + q = mempcpy (q, length_modifier, length_modifier_len); + *q++ = conversion; + *q = '\0'; + } + + switch (conversion) + { + case 'd': + case 'i': + { + intmax_t arg = vstrtoimax (argument); + if (!have_field_width) + { + if (!have_precision) + printf (p, arg); + else + printf (p, precision, arg); + } + else + { + if (!have_precision) + printf (p, field_width, arg); + else + printf (p, field_width, precision, arg); + } + } + break; + + case 'o': + case 'u': + case 'x': + case 'X': + { + uintmax_t arg = vstrtoumax (argument); + if (!have_field_width) + { + if (!have_precision) + printf (p, arg); + else + printf (p, precision, arg); + } + else + { + if (!have_precision) + printf (p, field_width, arg); + else + printf (p, field_width, precision, arg); + } + } + break; + + case 'a': + case 'A': + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + { + long double arg = vstrtold (argument); + if (!have_field_width) + { + if (!have_precision) + printf (p, arg); + else + printf (p, precision, arg); + } + else + { + if (!have_precision) + printf (p, field_width, arg); + else + printf (p, field_width, precision, arg); + } + } + break; + + case 'c': + if (!have_field_width) + printf (p, *argument); + else + printf (p, field_width, *argument); + break; + + case 's': + if (!have_field_width) + { + if (!have_precision) + printf (p, argument); + else + printf (p, precision, argument); + } + else + { + if (!have_precision) + printf (p, field_width, argument); + else + printf (p, field_width, precision, argument); + } + break; + } + + free (p); +} + +/* Print the text in FORMAT, using ARGV (with ARGC elements) for + arguments to any `%' directives. + Return the number of elements of ARGV used. */ + +static int +print_formatted (const char *format, int argc, char **argv) +{ + int save_argc = argc; /* Preserve original value. */ + const char *f; /* Pointer into `format'. */ + const char *direc_start; /* Start of % directive. */ + size_t direc_length; /* Length of % directive. */ + bool have_field_width; /* True if FIELD_WIDTH is valid. */ + int field_width = 0; /* Arg to first '*'. */ + bool have_precision; /* True if PRECISION is valid. */ + int precision = 0; /* Arg to second '*'. */ + char ok[UCHAR_MAX + 1]; /* ok['x'] is true if %x is allowed. */ + + for (f = format; *f; ++f) + { + switch (*f) + { + case '%': + direc_start = f++; + direc_length = 1; + have_field_width = have_precision = false; + if (*f == '%') + { + putchar ('%'); + break; + } + if (*f == 'b') + { + /* FIXME: Field width and precision are not supported + for %b, even though POSIX requires it. */ + if (argc > 0) + { + print_esc_string (*argv); + ++argv; + --argc; + } + break; + } + + memset (ok, 0, sizeof ok); + ok['a'] = ok['A'] = ok['c'] = ok['d'] = ok['e'] = ok['E'] = + ok['f'] = ok['F'] = ok['g'] = ok['G'] = ok['i'] = ok['o'] = + ok['s'] = ok['u'] = ok['x'] = ok['X'] = 1; + + for (;; f++, direc_length++) + switch (*f) + { +#if (__GLIBC__ == 2 && 2 <= __GLIBC_MINOR__) || 3 <= __GLIBC__ + case 'I': +#endif + case '\'': + ok['a'] = ok['A'] = ok['c'] = ok['e'] = ok['E'] = + ok['o'] = ok['s'] = ok['x'] = ok['X'] = 0; + break; + case '-': case '+': case ' ': + break; + case '#': + ok['c'] = ok['d'] = ok['i'] = ok['s'] = ok['u'] = 0; + break; + case '0': + ok['c'] = ok['s'] = 0; + break; + default: + goto no_more_flag_characters; + } + no_more_flag_characters:; + + if (*f == '*') + { + ++f; + ++direc_length; + if (argc > 0) + { + intmax_t width = vstrtoimax (*argv); + if (INT_MIN <= width && width <= INT_MAX) + field_width = width; + else + error (EXIT_FAILURE, 0, _("invalid field width: %s"), + *argv); + ++argv; + --argc; + } + else + field_width = 0; + have_field_width = true; + } + else + while (ISDIGIT (*f)) + { + ++f; + ++direc_length; + } + if (*f == '.') + { + ++f; + ++direc_length; + ok['c'] = 0; + if (*f == '*') + { + ++f; + ++direc_length; + if (argc > 0) + { + intmax_t prec = vstrtoimax (*argv); + if (prec < 0) + { + /* A negative precision is taken as if the + precision were omitted, so -1 is safe + here even if prec < INT_MIN. */ + precision = -1; + } + else if (INT_MAX < prec) + error (EXIT_FAILURE, 0, _("invalid precision: %s"), + *argv); + else + precision = prec; + ++argv; + --argc; + } + else + precision = 0; + have_precision = true; + } + else + while (ISDIGIT (*f)) + { + ++f; + ++direc_length; + } + } + + while (*f == 'l' || *f == 'L' || *f == 'h' + || *f == 'j' || *f == 't' || *f == 'z') + ++f; + + { + unsigned char conversion = *f; + if (! ok[conversion]) + error (EXIT_FAILURE, 0, + _("%.*s: invalid conversion specification"), + (int) (f + 1 - direc_start), direc_start); + } + + print_direc (direc_start, direc_length, *f, + have_field_width, field_width, + have_precision, precision, + (argc <= 0 ? "" : (argc--, *argv++))); + break; + + case '\\': + f += print_esc (f, false); + break; + + default: + putchar (*f); + } + } + + return save_argc - argc; +} + +int +main (int argc, char **argv) +{ + char *format; + int args_used; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + exit_status = EXIT_SUCCESS; + + posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + + /* The above handles --help and --version. + Since there is no other invocation of getopt, handle `--' here. */ + if (1 < argc && STREQ (argv[1], "--")) + { + --argc; + ++argv; + } + + if (argc <= 1) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + format = argv[1]; + argc -= 2; + argv += 2; + + do + { + args_used = print_formatted (format, argc, argv); + argc -= args_used; + argv += args_used; + } + while (args_used > 0 && argc > 0); + + if (argc > 0) + error (0, 0, + _("warning: ignoring excess arguments, starting with %s"), + quote (argv[0])); + + exit (exit_status); +} diff --git a/src/ptx.c b/src/ptx.c new file mode 100644 index 0000000..9c35596 --- /dev/null +++ b/src/ptx.c @@ -0,0 +1,2224 @@ +/* Permuted index for GNU, with keywords in their context. + Copyright (C) 1990, 1991, 1993, 1998-2006 Free Software Foundation, Inc. + François Pinard , 1988. + + 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + François Pinard */ + +#include + +#include +#include +#include +#include "system.h" +#include "argmatch.h" +#include "diacrit.h" +#include "error.h" +#include "quote.h" +#include "quotearg.h" +#include "regex.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "ptx" + +/* Note to translator: Please translate "F. Pinard" to "François + Pinard" if "ç" (c-with-cedilla) is available in the + translation's character set and encoding. */ +#define AUTHORS _("F. Pinard") + +/* Number of possible characters in a byte. */ +#define CHAR_SET_SIZE 256 + +#define ISODIGIT(C) ((C) >= '0' && (C) <= '7') +#define HEXTOBIN(C) ((C) >= 'a' && (C) <= 'f' ? (C)-'a'+10 \ + : (C) >= 'A' && (C) <= 'F' ? (C)-'A'+10 : (C)-'0') +#define OCTTOBIN(C) ((C) - '0') + +/* Debugging the memory allocator. */ + +#if WITH_DMALLOC +# define MALLOC_FUNC_CHECK 1 +# include +#endif + +/* Global definitions. */ + +/* FIXME: There are many unchecked integer overflows in this file, + that will cause this command to misbehave given large inputs or + options. Many of the "int" values below should be "size_t" or + something else like that. */ + +/* Reallocation step when swallowing non regular files. The value is not + the actual reallocation step, but its base two logarithm. */ +#define SWALLOW_REALLOC_LOG 12 + +/* Imported from "regex.c". */ +#define Sword 1 + +/* The name this program was run with. */ +char *program_name; + +/* Program options. */ + +enum Format +{ + UNKNOWN_FORMAT, /* output format still unknown */ + DUMB_FORMAT, /* output for a dumb terminal */ + ROFF_FORMAT, /* output for `troff' or `nroff' */ + TEX_FORMAT /* output for `TeX' or `LaTeX' */ +}; + +static bool gnu_extensions = true; /* trigger all GNU extensions */ +static bool auto_reference = false; /* refs are `file_name:line_number:' */ +static bool input_reference = false; /* refs at beginning of input lines */ +static bool right_reference = false; /* output refs after right context */ +static int line_width = 72; /* output line width in characters */ +static int gap_size = 3; /* number of spaces between output fields */ +static const char *truncation_string = "/"; + /* string used to mark line truncations */ +static const char *macro_name = "xx"; /* macro name for roff or TeX output */ +static enum Format output_format = UNKNOWN_FORMAT; + /* output format */ + +static bool ignore_case = false; /* fold lower to upper for sorting */ +static const char *break_file = NULL; /* name of the `Break characters' file */ +static const char *only_file = NULL; /* name of the `Only words' file */ +static const char *ignore_file = NULL; /* name of the `Ignore words' file */ + +/* Options that use regular expressions. */ +struct regex_data +{ + /* The original regular expression, as a string. */ + char const *string; + + /* The compiled regular expression, and its fastmap. */ + struct re_pattern_buffer pattern; + char fastmap[UCHAR_MAX + 1]; +}; + +static struct regex_data context_regex; /* end of context */ +static struct regex_data word_regex; /* keyword */ + +/* A BLOCK delimit a region in memory of arbitrary size, like the copy of a + whole file. A WORD is something smaller, its length should fit in a + short integer. A WORD_TABLE may contain several WORDs. */ + +typedef struct + { + char *start; /* pointer to beginning of region */ + char *end; /* pointer to end + 1 of region */ + } +BLOCK; + +typedef struct + { + char *start; /* pointer to beginning of region */ + short int size; /* length of the region */ + } +WORD; + +typedef struct + { + WORD *start; /* array of WORDs */ + size_t alloc; /* allocated length */ + size_t length; /* number of used entries */ + } +WORD_TABLE; + +/* Pattern description tables. */ + +/* For each character, provide its folded equivalent. */ +static unsigned char folded_chars[CHAR_SET_SIZE]; + +/* End of context pattern register indices. */ +static struct re_registers context_regs; + +/* Keyword pattern register indices. */ +static struct re_registers word_regs; + +/* A word characters fastmap is used only when no word regexp has been + provided. A word is then made up of a sequence of one or more characters + allowed by the fastmap. Contains !0 if character allowed in word. Not + only this is faster in most cases, but it simplifies the implementation + of the Break files. */ +static char word_fastmap[CHAR_SET_SIZE]; + +/* Maximum length of any word read. */ +static int maximum_word_length; + +/* Maximum width of any reference used. */ +static int reference_max_width; + +/* Ignore and Only word tables. */ + +static WORD_TABLE ignore_table; /* table of words to ignore */ +static WORD_TABLE only_table; /* table of words to select */ + +/* Source text table, and scanning macros. */ + +static int number_input_files; /* number of text input files */ +static int total_line_count; /* total number of lines seen so far */ +static const char **input_file_name; /* array of text input file names */ +static int *file_line_count; /* array of `total_line_count' values at end */ + +static BLOCK text_buffer; /* file to study */ + +/* SKIP_NON_WHITE used only for getting or skipping the reference. */ + +#define SKIP_NON_WHITE(cursor, limit) \ + while (cursor < limit && ! isspace (to_uchar (*cursor))) \ + cursor++ + +#define SKIP_WHITE(cursor, limit) \ + while (cursor < limit && isspace (to_uchar (*cursor))) \ + cursor++ + +#define SKIP_WHITE_BACKWARDS(cursor, start) \ + while (cursor > start && isspace (to_uchar (cursor[-1]))) \ + cursor-- + +#define SKIP_SOMETHING(cursor, limit) \ + if (word_regex.string) \ + { \ + regoff_t count; \ + count = re_match (&word_regex.pattern, cursor, limit - cursor, 0, NULL); \ + if (count == -2) \ + matcher_error (); \ + cursor += count == -1 ? 1 : count; \ + } \ + else if (word_fastmap[to_uchar (*cursor)]) \ + while (cursor < limit && word_fastmap[to_uchar (*cursor)]) \ + cursor++; \ + else \ + cursor++ + +/* Occurrences table. + + The `keyword' pointer provides the central word, which is surrounded + by a left context and a right context. The `keyword' and `length' + field allow full 8-bit characters keys, even including NULs. At other + places in this program, the name `keyafter' refers to the keyword + followed by its right context. + + The left context does not extend, towards the beginning of the file, + further than a distance given by the `left' value. This value is + relative to the keyword beginning, it is usually negative. This + insures that, except for white space, we will never have to backward + scan the source text, when it is time to generate the final output + lines. + + The right context, indirectly attainable through the keyword end, does + not extend, towards the end of the file, further than a distance given + by the `right' value. This value is relative to the keyword + beginning, it is usually positive. + + When automatic references are used, the `reference' value is the + overall line number in all input files read so far, in this case, it + is of type (int). When input references are used, the `reference' + value indicates the distance between the keyword beginning and the + start of the reference field, it is of type (DELTA) and usually + negative. */ + +typedef short int DELTA; /* to hold displacement within one context */ + +typedef struct + { + WORD key; /* description of the keyword */ + DELTA left; /* distance to left context start */ + DELTA right; /* distance to right context end */ + int reference; /* reference descriptor */ + } +OCCURS; + +/* The various OCCURS tables are indexed by the language. But the time + being, there is no such multiple language support. */ + +static OCCURS *occurs_table[1]; /* all words retained from the read text */ +static size_t occurs_alloc[1]; /* allocated size of occurs_table */ +static size_t number_of_occurs[1]; /* number of used slots in occurs_table */ + + +/* Communication among output routines. */ + +/* Indicate if special output processing is requested for each character. */ +static char edited_flag[CHAR_SET_SIZE]; + +static int half_line_width; /* half of line width, reference excluded */ +static int before_max_width; /* maximum width of before field */ +static int keyafter_max_width; /* maximum width of keyword-and-after field */ +static int truncation_string_length;/* length of string used to flag truncation */ + +/* When context is limited by lines, wraparound may happen on final output: + the `head' pointer gives access to some supplementary left context which + will be seen at the end of the output line, the `tail' pointer gives + access to some supplementary right context which will be seen at the + beginning of the output line. */ + +static BLOCK tail; /* tail field */ +static int tail_truncation; /* flag truncation after the tail field */ + +static BLOCK before; /* before field */ +static int before_truncation; /* flag truncation before the before field */ + +static BLOCK keyafter; /* keyword-and-after field */ +static int keyafter_truncation; /* flag truncation after the keyafter field */ + +static BLOCK head; /* head field */ +static int head_truncation; /* flag truncation before the head field */ + +static BLOCK reference; /* reference field for input reference mode */ + +/* Miscellaneous routines. */ + +/* Diagnose an error in the regular expression matcher. Then exit. */ + +static void ATTRIBUTE_NORETURN +matcher_error (void) +{ + error (0, errno, _("error in regular expression matcher")); + exit (EXIT_FAILURE); +} + +/*------------------------------------------------------. +| Duplicate string STRING, while evaluating \-escapes. | +`------------------------------------------------------*/ + +/* Loosely adapted from GNU sh-utils printf.c code. */ + +static char * +copy_unescaped_string (const char *string) +{ + char *result; /* allocated result */ + char *cursor; /* cursor in result */ + int value; /* value of \nnn escape */ + int length; /* length of \nnn escape */ + + result = xmalloc (strlen (string) + 1); + cursor = result; + + while (*string) + if (*string == '\\') + { + string++; + switch (*string) + { + case 'x': /* \xhhh escape, 3 chars maximum */ + value = 0; + for (length = 0, string++; + length < 3 && isxdigit (to_uchar (*string)); + length++, string++) + value = value * 16 + HEXTOBIN (*string); + if (length == 0) + { + *cursor++ = '\\'; + *cursor++ = 'x'; + } + else + *cursor++ = value; + break; + + case '0': /* \0ooo escape, 3 chars maximum */ + value = 0; + for (length = 0, string++; + length < 3 && ISODIGIT (*string); + length++, string++) + value = value * 8 + OCTTOBIN (*string); + *cursor++ = value; + break; + + case 'a': /* alert */ +#if __STDC__ + *cursor++ = '\a'; +#else + *cursor++ = 7; +#endif + string++; + break; + + case 'b': /* backspace */ + *cursor++ = '\b'; + string++; + break; + + case 'c': /* cancel the rest of the output */ + while (*string) + string++; + break; + + case 'f': /* form feed */ + *cursor++ = '\f'; + string++; + break; + + case 'n': /* new line */ + *cursor++ = '\n'; + string++; + break; + + case 'r': /* carriage return */ + *cursor++ = '\r'; + string++; + break; + + case 't': /* horizontal tab */ + *cursor++ = '\t'; + string++; + break; + + case 'v': /* vertical tab */ +#if __STDC__ + *cursor++ = '\v'; +#else + *cursor++ = 11; +#endif + string++; + break; + + default: + *cursor++ = '\\'; + *cursor++ = *string++; + break; + } + } + else + *cursor++ = *string++; + + *cursor = '\0'; + return result; +} + +/*--------------------------------------------------------------------------. +| Compile the regex represented by REGEX, diagnose and abort if any error. | +`--------------------------------------------------------------------------*/ + +static void +compile_regex (struct regex_data *regex) +{ + struct re_pattern_buffer *pattern = ®ex->pattern; + char const *string = regex->string; + char const *message; + + pattern->buffer = NULL; + pattern->allocated = 0; + pattern->fastmap = regex->fastmap; + pattern->translate = ignore_case ? folded_chars : NULL; + + message = re_compile_pattern (string, strlen (string), pattern); + if (message) + error (EXIT_FAILURE, 0, _("%s (for regexp %s)"), message, quote (string)); + + /* The fastmap should be compiled before `re_match'. The following + call is not mandatory, because `re_search' is always called sooner, + and it compiles the fastmap if this has not been done yet. */ + + re_compile_fastmap (pattern); +} + +/*------------------------------------------------------------------------. +| This will initialize various tables for pattern match and compiles some | +| regexps. | +`------------------------------------------------------------------------*/ + +static void +initialize_regex (void) +{ + int character; /* character value */ + + /* Initialize the case folding table. */ + + if (ignore_case) + for (character = 0; character < CHAR_SET_SIZE; character++) + folded_chars[character] = toupper (character); + + /* Unless the user already provided a description of the end of line or + end of sentence sequence, select an end of line sequence to compile. + If the user provided an empty definition, thus disabling end of line + or sentence feature, make it NULL to speed up tests. If GNU + extensions are enabled, use end of sentence like in GNU emacs. If + disabled, use end of lines. */ + + if (context_regex.string) + { + if (!*context_regex.string) + context_regex.string = NULL; + } + else if (gnu_extensions & !input_reference) + context_regex.string = "[.?!][]\"')}]*\\($\\|\t\\| \\)[ \t\n]*"; + else + context_regex.string = "\n"; + + if (context_regex.string) + compile_regex (&context_regex); + + /* If the user has already provided a non-empty regexp to describe + words, compile it. Else, unless this has already been done through + a user provided Break character file, construct a fastmap of + characters that may appear in a word. If GNU extensions enabled, + include only letters of the underlying character set. If disabled, + include almost everything, even punctuations; stop only on white + space. */ + + if (word_regex.string) + compile_regex (&word_regex); + else if (!break_file) + { + if (gnu_extensions) + { + + /* Simulate \w+. */ + + for (character = 0; character < CHAR_SET_SIZE; character++) + word_fastmap[character] = !! isalpha (character); + } + else + { + + /* Simulate [^ \t\n]+. */ + + memset (word_fastmap, 1, CHAR_SET_SIZE); + word_fastmap[' '] = 0; + word_fastmap['\t'] = 0; + word_fastmap['\n'] = 0; + } + } +} + +/*------------------------------------------------------------------------. +| This routine will attempt to swallow a whole file name FILE_NAME into a | +| contiguous region of memory and return a description of it into BLOCK. | +| Standard input is assumed whenever FILE_NAME is NULL, empty or "-". | +| | +| Previously, in some cases, white space compression was attempted while | +| inputting text. This was defeating some regexps like default end of | +| sentence, which checks for two consecutive spaces. If white space | +| compression is ever reinstated, it should be in output routines. | +`------------------------------------------------------------------------*/ + +static void +swallow_file_in_memory (const char *file_name, BLOCK *block) +{ + int file_handle; /* file descriptor number */ + struct stat stat_block; /* stat block for file */ + size_t allocated_length; /* allocated length of memory buffer */ + size_t used_length; /* used length in memory buffer */ + int read_length; /* number of character gotten on last read */ + + /* As special cases, a file name which is NULL or "-" indicates standard + input, which is already opened. In all other cases, open the file from + its name. */ + bool using_stdin = !file_name || !*file_name || STREQ (file_name, "-"); + if (using_stdin) + file_handle = STDIN_FILENO; + else + if ((file_handle = open (file_name, O_RDONLY)) < 0) + error (EXIT_FAILURE, errno, "%s", file_name); + + /* If the file is a plain, regular file, allocate the memory buffer all at + once and swallow the file in one blow. In other cases, read the file + repeatedly in smaller chunks until we have it all, reallocating memory + once in a while, as we go. */ + + if (fstat (file_handle, &stat_block) < 0) + error (EXIT_FAILURE, errno, "%s", file_name); + + if (S_ISREG (stat_block.st_mode)) + { + size_t in_memory_size; + + block->start = xmalloc ((size_t) stat_block.st_size); + + if ((in_memory_size = read (file_handle, + block->start, (size_t) stat_block.st_size)) + != stat_block.st_size) + { +#if MSDOS + /* On MSDOS, in memory size may be smaller than the file + size, because of end of line conversions. But it can + never be smaller than half the file size, because the + minimum is when all lines are empty and terminated by + CR+LF. */ + if (in_memory_size != (size_t)-1 + && in_memory_size >= stat_block.st_size / 2) + block->start = xrealloc (block->start, in_memory_size); + else +#endif /* not MSDOS */ + + error (EXIT_FAILURE, errno, "%s", file_name); + } + block->end = block->start + in_memory_size; + } + else + { + block->start = xmalloc ((size_t) 1 << SWALLOW_REALLOC_LOG); + used_length = 0; + allocated_length = (1 << SWALLOW_REALLOC_LOG); + + while (read_length = read (file_handle, + block->start + used_length, + allocated_length - used_length), + read_length > 0) + { + used_length += read_length; + if (used_length == allocated_length) + { + allocated_length += (1 << SWALLOW_REALLOC_LOG); + block->start + = xrealloc (block->start, allocated_length); + } + } + + if (read_length < 0) + error (EXIT_FAILURE, errno, "%s", file_name); + + block->end = block->start + used_length; + } + + /* Close the file, but only if it was not the standard input. */ + + if (! using_stdin && close (file_handle) != 0) + error (EXIT_FAILURE, errno, "%s", file_name); +} + +/* Sort and search routines. */ + +/*--------------------------------------------------------------------------. +| Compare two words, FIRST and SECOND, and return 0 if they are identical. | +| Return less than 0 if the first word goes before the second; return | +| greater than 0 if the first word goes after the second. | +| | +| If a word is indeed a prefix of the other, the shorter should go first. | +`--------------------------------------------------------------------------*/ + +static int +compare_words (const void *void_first, const void *void_second) +{ +#define first ((const WORD *) void_first) +#define second ((const WORD *) void_second) + int length; /* minimum of two lengths */ + int counter; /* cursor in words */ + int value; /* value of comparison */ + + length = first->size < second->size ? first->size : second->size; + + if (ignore_case) + { + for (counter = 0; counter < length; counter++) + { + value = (folded_chars [to_uchar (first->start[counter])] + - folded_chars [to_uchar (second->start[counter])]); + if (value != 0) + return value; + } + } + else + { + for (counter = 0; counter < length; counter++) + { + value = (to_uchar (first->start[counter]) + - to_uchar (second->start[counter])); + if (value != 0) + return value; + } + } + + return first->size - second->size; +#undef first +#undef second +} + +/*-----------------------------------------------------------------------. +| Decides which of two OCCURS, FIRST or SECOND, should lexicographically | +| go first. In case of a tie, preserve the original order through a | +| pointer comparison. | +`-----------------------------------------------------------------------*/ + +static int +compare_occurs (const void *void_first, const void *void_second) +{ +#define first ((const OCCURS *) void_first) +#define second ((const OCCURS *) void_second) + int value; + + value = compare_words (&first->key, &second->key); + return value == 0 ? first->key.start - second->key.start : value; +#undef first +#undef second +} + +/*------------------------------------------------------------. +| Return !0 if WORD appears in TABLE. Uses a binary search. | +`------------------------------------------------------------*/ + +static int +search_table (WORD *word, WORD_TABLE *table) +{ + int lowest; /* current lowest possible index */ + int highest; /* current highest possible index */ + int middle; /* current middle index */ + int value; /* value from last comparison */ + + lowest = 0; + highest = table->length - 1; + while (lowest <= highest) + { + middle = (lowest + highest) / 2; + value = compare_words (word, table->start + middle); + if (value < 0) + highest = middle - 1; + else if (value > 0) + lowest = middle + 1; + else + return 1; + } + return 0; +} + +/*---------------------------------------------------------------------. +| Sort the whole occurs table in memory. Presumably, `qsort' does not | +| take intermediate copies or table elements, so the sort will be | +| stabilized throughout the comparison routine. | +`---------------------------------------------------------------------*/ + +static void +sort_found_occurs (void) +{ + + /* Only one language for the time being. */ + + qsort (occurs_table[0], number_of_occurs[0], sizeof **occurs_table, + compare_occurs); +} + +/* Parameter files reading routines. */ + +/*----------------------------------------------------------------------. +| Read a file named FILE_NAME, containing a set of break characters. | +| Build a content to the array word_fastmap in which all characters are | +| allowed except those found in the file. Characters may be repeated. | +`----------------------------------------------------------------------*/ + +static void +digest_break_file (const char *file_name) +{ + BLOCK file_contents; /* to receive a copy of the file */ + char *cursor; /* cursor in file copy */ + + swallow_file_in_memory (file_name, &file_contents); + + /* Make the fastmap and record the file contents in it. */ + + memset (word_fastmap, 1, CHAR_SET_SIZE); + for (cursor = file_contents.start; cursor < file_contents.end; cursor++) + word_fastmap[to_uchar (*cursor)] = 0; + + if (!gnu_extensions) + { + + /* If GNU extensions are enabled, the only way to avoid newline as + a break character is to write all the break characters in the + file with no newline at all, not even at the end of the file. + If disabled, spaces, tabs and newlines are always considered as + break characters even if not included in the break file. */ + + word_fastmap[' '] = 0; + word_fastmap['\t'] = 0; + word_fastmap['\n'] = 0; + } + + /* Return the space of the file, which is no more required. */ + + free (file_contents.start); +} + +/*-----------------------------------------------------------------------. +| Read a file named FILE_NAME, containing one word per line, then | +| construct in TABLE a table of WORD descriptors for them. The routine | +| swallows the whole file in memory; this is at the expense of space | +| needed for newlines, which are useless; however, the reading is fast. | +`-----------------------------------------------------------------------*/ + +static void +digest_word_file (const char *file_name, WORD_TABLE *table) +{ + BLOCK file_contents; /* to receive a copy of the file */ + char *cursor; /* cursor in file copy */ + char *word_start; /* start of the current word */ + + swallow_file_in_memory (file_name, &file_contents); + + table->start = NULL; + table->alloc = 0; + table->length = 0; + + /* Read the whole file. */ + + cursor = file_contents.start; + while (cursor < file_contents.end) + { + + /* Read one line, and save the word in contains. */ + + word_start = cursor; + while (cursor < file_contents.end && *cursor != '\n') + cursor++; + + /* Record the word in table if it is not empty. */ + + if (cursor > word_start) + { + if (table->length == table->alloc) + { + if ((SIZE_MAX / sizeof *table->start - 1) / 2 < table->alloc) + xalloc_die (); + table->alloc = table->alloc * 2 + 1; + table->start = xrealloc (table->start, + table->alloc * sizeof *table->start); + } + + table->start[table->length].start = word_start; + table->start[table->length].size = cursor - word_start; + table->length++; + } + + /* This test allows for an incomplete line at end of file. */ + + if (cursor < file_contents.end) + cursor++; + } + + /* Finally, sort all the words read. */ + + qsort (table->start, table->length, sizeof table->start[0], compare_words); +} + +/* Keyword recognition and selection. */ + +/*----------------------------------------------------------------------. +| For each keyword in the source text, constructs an OCCURS structure. | +`----------------------------------------------------------------------*/ + +static void +find_occurs_in_text (void) +{ + char *cursor; /* for scanning the source text */ + char *scan; /* for scanning the source text also */ + char *line_start; /* start of the current input line */ + char *line_scan; /* newlines scanned until this point */ + int reference_length; /* length of reference in input mode */ + WORD possible_key; /* possible key, to ease searches */ + OCCURS *occurs_cursor; /* current OCCURS under construction */ + + char *context_start; /* start of left context */ + char *context_end; /* end of right context */ + char *word_start; /* start of word */ + char *word_end; /* end of word */ + char *next_context_start; /* next start of left context */ + + /* reference_length is always used within `if (input_reference)'. + However, GNU C diagnoses that it may be used uninitialized. The + following assignment is merely to shut it up. */ + + reference_length = 0; + + /* Tracking where lines start is helpful for reference processing. In + auto reference mode, this allows counting lines. In input reference + mode, this permits finding the beginning of the references. + + The first line begins with the file, skip immediately this very first + reference in input reference mode, to help further rejection any word + found inside it. Also, unconditionally assigning these variable has + the happy effect of shutting up lint. */ + + line_start = text_buffer.start; + line_scan = line_start; + if (input_reference) + { + SKIP_NON_WHITE (line_scan, text_buffer.end); + reference_length = line_scan - line_start; + SKIP_WHITE (line_scan, text_buffer.end); + } + + /* Process the whole buffer, one line or one sentence at a time. */ + + for (cursor = text_buffer.start; + cursor < text_buffer.end; + cursor = next_context_start) + { + + /* `context_start' gets initialized before the processing of each + line, or once for the whole buffer if no end of line or sentence + sequence separator. */ + + context_start = cursor; + + /* If a end of line or end of sentence sequence is defined and + non-empty, `next_context_start' will be recomputed to be the end of + each line or sentence, before each one is processed. If no such + sequence, then `next_context_start' is set at the end of the whole + buffer, which is then considered to be a single line or sentence. + This test also accounts for the case of an incomplete line or + sentence at the end of the buffer. */ + + next_context_start = text_buffer.end; + if (context_regex.string) + switch (re_search (&context_regex.pattern, cursor, + text_buffer.end - cursor, + 0, text_buffer.end - cursor, &context_regs)) + { + case -2: + matcher_error (); + + case -1: + break; + + default: + next_context_start = cursor + context_regs.end[0]; + break; + } + + /* Include the separator into the right context, but not any suffix + white space in this separator; this insures it will be seen in + output and will not take more space than necessary. */ + + context_end = next_context_start; + SKIP_WHITE_BACKWARDS (context_end, context_start); + + /* Read and process a single input line or sentence, one word at a + time. */ + + while (1) + { + if (word_regex.string) + + /* If a word regexp has been compiled, use it to skip at the + beginning of the next word. If there is no such word, exit + the loop. */ + + { + regoff_t r = re_search (&word_regex.pattern, cursor, + context_end - cursor, + 0, context_end - cursor, &word_regs); + if (r == -2) + matcher_error (); + if (r == -1) + break; + word_start = cursor + word_regs.start[0]; + word_end = cursor + word_regs.end[0]; + } + else + + /* Avoid re_search and use the fastmap to skip to the + beginning of the next word. If there is no more word in + the buffer, exit the loop. */ + + { + scan = cursor; + while (scan < context_end + && !word_fastmap[to_uchar (*scan)]) + scan++; + + if (scan == context_end) + break; + + word_start = scan; + + while (scan < context_end + && word_fastmap[to_uchar (*scan)]) + scan++; + + word_end = scan; + } + + /* Skip right to the beginning of the found word. */ + + cursor = word_start; + + /* Skip any zero length word. Just advance a single position, + then go fetch the next word. */ + + if (word_end == word_start) + { + cursor++; + continue; + } + + /* This is a genuine, non empty word, so save it as a possible + key. Then skip over it. Also, maintain the maximum length of + all words read so far. It is mandatory to take the maximum + length of all words in the file, without considering if they + are actually kept or rejected, because backward jumps at output + generation time may fall in *any* word. */ + + possible_key.start = cursor; + possible_key.size = word_end - word_start; + cursor += possible_key.size; + + if (possible_key.size > maximum_word_length) + maximum_word_length = possible_key.size; + + /* In input reference mode, update `line_start' from its previous + value. Count the lines just in case auto reference mode is + also selected. If it happens that the word just matched is + indeed part of a reference; just ignore it. */ + + if (input_reference) + { + while (line_scan < possible_key.start) + if (*line_scan == '\n') + { + total_line_count++; + line_scan++; + line_start = line_scan; + SKIP_NON_WHITE (line_scan, text_buffer.end); + reference_length = line_scan - line_start; + } + else + line_scan++; + if (line_scan > possible_key.start) + continue; + } + + /* Ignore the word if an `Ignore words' table exists and if it is + part of it. Also ignore the word if an `Only words' table and + if it is *not* part of it. + + It is allowed that both tables be used at once, even if this + may look strange for now. Just ignore a word that would appear + in both. If regexps are eventually implemented for these + tables, the Ignore table could then reject words that would + have been previously accepted by the Only table. */ + + if (ignore_file && search_table (&possible_key, &ignore_table)) + continue; + if (only_file && !search_table (&possible_key, &only_table)) + continue; + + /* A non-empty word has been found. First of all, insure + proper allocation of the next OCCURS, and make a pointer to + where it will be constructed. */ + + if (number_of_occurs[0] == occurs_alloc[0]) + { + if ((SIZE_MAX / sizeof *occurs_table[0] - 1) / 2 + < occurs_alloc[0]) + xalloc_die (); + occurs_alloc[0] = occurs_alloc[0] * 2 + 1; + occurs_table[0] = xrealloc (occurs_table[0], + occurs_alloc[0] * sizeof *occurs_table[0]); + } + + occurs_cursor = occurs_table[0] + number_of_occurs[0]; + + /* Define the refence field, if any. */ + + if (auto_reference) + { + + /* While auto referencing, update `line_start' from its + previous value, counting lines as we go. If input + referencing at the same time, `line_start' has been + advanced earlier, and the following loop is never really + executed. */ + + while (line_scan < possible_key.start) + if (*line_scan == '\n') + { + total_line_count++; + line_scan++; + line_start = line_scan; + SKIP_NON_WHITE (line_scan, text_buffer.end); + } + else + line_scan++; + + occurs_cursor->reference = total_line_count; + } + else if (input_reference) + { + + /* If only input referencing, `line_start' has been computed + earlier to detect the case the word matched would be part + of the reference. The reference position is simply the + value of `line_start'. */ + + occurs_cursor->reference + = (DELTA) (line_start - possible_key.start); + if (reference_length > reference_max_width) + reference_max_width = reference_length; + } + + /* Exclude the reference from the context in simple cases. */ + + if (input_reference && line_start == context_start) + { + SKIP_NON_WHITE (context_start, context_end); + SKIP_WHITE (context_start, context_end); + } + + /* Completes the OCCURS structure. */ + + occurs_cursor->key = possible_key; + occurs_cursor->left = context_start - possible_key.start; + occurs_cursor->right = context_end - possible_key.start; + + number_of_occurs[0]++; + } + } +} + +/* Formatting and actual output - service routines. */ + +/*-----------------------------------------. +| Prints some NUMBER of spaces on stdout. | +`-----------------------------------------*/ + +static void +print_spaces (int number) +{ + int counter; + + for (counter = number; counter > 0; counter--) + putchar (' '); +} + +/*-------------------------------------. +| Prints the field provided by FIELD. | +`-------------------------------------*/ + +static void +print_field (BLOCK field) +{ + char *cursor; /* Cursor in field to print */ + int base; /* Base character, without diacritic */ + int diacritic; /* Diacritic code for the character */ + + /* Whitespace is not really compressed. Instead, each white space + character (tab, vt, ht etc.) is printed as one single space. */ + + for (cursor = field.start; cursor < field.end; cursor++) + { + unsigned char character = *cursor; + if (edited_flag[character]) + { + + /* First check if this is a diacriticized character. + + This works only for TeX. I do not know how diacriticized + letters work with `roff'. Please someone explain it to me! */ + + diacritic = todiac (character); + if (diacritic != 0 && output_format == TEX_FORMAT) + { + base = tobase (character); + switch (diacritic) + { + + case 1: /* Latin diphthongs */ + switch (base) + { + case 'o': + fputs ("\\oe{}", stdout); + break; + + case 'O': + fputs ("\\OE{}", stdout); + break; + + case 'a': + fputs ("\\ae{}", stdout); + break; + + case 'A': + fputs ("\\AE{}", stdout); + break; + + default: + putchar (' '); + } + break; + + case 2: /* Acute accent */ + printf ("\\'%s%c", (base == 'i' ? "\\" : ""), base); + break; + + case 3: /* Grave accent */ + printf ("\\`%s%c", (base == 'i' ? "\\" : ""), base); + break; + + case 4: /* Circumflex accent */ + printf ("\\^%s%c", (base == 'i' ? "\\" : ""), base); + break; + + case 5: /* Diaeresis */ + printf ("\\\"%s%c", (base == 'i' ? "\\" : ""), base); + break; + + case 6: /* Tilde accent */ + printf ("\\~%s%c", (base == 'i' ? "\\" : ""), base); + break; + + case 7: /* Cedilla */ + printf ("\\c{%c}", base); + break; + + case 8: /* Small circle beneath */ + switch (base) + { + case 'a': + fputs ("\\aa{}", stdout); + break; + + case 'A': + fputs ("\\AA{}", stdout); + break; + + default: + putchar (' '); + } + break; + + case 9: /* Strike through */ + switch (base) + { + case 'o': + fputs ("\\o{}", stdout); + break; + + case 'O': + fputs ("\\O{}", stdout); + break; + + default: + putchar (' '); + } + break; + } + } + else + + /* This is not a diacritic character, so handle cases which are + really specific to `roff' or TeX. All white space processing + is done as the default case of this switch. */ + + switch (character) + { + case '"': + /* In roff output format, double any quote. */ + putchar ('"'); + putchar ('"'); + break; + + case '$': + case '%': + case '&': + case '#': + case '_': + /* In TeX output format, precede these with a backslash. */ + putchar ('\\'); + putchar (character); + break; + + case '{': + case '}': + /* In TeX output format, precede these with a backslash and + force mathematical mode. */ + printf ("$\\%c$", character); + break; + + case '\\': + /* In TeX output mode, request production of a backslash. */ + fputs ("\\backslash{}", stdout); + break; + + default: + /* Any other flagged character produces a single space. */ + putchar (' '); + } + } + else + putchar (*cursor); + } +} + +/* Formatting and actual output - planning routines. */ + +/*--------------------------------------------------------------------. +| From information collected from command line options and input file | +| readings, compute and fix some output parameter values. | +`--------------------------------------------------------------------*/ + +static void +fix_output_parameters (void) +{ + int file_index; /* index in text input file arrays */ + int line_ordinal; /* line ordinal value for reference */ + char ordinal_string[12]; /* edited line ordinal for reference */ + int reference_width; /* width for the whole reference */ + int character; /* character ordinal */ + const char *cursor; /* cursor in some constant strings */ + + /* In auto reference mode, the maximum width of this field is + precomputed and subtracted from the overall line width. Add one for + the column which separate the file name from the line number. */ + + if (auto_reference) + { + reference_max_width = 0; + for (file_index = 0; file_index < number_input_files; file_index++) + { + line_ordinal = file_line_count[file_index] + 1; + if (file_index > 0) + line_ordinal -= file_line_count[file_index - 1]; + sprintf (ordinal_string, "%d", line_ordinal); + reference_width = strlen (ordinal_string); + if (input_file_name[file_index]) + reference_width += strlen (input_file_name[file_index]); + if (reference_width > reference_max_width) + reference_max_width = reference_width; + } + reference_max_width++; + reference.start = xmalloc ((size_t) reference_max_width + 1); + } + + /* If the reference appears to the left of the output line, reserve some + space for it right away, including one gap size. */ + + if ((auto_reference | input_reference) & !right_reference) + line_width -= reference_max_width + gap_size; + + /* The output lines, minimally, will contain from left to right a left + context, a gap, and a keyword followed by the right context with no + special intervening gap. Half of the line width is dedicated to the + left context and the gap, the other half is dedicated to the keyword + and the right context; these values are computed once and for all here. + There also are tail and head wrap around fields, used when the keyword + is near the beginning or the end of the line, or when some long word + cannot fit in, but leave place from wrapped around shorter words. The + maximum width of these fields are recomputed separately for each line, + on a case by case basis. It is worth noting that it cannot happen that + both the tail and head fields are used at once. */ + + half_line_width = line_width / 2; + before_max_width = half_line_width - gap_size; + keyafter_max_width = half_line_width; + + /* If truncation_string is the empty string, make it NULL to speed up + tests. In this case, truncation_string_length will never get used, so + there is no need to set it. */ + + if (truncation_string && *truncation_string) + truncation_string_length = strlen (truncation_string); + else + truncation_string = NULL; + + if (gnu_extensions) + { + + /* When flagging truncation at the left of the keyword, the + truncation mark goes at the beginning of the before field, + unless there is a head field, in which case the mark goes at the + left of the head field. When flagging truncation at the right + of the keyword, the mark goes at the end of the keyafter field, + unless there is a tail field, in which case the mark goes at the + end of the tail field. Only eight combination cases could arise + for truncation marks: + + . None. + . One beginning the before field. + . One beginning the head field. + . One ending the keyafter field. + . One ending the tail field. + . One beginning the before field, another ending the keyafter field. + . One ending the tail field, another beginning the before field. + . One ending the keyafter field, another beginning the head field. + + So, there is at most two truncation marks, which could appear both + on the left side of the center of the output line, both on the + right side, or one on either side. */ + + before_max_width -= 2 * truncation_string_length; + keyafter_max_width -= 2 * truncation_string_length; + } + else + { + + /* I never figured out exactly how UNIX' ptx plans the output width + of its various fields. If GNU extensions are disabled, do not + try computing the field widths correctly; instead, use the + following formula, which does not completely imitate UNIX' ptx, + but almost. */ + + keyafter_max_width -= 2 * truncation_string_length + 1; + } + + /* Compute which characters need special output processing. Initialize + by flagging any white space character. Some systems do not consider + form feed as a space character, but we do. */ + + for (character = 0; character < CHAR_SET_SIZE; character++) + edited_flag[character] = !! isspace (character); + edited_flag['\f'] = 1; + + /* Complete the special character flagging according to selected output + format. */ + + switch (output_format) + { + case UNKNOWN_FORMAT: + /* Should never happen. */ + + case DUMB_FORMAT: + break; + + case ROFF_FORMAT: + + /* `Quote' characters should be doubled. */ + + edited_flag['"'] = 1; + break; + + case TEX_FORMAT: + + /* Various characters need special processing. */ + + for (cursor = "$%&#_{}\\"; *cursor; cursor++) + edited_flag[to_uchar (*cursor)] = 1; + + /* Any character with 8th bit set will print to a single space, unless + it is diacriticized. */ + + for (character = 0200; character < CHAR_SET_SIZE; character++) + edited_flag[character] = todiac (character) != 0; + break; + } +} + +/*------------------------------------------------------------------. +| Compute the position and length of all the output fields, given a | +| pointer to some OCCURS. | +`------------------------------------------------------------------*/ + +static void +define_all_fields (OCCURS *occurs) +{ + int tail_max_width; /* allowable width of tail field */ + int head_max_width; /* allowable width of head field */ + char *cursor; /* running cursor in source text */ + char *left_context_start; /* start of left context */ + char *right_context_end; /* end of right context */ + char *left_field_start; /* conservative start for `head'/`before' */ + int file_index; /* index in text input file arrays */ + const char *file_name; /* file name for reference */ + int line_ordinal; /* line ordinal for reference */ + + /* Define `keyafter', start of left context and end of right context. + `keyafter' starts at the saved position for keyword and extend to the + right from the end of the keyword, eating separators or full words, but + not beyond maximum allowed width for `keyafter' field or limit for the + right context. Suffix spaces will be removed afterwards. */ + + keyafter.start = occurs->key.start; + keyafter.end = keyafter.start + occurs->key.size; + left_context_start = keyafter.start + occurs->left; + right_context_end = keyafter.start + occurs->right; + + cursor = keyafter.end; + while (cursor < right_context_end + && cursor <= keyafter.start + keyafter_max_width) + { + keyafter.end = cursor; + SKIP_SOMETHING (cursor, right_context_end); + } + if (cursor <= keyafter.start + keyafter_max_width) + keyafter.end = cursor; + + keyafter_truncation = truncation_string && keyafter.end < right_context_end; + + SKIP_WHITE_BACKWARDS (keyafter.end, keyafter.start); + + /* When the left context is wide, it might take some time to catch up from + the left context boundary to the beginning of the `head' or `before' + fields. So, in this case, to speed the catchup, we jump back from the + keyword, using some secure distance, possibly falling in the middle of + a word. A secure backward jump would be at least half the maximum + width of a line, plus the size of the longest word met in the whole + input. We conclude this backward jump by a skip forward of at least + one word. In this manner, we should not inadvertently accept only part + of a word. From the reached point, when it will be time to fix the + beginning of `head' or `before' fields, we will skip forward words or + delimiters until we get sufficiently near. */ + + if (-occurs->left > half_line_width + maximum_word_length) + { + left_field_start + = keyafter.start - (half_line_width + maximum_word_length); + SKIP_SOMETHING (left_field_start, keyafter.start); + } + else + left_field_start = keyafter.start + occurs->left; + + /* `before' certainly ends at the keyword, but not including separating + spaces. It starts after than the saved value for the left context, by + advancing it until it falls inside the maximum allowed width for the + before field. There will be no prefix spaces either. `before' only + advances by skipping single separators or whole words. */ + + before.start = left_field_start; + before.end = keyafter.start; + SKIP_WHITE_BACKWARDS (before.end, before.start); + + while (before.start + before_max_width < before.end) + SKIP_SOMETHING (before.start, before.end); + + if (truncation_string) + { + cursor = before.start; + SKIP_WHITE_BACKWARDS (cursor, text_buffer.start); + before_truncation = cursor > left_context_start; + } + else + before_truncation = 0; + + SKIP_WHITE (before.start, text_buffer.end); + + /* The tail could not take more columns than what has been left in the + left context field, and a gap is mandatory. It starts after the + right context, and does not contain prefixed spaces. It ends at + the end of line, the end of buffer or when the tail field is full, + whichever comes first. It cannot contain only part of a word, and + has no suffixed spaces. */ + + tail_max_width + = before_max_width - (before.end - before.start) - gap_size; + + if (tail_max_width > 0) + { + tail.start = keyafter.end; + SKIP_WHITE (tail.start, text_buffer.end); + + tail.end = tail.start; + cursor = tail.end; + while (cursor < right_context_end + && cursor < tail.start + tail_max_width) + { + tail.end = cursor; + SKIP_SOMETHING (cursor, right_context_end); + } + + if (cursor < tail.start + tail_max_width) + tail.end = cursor; + + if (tail.end > tail.start) + { + keyafter_truncation = 0; + tail_truncation = truncation_string && tail.end < right_context_end; + } + else + tail_truncation = 0; + + SKIP_WHITE_BACKWARDS (tail.end, tail.start); + } + else + { + + /* No place left for a tail field. */ + + tail.start = NULL; + tail.end = NULL; + tail_truncation = 0; + } + + /* `head' could not take more columns than what has been left in the right + context field, and a gap is mandatory. It ends before the left + context, and does not contain suffixed spaces. Its pointer is advanced + until the head field has shrunk to its allowed width. It cannot + contain only part of a word, and has no suffixed spaces. */ + + head_max_width + = keyafter_max_width - (keyafter.end - keyafter.start) - gap_size; + + if (head_max_width > 0) + { + head.end = before.start; + SKIP_WHITE_BACKWARDS (head.end, text_buffer.start); + + head.start = left_field_start; + while (head.start + head_max_width < head.end) + SKIP_SOMETHING (head.start, head.end); + + if (head.end > head.start) + { + before_truncation = 0; + head_truncation = (truncation_string + && head.start > left_context_start); + } + else + head_truncation = 0; + + SKIP_WHITE (head.start, head.end); + } + else + { + + /* No place left for a head field. */ + + head.start = NULL; + head.end = NULL; + head_truncation = 0; + } + + if (auto_reference) + { + + /* Construct the reference text in preallocated space from the file + name and the line number. Find out in which file the reference + occurred. Standard input yields an empty file name. Insure line + numbers are one based, even if they are computed zero based. */ + + file_index = 0; + while (file_line_count[file_index] < occurs->reference) + file_index++; + + file_name = input_file_name[file_index]; + if (!file_name) + file_name = ""; + + line_ordinal = occurs->reference + 1; + if (file_index > 0) + line_ordinal -= file_line_count[file_index - 1]; + + sprintf (reference.start, "%s:%d", file_name, line_ordinal); + reference.end = reference.start + strlen (reference.start); + } + else if (input_reference) + { + + /* Reference starts at saved position for reference and extends right + until some white space is met. */ + + reference.start = keyafter.start + (DELTA) occurs->reference; + reference.end = reference.start; + SKIP_NON_WHITE (reference.end, right_context_end); + } +} + +/* Formatting and actual output - control routines. */ + +/*----------------------------------------------------------------------. +| Output the current output fields as one line for `troff' or `nroff'. | +`----------------------------------------------------------------------*/ + +static void +output_one_roff_line (void) +{ + /* Output the `tail' field. */ + + printf (".%s \"", macro_name); + print_field (tail); + if (tail_truncation) + fputs (truncation_string, stdout); + putchar ('"'); + + /* Output the `before' field. */ + + fputs (" \"", stdout); + if (before_truncation) + fputs (truncation_string, stdout); + print_field (before); + putchar ('"'); + + /* Output the `keyafter' field. */ + + fputs (" \"", stdout); + print_field (keyafter); + if (keyafter_truncation) + fputs (truncation_string, stdout); + putchar ('"'); + + /* Output the `head' field. */ + + fputs (" \"", stdout); + if (head_truncation) + fputs (truncation_string, stdout); + print_field (head); + putchar ('"'); + + /* Conditionally output the `reference' field. */ + + if (auto_reference | input_reference) + { + fputs (" \"", stdout); + print_field (reference); + putchar ('"'); + } + + putchar ('\n'); +} + +/*---------------------------------------------------------. +| Output the current output fields as one line for `TeX'. | +`---------------------------------------------------------*/ + +static void +output_one_tex_line (void) +{ + BLOCK key; /* key field, isolated */ + BLOCK after; /* after field, isolated */ + char *cursor; /* running cursor in source text */ + + printf ("\\%s ", macro_name); + putchar ('{'); + print_field (tail); + fputs ("}{", stdout); + print_field (before); + fputs ("}{", stdout); + key.start = keyafter.start; + after.end = keyafter.end; + cursor = keyafter.start; + SKIP_SOMETHING (cursor, keyafter.end); + key.end = cursor; + after.start = cursor; + print_field (key); + fputs ("}{", stdout); + print_field (after); + fputs ("}{", stdout); + print_field (head); + putchar ('}'); + if (auto_reference | input_reference) + { + putchar ('{'); + print_field (reference); + putchar ('}'); + } + putchar ('\n'); +} + +/*-------------------------------------------------------------------. +| Output the current output fields as one line for a dumb terminal. | +`-------------------------------------------------------------------*/ + +static void +output_one_dumb_line (void) +{ + if (!right_reference) + { + if (auto_reference) + { + + /* Output the `reference' field, in such a way that GNU emacs + next-error will handle it. The ending colon is taken from the + gap which follows. */ + + print_field (reference); + putchar (':'); + print_spaces (reference_max_width + + gap_size + - (reference.end - reference.start) + - 1); + } + else + { + + /* Output the `reference' field and its following gap. */ + + print_field (reference); + print_spaces (reference_max_width + + gap_size + - (reference.end - reference.start)); + } + } + + if (tail.start < tail.end) + { + /* Output the `tail' field. */ + + print_field (tail); + if (tail_truncation) + fputs (truncation_string, stdout); + + print_spaces (half_line_width - gap_size + - (before.end - before.start) + - (before_truncation ? truncation_string_length : 0) + - (tail.end - tail.start) + - (tail_truncation ? truncation_string_length : 0)); + } + else + print_spaces (half_line_width - gap_size + - (before.end - before.start) + - (before_truncation ? truncation_string_length : 0)); + + /* Output the `before' field. */ + + if (before_truncation) + fputs (truncation_string, stdout); + print_field (before); + + print_spaces (gap_size); + + /* Output the `keyafter' field. */ + + print_field (keyafter); + if (keyafter_truncation) + fputs (truncation_string, stdout); + + if (head.start < head.end) + { + /* Output the `head' field. */ + + print_spaces (half_line_width + - (keyafter.end - keyafter.start) + - (keyafter_truncation ? truncation_string_length : 0) + - (head.end - head.start) + - (head_truncation ? truncation_string_length : 0)); + if (head_truncation) + fputs (truncation_string, stdout); + print_field (head); + } + else + + if ((auto_reference | input_reference) & right_reference) + print_spaces (half_line_width + - (keyafter.end - keyafter.start) + - (keyafter_truncation ? truncation_string_length : 0)); + + if ((auto_reference | input_reference) & right_reference) + { + /* Output the `reference' field. */ + + print_spaces (gap_size); + print_field (reference); + } + + putchar ('\n'); +} + +/*------------------------------------------------------------------------. +| Scan the whole occurs table and, for each entry, output one line in the | +| appropriate format. | +`------------------------------------------------------------------------*/ + +static void +generate_all_output (void) +{ + size_t occurs_index; /* index of keyword entry being processed */ + OCCURS *occurs_cursor; /* current keyword entry being processed */ + + /* The following assignments are useful to provide default values in case + line contexts or references are not used, in which case these variables + would never be computed. */ + + tail.start = NULL; + tail.end = NULL; + tail_truncation = 0; + + head.start = NULL; + head.end = NULL; + head_truncation = 0; + + /* Loop over all keyword occurrences. */ + + occurs_cursor = occurs_table[0]; + + for (occurs_index = 0; occurs_index < number_of_occurs[0]; occurs_index++) + { + /* Compute the exact size of every field and whenever truncation flags + are present or not. */ + + define_all_fields (occurs_cursor); + + /* Produce one output line according to selected format. */ + + switch (output_format) + { + case UNKNOWN_FORMAT: + /* Should never happen. */ + + case DUMB_FORMAT: + output_one_dumb_line (); + break; + + case ROFF_FORMAT: + output_one_roff_line (); + break; + + case TEX_FORMAT: + output_one_tex_line (); + break; + } + + /* Advance the cursor into the occurs table. */ + + occurs_cursor++; + } +} + +/* Option decoding and main program. */ + +/*------------------------------------------------------. +| Print program identification and options, then exit. | +`------------------------------------------------------*/ + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [INPUT]... (without -G)\n\ + or: %s -G [OPTION]... [INPUT [OUTPUT]]\n"), + program_name, program_name); + fputs (_("\ +Output a permuted index, including context, of the words in the input files.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -A, --auto-reference output automatically generated references\n\ + -G, --traditional behave more like System V `ptx'\n\ + -F, --flag-truncation=STRING use STRING for flagging line truncations\n\ +"), stdout); + fputs (_("\ + -M, --macro-name=STRING macro name to use instead of `xx'\n\ + -O, --format=roff generate output as roff directives\n\ + -R, --right-side-refs put references at right, not counted in -w\n\ + -S, --sentence-regexp=REGEXP for end of lines or end of sentences\n\ + -T, --format=tex generate output as TeX directives\n\ +"), stdout); + fputs (_("\ + -W, --word-regexp=REGEXP use REGEXP to match each keyword\n\ + -b, --break-file=FILE word break characters in this FILE\n\ + -f, --ignore-case fold lower case to upper case for sorting\n\ + -g, --gap-size=NUMBER gap size in columns between output fields\n\ + -i, --ignore-file=FILE read ignore word list from FILE\n\ + -o, --only-file=FILE read only word list from this FILE\n\ +"), stdout); + fputs (_("\ + -r, --references first field of each line is a reference\n\ + -t, --typeset-mode - not implemented -\n\ + -w, --width=NUMBER output width in columns, reference excluded\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +With no FILE or if FILE is -, read Standard Input. `-F /' by default.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/*----------------------------------------------------------------------. +| Main program. Decode ARGC arguments passed through the ARGV array of | +| strings, then launch execution. | +`----------------------------------------------------------------------*/ + +/* Long options equivalences. */ +static const struct option long_options[] = +{ + {"auto-reference", no_argument, NULL, 'A'}, + {"break-file", required_argument, NULL, 'b'}, + {"copyright", no_argument, NULL, 'C'}, /* Deprecated, remove in 2007. */ + {"flag-truncation", required_argument, NULL, 'F'}, + {"ignore-case", no_argument, NULL, 'f'}, + {"gap-size", required_argument, NULL, 'g'}, + {"ignore-file", required_argument, NULL, 'i'}, + {"macro-name", required_argument, NULL, 'M'}, + {"only-file", required_argument, NULL, 'o'}, + {"references", no_argument, NULL, 'r'}, + {"right-side-refs", no_argument, NULL, 'R'}, + {"format", required_argument, NULL, 10}, + {"sentence-regexp", required_argument, NULL, 'S'}, + {"traditional", no_argument, NULL, 'G'}, + {"typeset-mode", no_argument, NULL, 't'}, + {"width", required_argument, NULL, 'w'}, + {"word-regexp", required_argument, NULL, 'W'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0}, +}; + +static char const* const format_args[] = +{ + "roff", "tex", NULL +}; + +static enum Format const format_vals[] = +{ + ROFF_FORMAT, TEX_FORMAT +}; + +int +main (int argc, char **argv) +{ + int optchar; /* argument character */ + int file_index; /* index in text input file arrays */ + + /* Decode program options. */ + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + +#if HAVE_SETCHRCLASS + setchrclass (NULL); +#endif + + while (optchar = getopt_long (argc, argv, "ACF:GM:ORS:TW:b:i:fg:o:trw:", + long_options, NULL), + optchar != EOF) + { + switch (optchar) + { + default: + usage (EXIT_FAILURE); + + case 'G': + gnu_extensions = false; + break; + + case 'b': + break_file = optarg; + break; + + case 'f': + ignore_case = true; + break; + + case 'g': + { + unsigned long int tmp_ulong; + if (xstrtoul (optarg, NULL, 0, &tmp_ulong, NULL) != LONGINT_OK + || ! (0 < tmp_ulong && tmp_ulong <= INT_MAX)) + error (EXIT_FAILURE, 0, _("invalid gap width: %s"), + quotearg (optarg)); + gap_size = tmp_ulong; + break; + } + + case 'i': + ignore_file = optarg; + break; + + case 'o': + only_file = optarg; + break; + + case 'r': + input_reference = true; + break; + + case 't': + /* Yet to understand... */ + break; + + case 'w': + { + unsigned long int tmp_ulong; + if (xstrtoul (optarg, NULL, 0, &tmp_ulong, NULL) != LONGINT_OK + || ! (0 < tmp_ulong && tmp_ulong <= INT_MAX)) + error (EXIT_FAILURE, 0, _("invalid line width: %s"), + quotearg (optarg)); + line_width = tmp_ulong; + break; + } + + case 'A': + auto_reference = true; + break; + + case 'F': + truncation_string = copy_unescaped_string (optarg); + break; + + case 'M': + macro_name = optarg; + break; + + case 'O': + output_format = ROFF_FORMAT; + break; + + case 'R': + right_reference = true; + break; + + case 'S': + context_regex.string = copy_unescaped_string (optarg); + break; + + case 'T': + output_format = TEX_FORMAT; + break; + + case 'W': + word_regex.string = copy_unescaped_string (optarg); + if (!*word_regex.string) + word_regex.string = NULL; + break; + + case 10: + output_format = XARGMATCH ("--format", optarg, + format_args, format_vals); + case_GETOPT_HELP_CHAR; + + case 'C': + error (0, 0, _("\ +the --copyright option is deprecated; use --version instead")); + /* fallthrough */ + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + } + } + + /* Process remaining arguments. If GNU extensions are enabled, process + all arguments as input parameters. If disabled, accept at most two + arguments, the second of which is an output parameter. */ + + if (optind == argc) + { + + /* No more argument simply means: read standard input. */ + + input_file_name = xmalloc (sizeof *input_file_name); + file_line_count = xmalloc (sizeof *file_line_count); + number_input_files = 1; + input_file_name[0] = NULL; + } + else if (gnu_extensions) + { + number_input_files = argc - optind; + input_file_name = xmalloc (number_input_files * sizeof *input_file_name); + file_line_count = xmalloc (number_input_files * sizeof *file_line_count); + + for (file_index = 0; file_index < number_input_files; file_index++) + { + input_file_name[file_index] = argv[optind]; + if (!*argv[optind] || STREQ (argv[optind], "-")) + input_file_name[0] = NULL; + else + input_file_name[0] = argv[optind]; + optind++; + } + } + else + { + + /* There is one necessary input file. */ + + number_input_files = 1; + input_file_name = xmalloc (sizeof *input_file_name); + file_line_count = xmalloc (sizeof *file_line_count); + if (!*argv[optind] || STREQ (argv[optind], "-")) + input_file_name[0] = NULL; + else + input_file_name[0] = argv[optind]; + optind++; + + /* Redirect standard output, only if requested. */ + + if (optind < argc) + { + if (! freopen (argv[optind], "w", stdout)) + error (EXIT_FAILURE, errno, "%s", argv[optind]); + optind++; + } + + /* Diagnose any other argument as an error. */ + + if (optind < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind])); + usage (EXIT_FAILURE); + } + } + + /* If the output format has not been explicitly selected, choose dumb + terminal format if GNU extensions are enabled, else `roff' format. */ + + if (output_format == UNKNOWN_FORMAT) + output_format = gnu_extensions ? DUMB_FORMAT : ROFF_FORMAT; + + /* Initialize the main tables. */ + + initialize_regex (); + + /* Read `Break character' file, if any. */ + + if (break_file) + digest_break_file (break_file); + + /* Read `Ignore words' file and `Only words' files, if any. If any of + these files is empty, reset the name of the file to NULL, to avoid + unnecessary calls to search_table. */ + + if (ignore_file) + { + digest_word_file (ignore_file, &ignore_table); + if (ignore_table.length == 0) + ignore_file = NULL; + } + + if (only_file) + { + digest_word_file (only_file, &only_table); + if (only_table.length == 0) + only_file = NULL; + } + + /* Prepare to study all the input files. */ + + number_of_occurs[0] = 0; + total_line_count = 0; + maximum_word_length = 0; + reference_max_width = 0; + + for (file_index = 0; file_index < number_input_files; file_index++) + { + + /* Read the file in core, than study it. */ + + swallow_file_in_memory (input_file_name[file_index], &text_buffer); + find_occurs_in_text (); + + /* Maintain for each file how many lines has been read so far when its + end is reached. Incrementing the count first is a simple kludge to + handle a possible incomplete line at end of file. */ + + total_line_count++; + file_line_count[file_index] = total_line_count; + } + + /* Do the output process phase. */ + + sort_found_occurs (); + fix_output_parameters (); + generate_all_output (); + + /* All done. */ + + exit (EXIT_SUCCESS); +} diff --git a/src/pwd.c b/src/pwd.c new file mode 100644 index 0000000..4f16b73 --- /dev/null +++ b/src/pwd.c @@ -0,0 +1,323 @@ +/* pwd - print current directory + Copyright (C) 1994-1997, 1999-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include +#include +#include +#include + +#include "system.h" +#include "dirfd.h" +#include "error.h" +#include "long-options.h" +#include "quote.h" +#include "root-dev-ino.h" +#include "xgetcwd.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "pwd" + +#define AUTHORS "Jim Meyering" + +struct file_name +{ + char *buf; + size_t n_alloc; + char *start; +}; + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]\n"), program_name); + fputs (_("\ +Print the full filename of the current working directory.\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +static void +file_name_free (struct file_name *p) +{ + free (p->buf); + free (p); +} + +static struct file_name * +file_name_init (void) +{ + struct file_name *p = xmalloc (sizeof *p); + + /* Start with a buffer larger than PATH_MAX, but beware of systems + on which PATH_MAX is very large -- e.g., INT_MAX. */ + p->n_alloc = MIN (2 * PATH_MAX, 32 * 1024); + + p->buf = xmalloc (p->n_alloc); + p->start = p->buf + (p->n_alloc - 1); + p->start[0] = '\0'; + return p; +} + +/* Prepend the name S of length S_LEN, to the growing file_name, P. */ +static void +file_name_prepend (struct file_name *p, char const *s, size_t s_len) +{ + size_t n_free = p->start - p->buf; + if (n_free < 1 + s_len) + { + size_t half = p->n_alloc + 1 + s_len; + /* Use xnmalloc+free rather than xnrealloc, since with the latter + we'd end up copying the data twice: once via realloc, then again + to align it with the end of the new buffer. With xnmalloc, we + copy it only once. */ + char *q = xnmalloc (2, half); + size_t n_used = p->n_alloc - n_free; + p->start = q + 2 * half - n_used; + memcpy (p->start, p->buf + n_free, n_used); + free (p->buf); + p->buf = q; + p->n_alloc = 2 * half; + } + + p->start -= 1 + s_len; + p->start[0] = '/'; + memcpy (p->start + 1, s, s_len); +} + +/* Return a string (malloc'd) consisting of N `/'-separated ".." components. */ +static char * +nth_parent (size_t n) +{ + char *buf = xnmalloc (3, n); + char *p = buf; + size_t i; + + for (i = 0; i < n; i++) + { + memcpy (p, "../", 3); + p += 3; + } + p[-1] = '\0'; + return buf; +} + +/* Determine the basename of the current directory, where DOT_SB is the + result of lstat'ing "." and prepend that to the file name in *FILE_NAME. + Find the directory entry in `..' that matches the dev/i-node of DOT_SB. + Upon success, update *DOT_SB with stat information of `..', chdir to `..', + and prepend "/basename" to FILE_NAME. + Otherwise, exit with a diagnostic. + PARENT_HEIGHT is the number of levels `..' is above the starting directory. + The first time this function is called (from the initial directory), + PARENT_HEIGHT is 1. This is solely for diagnostics. + Exit nonzero upon error. */ + +static void +find_dir_entry (struct stat *dot_sb, struct file_name *file_name, + size_t parent_height) +{ + DIR *dirp; + int fd; + struct stat parent_sb; + bool use_lstat; + bool found; + + dirp = opendir (".."); + if (dirp == NULL) + error (EXIT_FAILURE, errno, _("cannot open directory %s"), + quote (nth_parent (parent_height))); + + fd = dirfd (dirp); + if ((0 <= fd ? fchdir (fd) : chdir ("..")) < 0) + error (EXIT_FAILURE, errno, _("failed to chdir to %s"), + quote (nth_parent (parent_height))); + + if ((0 <= fd ? fstat (fd, &parent_sb) : stat (".", &parent_sb)) < 0) + error (EXIT_FAILURE, errno, _("failed to stat %s"), + quote (nth_parent (parent_height))); + + /* If parent and child directory are on different devices, then we + can't rely on d_ino for useful i-node numbers; use lstat instead. */ + use_lstat = (parent_sb.st_dev != dot_sb->st_dev); + + found = false; + while (1) + { + struct dirent const *dp; + struct stat ent_sb; + ino_t ino; + + errno = 0; + if ((dp = readdir_ignoring_dot_and_dotdot (dirp)) == NULL) + { + if (errno) + { + /* Save/restore errno across closedir call. */ + int e = errno; + closedir (dirp); + errno = e; + + /* Arrange to give a diagnostic after exiting this loop. */ + dirp = NULL; + } + break; + } + + ino = D_INO (dp); + + if (ino == NOT_AN_INODE_NUMBER || use_lstat) + { + if (lstat (dp->d_name, &ent_sb) < 0) + { + /* Skip any entry we can't stat. */ + continue; + } + ino = ent_sb.st_ino; + } + + if (ino != dot_sb->st_ino) + continue; + + /* If we're not crossing a device boundary, then a simple i-node + match is enough. */ + if ( ! use_lstat || ent_sb.st_dev == dot_sb->st_dev) + { + file_name_prepend (file_name, dp->d_name, _D_EXACT_NAMLEN (dp)); + found = true; + break; + } + } + + if (dirp == NULL || closedir (dirp) != 0) + { + /* Note that this diagnostic serves for both readdir + and closedir failures. */ + error (EXIT_FAILURE, errno, _("reading directory %s"), + quote (nth_parent (parent_height))); + } + + if ( ! found) + error (EXIT_FAILURE, 0, + _("couldn't find directory entry in %s with matching i-node"), + quote (nth_parent (parent_height))); + + *dot_sb = parent_sb; +} + +/* Construct the full, absolute name of the current working + directory and store it in *FILE_NAME. + The getcwd function performs nearly the same task, but is typically + unable to handle names longer than PATH_MAX. This function has + no such limitation. However, this function *can* fail due to + permission problems or a lack of memory, while Linux's getcwd + function works regardless of restricted permissions on parent + directories. Upon failure, give a diagnostic and exit nonzero. + + Note: although this function is similar to getcwd, it has a fundamental + difference in that it gives a diagnostic and exits upon failure. + I would have liked a function that did not exit, and that could be + used as a getcwd replacement. Unfortunately, considering all of + the information the caller would require in order to produce good + diagnostics, it doesn't seem worth the added complexity. + In any case, any getcwd replacement must *not* exceed the PATH_MAX + limitation. Otherwise, functions like `chdir' would fail with + ENAMETOOLONG. + + FIXME-maybe: if find_dir_entry fails due to permissions, try getcwd, + in case the unreadable directory is close enough to the root that + getcwd works from there. */ + +static void +robust_getcwd (struct file_name *file_name) +{ + size_t height = 1; + struct dev_ino dev_ino_buf; + struct dev_ino *root_dev_ino = get_root_dev_ino (&dev_ino_buf); + struct stat dot_sb; + + if (root_dev_ino == NULL) + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quote ("/")); + + if (stat (".", &dot_sb) < 0) + error (EXIT_FAILURE, errno, _("failed to stat %s"), quote (".")); + + while (1) + { + /* If we've reached the root, we're done. */ + if (SAME_INODE (dot_sb, *root_dev_ino)) + break; + + find_dir_entry (&dot_sb, file_name, height++); + } + + /* See if a leading slash is needed; file_name_prepend adds one. */ + if (file_name->start[0] == '\0') + file_name_prepend (file_name, "", 0); +} + +int +main (int argc, char **argv) +{ + char *wd; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (optind < argc) + error (0, 0, _("ignoring non-option arguments")); + + wd = xgetcwd (); + if (wd != NULL) + { + puts (wd); + free (wd); + } + else + { + struct file_name *file_name = file_name_init (); + robust_getcwd (file_name); + puts (file_name->start); + file_name_free (file_name); + } + + exit (EXIT_SUCCESS); +} diff --git a/src/readlink.c b/src/readlink.c new file mode 100644 index 0000000..121c7ff --- /dev/null +++ b/src/readlink.c @@ -0,0 +1,174 @@ +/* readlink -- display value of a symbolic link. + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Dmitry V. Levin */ + +#include +#include +#include +#include + +#include "system.h" +#include "canonicalize.h" +#include "error.h" +#include "xreadlink.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "readlink" + +#define AUTHORS "Dmitry V. Levin" + +/* Name this program was run with. */ +char *program_name; + +/* If true, do not output the trailing newline. */ +static bool no_newline; + +/* If true, report error messages. */ +static bool verbose; + +static struct option const longopts[] = +{ + {"canonicalize", no_argument, NULL, 'f'}, + {"canonicalize-existing", no_argument, NULL, 'e'}, + {"canonicalize-missing", no_argument, NULL, 'm'}, + {"no-newline", no_argument, NULL, 'n'}, + {"quiet", no_argument, NULL, 'q'}, + {"silent", no_argument, NULL, 's'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... FILE\n"), program_name); + fputs (_("Display value of a symbolic link on standard output.\n\n"), + stdout); + fputs (_("\ + -f, --canonicalize canonicalize by following every symlink in\n\ + every component of the given name recursively;\n\ + all but the last component must exist\n\ + -e, --canonicalize-existing canonicalize by following every symlink in\n\ + every component of the given name recursively,\n\ + all components must exist\n\ +"), stdout); + fputs (_("\ + -m, --canonicalize-missing canonicalize by following every symlink in\n\ + every component of the given name recursively,\n\ + without requirements on components existence\n\ + -n, --no-newline do not output the trailing newline\n\ + -q, --quiet,\n\ + -s, --silent suppress most error messages\n\ + -v, --verbose report error messages\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + /* If not -1, use this method to canonicalize. */ + int can_mode = -1; + + /* File name to canonicalize. */ + const char *fname; + + /* Result of canonicalize. */ + char *value; + + int optc; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "efmnqsv", longopts, NULL)) != -1) + { + switch (optc) + { + case 'e': + can_mode = CAN_EXISTING; + break; + case 'f': + can_mode = CAN_ALL_BUT_LAST; + break; + case 'm': + can_mode = CAN_MISSING; + break; + case 'n': + no_newline = true; + break; + case 'q': + case 's': + verbose = false; + break; + case 'v': + verbose = true; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + if (optind >= argc) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + fname = argv[optind++]; + + if (optind < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind])); + usage (EXIT_FAILURE); + } + + value = (can_mode != -1 + ? canonicalize_filename_mode (fname, can_mode) + : xreadlink (fname)); + if (value) + { + printf ("%s%s", value, (no_newline ? "" : "\n")); + free (value); + return EXIT_SUCCESS; + } + + if (verbose) + error (EXIT_FAILURE, errno, "%s", fname); + + return EXIT_FAILURE; +} diff --git a/src/remove.c b/src/remove.c new file mode 100644 index 0000000..59ee9e5 --- /dev/null +++ b/src/remove.c @@ -0,0 +1,1565 @@ +/* remove.c -- core functions for removing files and directories + Copyright (C) 88, 90, 91, 1994-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Extracted from rm.c and librarified, then rewritten by Jim Meyering. */ + +#include +#include +#include +#include +#include + +#include "system.h" +#include "cycle-check.h" +#include "dirfd.h" +#include "error.h" +#include "euidaccess.h" +#include "euidaccess-stat.h" +#include "file-type.h" +#include "hash.h" +#include "hash-pjw.h" +#include "lstat.h" +#include "obstack.h" +#include "openat.h" +#include "quote.h" +#include "remove.h" +#include "root-dev-ino.h" +#include "unlinkdir.h" +#include "yesno.h" + +/* Avoid shadowing warnings because these are functions declared + in dirname.h as well as locals used below. */ +#define dir_name rm_dir_name +#define dir_len rm_dir_len + +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free + +/* This is the maximum number of consecutive readdir/unlink calls that + can be made (with no intervening rewinddir or closedir/opendir) before + triggering a bug that makes readdir return NULL even though some + directory entries have not been processed. The bug afflicts SunOS's + readdir when applied to ufs file systems and Darwin 6.5's (and OSX + v.10.3.8's) HFS+. This maximum is conservative in that demonstrating + the problem requires a directory containing at least 16 deletable + entries (which doesn't count . and ..). + This problem also affects Darwin 7.9.0 (aka MacOS X 10.3.9) on HFS+ + and NFS-mounted file systems, but not vfat ones. */ +enum + { + CONSECUTIVE_READDIR_UNLINK_THRESHOLD = 10 + }; + +/* FIXME: in 2009, or whenever Darwin 7.9.0 (aka MacOS X 10.3.9) is no + longer relevant, remove this work-around code. Then, there will be + no need to perform the extra rewinddir call, ever. */ +#define NEED_REWIND(readdir_unlink_count) \ + (CONSECUTIVE_READDIR_UNLINK_THRESHOLD <= (readdir_unlink_count)) + +enum Ternary + { + T_UNKNOWN = 2, + T_NO, + T_YES + }; +typedef enum Ternary Ternary; + +/* The prompt function may be called twice for a given directory. + The first time, we ask whether to descend into it, and the + second time, we ask whether to remove it. */ +enum Prompt_action + { + PA_DESCEND_INTO_DIR = 2, + PA_REMOVE_DIR + }; + +/* Initial capacity of per-directory hash table of entries that have + been processed but not been deleted. */ +enum { HT_UNREMOVABLE_INITIAL_CAPACITY = 13 }; + +/* An entry in the active directory stack. + Each entry corresponds to an `active' directory. */ +struct AD_ent +{ + /* For a given active directory, this is the set of names of + entries in that directory that could/should not be removed. + For example, `.' and `..', as well as files/dirs for which + unlink/rmdir failed e.g., due to access restrictions. */ + Hash_table *unremovable; + + /* Record the status for a given active directory; we need to know + whether an entry was not removed, either because of an error or + because the user declined. */ + enum RM_status status; + + /* The directory's dev/ino. Used to ensure that a malicious user does + not replace a directory we're about to process with a symlink to + some other directory. */ + struct dev_ino dev_ino; +}; + +extern char *program_name; + +struct dirstack_state +{ + /* The name of the directory (starting with and relative to a command + line argument) being processed. When a subdirectory is entered, a new + component is appended (pushed). Remove (pop) the top component + upon chdir'ing out of a directory. This is used to form the full + name of the current directory or a file therein, when necessary. */ + struct obstack dir_stack; + + /* Stack of lengths of directory names (including trailing slash) + appended to dir_stack. We have to have a separate stack of lengths + (rather than just popping back to previous slash) because the first + element pushed onto the dir stack may contain slashes. */ + struct obstack len_stack; + + /* Stack of active directory entries. + The first `active' directory is the initial working directory. + Additional active dirs are pushed onto the stack as we `chdir' + into each directory to be processed. When finished with the + hierarchy under a directory, pop the active dir stack. */ + struct obstack Active_dir; + + /* Used to detect cycles. */ + struct cycle_check_state cycle_check_state; + + /* Target of a longjmp in case rm has to stop processing the current + command-line argument. This happens 1) when rm detects a directory + cycle or 2) when it has processed one or more directories, but then + is unable to return to the initial working directory to process + additional `.'-relative command-line arguments. */ + jmp_buf current_arg_jumpbuf; +}; +typedef struct dirstack_state Dirstack_state; + +/* Like fstatat, but cache the result. If ST->st_size is -1, the + status has not been gotten yet. If less than -1, fstatat failed + with errno == -1 - ST->st_size. Otherwise, the status has already + been gotten, so return 0. */ +static int +cache_fstatat (int fd, char const *file, struct stat *st, int flag) +{ + if (st->st_size == -1 && fstatat (fd, file, st, flag) != 0) + st->st_size = -1 - errno; + if (0 <= st->st_size) + return 0; + errno = -1 - st->st_size; + return -1; +} + +/* Initialize a fstatat cache *ST. Return ST for convenience. */ +static inline struct stat * +cache_stat_init (struct stat *st) +{ + st->st_size = -1; + return st; +} + +/* Return true if *ST has been statted. */ +static inline bool +cache_statted (struct stat *st) +{ + return (st->st_size != -1); +} + +/* Return true if *ST has been statted successfully. */ +static inline bool +cache_stat_ok (struct stat *st) +{ + return (0 <= st->st_size); +} + + +static void +hash_freer (void *x) +{ + free (x); +} + +static bool +hash_compare_strings (void const *x, void const *y) +{ + return STREQ (x, y) ? true : false; +} + +static inline void +push_dir (Dirstack_state *ds, const char *dir_name) +{ + size_t len = strlen (dir_name); + + /* Append the string onto the stack. */ + obstack_grow (&ds->dir_stack, dir_name, len); + + /* Append a trailing slash. */ + obstack_1grow (&ds->dir_stack, '/'); + + /* Add one for the slash. */ + ++len; + + /* Push the length (including slash) onto its stack. */ + obstack_grow (&ds->len_stack, &len, sizeof (len)); +} + +/* Return the entry name of the directory on the top of the stack + in malloc'd storage. */ +static inline char * +top_dir (Dirstack_state const *ds) +{ + size_t n_lengths = obstack_object_size (&ds->len_stack) / sizeof (size_t); + size_t *length = obstack_base (&ds->len_stack); + size_t top_len = length[n_lengths - 1]; + char const *p = obstack_next_free (&ds->dir_stack) - top_len; + char *q = xmalloc (top_len); + memcpy (q, p, top_len - 1); + q[top_len - 1] = 0; + return q; +} + +static inline void +pop_dir (Dirstack_state *ds) +{ + size_t n_lengths = obstack_object_size (&ds->len_stack) / sizeof (size_t); + size_t *length = obstack_base (&ds->len_stack); + + assert (n_lengths > 0); + size_t top_len = length[n_lengths - 1]; + assert (top_len >= 2); + + /* Pop the specified length of file name. */ + assert (obstack_object_size (&ds->dir_stack) >= top_len); + obstack_blank (&ds->dir_stack, -top_len); + + /* Pop the length stack, too. */ + assert (obstack_object_size (&ds->len_stack) >= sizeof (size_t)); + obstack_blank (&ds->len_stack, -(int) sizeof (size_t)); +} + +/* Copy the SRC_LEN bytes of data beginning at SRC into the DST_LEN-byte + buffer, DST, so that the last source byte is at the end of the destination + buffer. If SRC_LEN is longer than DST_LEN, then set *TRUNCATED. + Set *RESULT to point to the beginning of (the portion of) the source data + in DST. Return the number of bytes remaining in the destination buffer. */ + +static size_t +right_justify (char *dst, size_t dst_len, const char *src, size_t src_len, + char **result, bool *truncated) +{ + const char *sp; + char *dp; + + if (src_len <= dst_len) + { + sp = src; + dp = dst + (dst_len - src_len); + *truncated = false; + } + else + { + sp = src + (src_len - dst_len); + dp = dst; + src_len = dst_len; + *truncated = true; + } + + *result = memcpy (dp, sp, src_len); + return dst_len - src_len; +} + +/* Using the global directory name obstack, create the full name FILENAME. + Return it in sometimes-realloc'd space that should not be freed by the + caller. Realloc as necessary. If realloc fails, use a static buffer + and put as long a suffix in that buffer as possible. */ + +#define full_filename(Filename) full_filename_ (ds, Filename) +static char * +full_filename_ (Dirstack_state const *ds, const char *filename) +{ + static char *buf = NULL; + static size_t n_allocated = 0; + + size_t dir_len = obstack_object_size (&ds->dir_stack); + char *dir_name = obstack_base (&ds->dir_stack); + size_t n_bytes_needed; + size_t filename_len; + + filename_len = strlen (filename); + n_bytes_needed = dir_len + filename_len + 1; + + if (n_allocated < n_bytes_needed) + { + /* This code requires that realloc accept NULL as the first arg. + This function must not use xrealloc. Otherwise, an out-of-memory + error involving a file name to be expanded here wouldn't ever + be issued. Use realloc and fall back on using a static buffer + if memory allocation fails. */ + char *new_buf = realloc (buf, n_bytes_needed); + n_allocated = n_bytes_needed; + + if (new_buf == NULL) + { +#define SBUF_SIZE 512 +#define ELLIPSES_PREFIX "[...]" + static char static_buf[SBUF_SIZE]; + bool truncated; + size_t len; + char *p; + + free (buf); + len = right_justify (static_buf, SBUF_SIZE, filename, + filename_len + 1, &p, &truncated); + right_justify (static_buf, len, dir_name, dir_len, &p, &truncated); + if (truncated) + { + memcpy (static_buf, ELLIPSES_PREFIX, + sizeof (ELLIPSES_PREFIX) - 1); + } + return p; + } + + buf = new_buf; + } + + if (filename_len == 1 && *filename == '.' && dir_len) + { + /* FILENAME is just `.' and dir_len is nonzero. + Copy the directory part, omitting the trailing slash, + and append a trailing zero byte. */ + char *p = mempcpy (buf, dir_name, dir_len - 1); + *p = 0; + } + else + { + /* Copy the directory part, including trailing slash, and then + append the filename part, including a trailing zero byte. */ + memcpy (mempcpy (buf, dir_name, dir_len), filename, filename_len + 1); + assert (strlen (buf) + 1 == n_bytes_needed); + } + + return buf; +} + +static inline size_t +AD_stack_height (Dirstack_state const *ds) +{ + return obstack_object_size (&ds->Active_dir) / sizeof (struct AD_ent); +} + +static inline struct AD_ent * +AD_stack_top (Dirstack_state const *ds) +{ + return (struct AD_ent *) + ((char *) obstack_next_free (&ds->Active_dir) - sizeof (struct AD_ent)); +} + +static void +AD_stack_pop (Dirstack_state *ds) +{ + assert (0 < AD_stack_height (ds)); + + /* operate on Active_dir. pop and free top entry */ + struct AD_ent *top = AD_stack_top (ds); + if (top->unremovable) + hash_free (top->unremovable); + obstack_blank (&ds->Active_dir, -(int) sizeof (struct AD_ent)); +} + +static void +AD_stack_clear (Dirstack_state *ds) +{ + while (0 < AD_stack_height (ds)) + { + AD_stack_pop (ds); + } +} + +static Dirstack_state * +ds_init (void) +{ + Dirstack_state *ds = xmalloc (sizeof *ds); + obstack_init (&ds->dir_stack); + obstack_init (&ds->len_stack); + obstack_init (&ds->Active_dir); + return ds; +} + +static void +ds_clear (Dirstack_state *ds) +{ + obstack_free (&ds->dir_stack, obstack_finish (&ds->dir_stack)); + obstack_free (&ds->len_stack, obstack_finish (&ds->len_stack)); + while (0 < AD_stack_height (ds)) + AD_stack_pop (ds); + obstack_free (&ds->Active_dir, obstack_finish (&ds->Active_dir)); +} + +static void +ds_free (Dirstack_state *ds) +{ + obstack_free (&ds->dir_stack, NULL); + obstack_free (&ds->len_stack, NULL); + obstack_free (&ds->Active_dir, NULL); + free (ds); +} + +/* Pop the active directory (AD) stack and prepare to move `up' one level, + safely. Moving `up' usually means opening `..', but when we've just + finished recursively processing a command-line directory argument, + there's nothing left on the stack, so set *FDP to AT_FDCWD in that case. + The idea is to return with *FDP opened on the parent directory, + assuming there are entries in that directory that we need to remove. + + Note that we must not call opendir (or fdopendir) just yet, since + the caller must first remove the directory we're coming from. + That is because some file system implementations cache readdir + results at opendir time; so calling opendir, rmdir, readdir would + return an entry for the just-removed directory. + + Whenever using chdir '..' (virtually, now, via openat), verify + that the post-chdir dev/ino numbers for `.' match the saved ones. + If any system call fails or if dev/ino don't match, then give a + diagnostic and longjump out. + Return the name (in malloc'd storage) of the + directory (usually now empty) from which we're coming, and which + corresponds to the input value of DIRP. + + Finally, note that while this function's name is no longer as + accurate as it once was (it no longer calls chdir), it does open + the destination directory. */ +static char * +AD_pop_and_chdir (DIR *dirp, int *fdp, Dirstack_state *ds) +{ + struct AD_ent *leaf_dir_ent = AD_stack_top(ds); + struct dev_ino leaf_dev_ino = leaf_dir_ent->dev_ino; + enum RM_status old_status = leaf_dir_ent->status; + struct AD_ent *top; + + /* Get the name of the current (but soon to be `previous') directory + from the top of the stack. */ + char *prev_dir = top_dir (ds); + + AD_stack_pop (ds); + pop_dir (ds); + top = AD_stack_top (ds); + + /* If the directory we're about to leave (and try to rmdir) + is the one whose dev_ino is being used to detect a cycle, + reset cycle_check_state.dev_ino to that of the parent. + Otherwise, once that directory is removed, its dev_ino + could be reused in the creation (by some other process) + of a directory that this rm process would encounter, + which would result in a false-positive cycle indication. */ + CYCLE_CHECK_REFLECT_CHDIR_UP (&ds->cycle_check_state, + top->dev_ino, leaf_dev_ino); + + /* Propagate any failure to parent. */ + UPDATE_STATUS (top->status, old_status); + + assert (AD_stack_height (ds)); + + if (1 < AD_stack_height (ds)) + { + struct stat sb; + int fd = openat (dirfd (dirp), "..", O_RDONLY); + if (closedir (dirp) != 0) + { + error (0, errno, _("FATAL: failed to close directory %s"), + quote (full_filename (prev_dir))); + goto next_cmdline_arg; + } + + /* The above fails with EACCES when DIRP is readable but not + searchable, when using Solaris' openat. Without this openat + call, tests/rm2 would fail to remove directories a/2 and a/3. */ + if (fd < 0) + fd = openat (AT_FDCWD, full_filename ("."), O_RDONLY); + + if (fd < 0) + { + error (0, errno, _("FATAL: cannot open .. from %s"), + quote (full_filename (prev_dir))); + goto next_cmdline_arg; + } + + if (fstat (fd, &sb)) + { + error (0, errno, + _("FATAL: cannot ensure %s (returned to via ..) is safe"), + quote (full_filename ("."))); + goto close_and_next; + } + + /* Ensure that post-chdir dev/ino match the stored ones. */ + if ( ! SAME_INODE (sb, top->dev_ino)) + { + error (0, 0, _("FATAL: directory %s changed dev/ino"), + quote (full_filename ("."))); + close_and_next:; + close (fd); + + next_cmdline_arg:; + free (prev_dir); + longjmp (ds->current_arg_jumpbuf, 1); + } + *fdp = fd; + } + else + { + if (closedir (dirp) != 0) + { + error (0, errno, _("FATAL: failed to close directory %s"), + quote (full_filename (prev_dir))); + goto next_cmdline_arg; + } + *fdp = AT_FDCWD; + } + + return prev_dir; +} + +/* Initialize *HT if it is NULL. Return *HT. */ +static Hash_table * +AD_ensure_initialized (Hash_table **ht) +{ + if (*ht == NULL) + { + *ht = hash_initialize (HT_UNREMOVABLE_INITIAL_CAPACITY, NULL, hash_pjw, + hash_compare_strings, hash_freer); + if (*ht == NULL) + xalloc_die (); + } + + return *ht; +} + +/* Initialize *HT if it is NULL. + Insert FILENAME into HT. */ +static void +AD_mark_helper (Hash_table **ht, char *filename) +{ + void *ent = hash_insert (AD_ensure_initialized (ht), filename); + if (ent == NULL) + xalloc_die (); + else + { + if (ent != filename) + free (filename); + } +} + +/* Mark FILENAME (in current directory) as unremovable. */ +static void +AD_mark_as_unremovable (Dirstack_state *ds, char const *filename) +{ + AD_mark_helper (&AD_stack_top(ds)->unremovable, xstrdup (filename)); +} + +/* Mark the current directory as unremovable. I.e., mark the entry + in the parent directory corresponding to `.'. + This happens e.g., when an opendir fails and the only name + the caller has conveniently at hand is `.'. */ +static void +AD_mark_current_as_unremovable (Dirstack_state *ds) +{ + struct AD_ent *top = AD_stack_top (ds); + char *curr = top_dir (ds); + + assert (1 < AD_stack_height (ds)); + + --top; + AD_mark_helper (&top->unremovable, curr); +} + +/* Push an initial dummy entry onto the stack. + This will always be the bottommost entry on the stack. */ +static void +AD_push_initial (Dirstack_state *ds) +{ + struct AD_ent *top; + + /* Extend the stack. */ + obstack_blank (&ds->Active_dir, sizeof (struct AD_ent)); + + /* Fill in the new values. */ + top = AD_stack_top (ds); + top->unremovable = NULL; + + /* These should never be used. + Give them values that might look suspicious + in a debugger or in a diagnostic. */ + top->dev_ino.st_dev = TYPE_MAXIMUM (dev_t); + top->dev_ino.st_ino = TYPE_MAXIMUM (ino_t); +} + +/* Push info about the current working directory (".") onto the + active directory stack. DIR is the ./-relative name through + which we've just `chdir'd to this directory. DIR_SB_FROM_PARENT + is the result of calling lstat on DIR from the parent of DIR. + Longjump out (skipping the entire command line argument we're + dealing with) if `fstat (FD_CWD, ...' fails or if someone has + replaced DIR with e.g., a symlink to some other directory. */ +static void +AD_push (int fd_cwd, Dirstack_state *ds, char const *dir, + struct stat const *dir_sb_from_parent) +{ + struct AD_ent *top; + + push_dir (ds, dir); + + /* If our uses of openat are guaranteed not to + follow a symlink, then we can skip this check. */ + if (! HAVE_WORKING_O_NOFOLLOW) + { + struct stat sb; + if (fstat (fd_cwd, &sb) != 0) + { + error (0, errno, _("FATAL: cannot enter directory %s"), + quote (full_filename ("."))); + longjmp (ds->current_arg_jumpbuf, 1); + } + + if ( ! SAME_INODE (sb, *dir_sb_from_parent)) + { + error (0, 0, + _("FATAL: just-changed-to directory %s changed dev/ino"), + quote (full_filename ("."))); + longjmp (ds->current_arg_jumpbuf, 1); + } + } + + if (cycle_check (&ds->cycle_check_state, dir_sb_from_parent)) + { + error (0, 0, _("\ +WARNING: Circular directory structure.\n\ +This almost certainly means that you have a corrupted file system.\n\ +NOTIFY YOUR SYSTEM MANAGER.\n\ +The following directory is part of the cycle:\n %s\n"), + quote (full_filename ("."))); + longjmp (ds->current_arg_jumpbuf, 1); + } + + /* Extend the stack. */ + obstack_blank (&ds->Active_dir, sizeof (struct AD_ent)); + + /* The active directory stack must be one larger than the length stack. */ + assert (AD_stack_height (ds) == + 1 + obstack_object_size (&ds->len_stack) / sizeof (size_t)); + + /* Fill in the new values. */ + top = AD_stack_top (ds); + top->dev_ino.st_dev = dir_sb_from_parent->st_dev; + top->dev_ino.st_ino = dir_sb_from_parent->st_ino; + top->unremovable = NULL; +} + +static inline bool +AD_is_removable (Dirstack_state const *ds, char const *file) +{ + struct AD_ent *top = AD_stack_top (ds); + return ! (top->unremovable && hash_lookup (top->unremovable, file)); +} + +/* Return true if DIR is determined to be an empty directory. */ +static bool +is_empty_dir (int fd_cwd, char const *dir) +{ + DIR *dirp; + struct dirent const *dp; + int saved_errno; + int fd = openat (fd_cwd, dir, + (O_RDONLY | O_DIRECTORY + | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK)); + + if (fd < 0) + return false; + + dirp = fdopendir (fd); + if (dirp == NULL) + { + close (fd); + return false; + } + + errno = 0; + dp = readdir_ignoring_dot_and_dotdot (dirp); + saved_errno = errno; + closedir (dirp); + if (dp != NULL) + return false; + return saved_errno == 0 ? true : false; +} + +/* Return -1 if FILE is an unwritable non-symlink, + 0 if it is writable or some other type of file, + a positive error number if there is some problem in determining the answer. + Set *BUF to the file status. + This is to avoid calling euidaccess when FILE is a symlink. */ +static int +write_protected_non_symlink (int fd_cwd, + char const *file, + Dirstack_state const *ds, + struct stat *buf) +{ + if (cache_fstatat (fd_cwd, file, buf, AT_SYMLINK_NOFOLLOW) != 0) + return errno; + if (S_ISLNK (buf->st_mode)) + return 0; + /* Here, we know FILE is not a symbolic link. */ + + /* In order to be reentrant -- i.e., to avoid changing the working + directory, and at the same time to be able to deal with alternate + access control mechanisms (ACLs, xattr-style attributes) and + arbitrarily deep trees -- we need a function like eaccessat, i.e., + like Solaris' eaccess, but fd-relative, in the spirit of openat. */ + + /* In the absence of a native eaccessat function, here are some of + the implementation choices [#4 and #5 were suggested by Paul Eggert]: + 1) call openat with O_WRONLY|O_NOCTTY + Disadvantage: may create the file and doesn't work for directory, + may mistakenly report `unwritable' for EROFS or ACLs even though + perm bits say the file is writable. + + 2) fake eaccessat (save_cwd, fchdir, call euidaccess, restore_cwd) + Disadvantage: changes working directory (not reentrant) and can't + work if save_cwd fails. + + 3) if (euidaccess (full_filename (file), W_OK) == 0) + Disadvantage: doesn't work if full_filename is too long. + Inefficient for very deep trees (O(Depth^2)). + + 4) If the full pathname is sufficiently short (say, less than + PATH_MAX or 8192 bytes, whichever is shorter): + use method (3) (i.e., euidaccess (full_filename (file), W_OK)); + Otherwise: vfork, fchdir in the child, run euidaccess in the + child, then the child exits with a status that tells the parent + whether euidaccess succeeded. + + This avoids the O(N**2) algorithm of method (3), and it also avoids + the failure-due-to-too-long-file-names of method (3), but it's fast + in the normal shallow case. It also avoids the lack-of-reentrancy + and the save_cwd problems. + Disadvantage; it uses a process slot for very-long file names, + and would be very slow for hierarchies with many such files. + + 5) If the full file name is sufficiently short (say, less than + PATH_MAX or 8192 bytes, whichever is shorter): + use method (3) (i.e., euidaccess (full_filename (file), W_OK)); + Otherwise: look just at the file bits. Perhaps issue a warning + the first time this occurs. + + This is like (4), except for the "Otherwise" case where it isn't as + "perfect" as (4) but is considerably faster. It conforms to current + POSIX, and is uniformly better than what Solaris and FreeBSD do (they + mess up with long file names). */ + + { + /* This implements #5: */ + size_t file_name_len + = obstack_object_size (&ds->dir_stack) + strlen (file); + + if (MIN (PATH_MAX, 8192) <= file_name_len) + return - euidaccess_stat (buf, W_OK); + if (euidaccess (full_filename (file), W_OK) == 0) + return 0; + if (errno == EACCES) + return -1; + + /* Perhaps some other process has removed the file, or perhaps this + is a buggy NFS client. */ + return errno; + } +} + +/* Prompt whether to remove FILENAME, if required via a combination of + the options specified by X and/or file attributes. If the file may + be removed, return RM_OK. If the user declines to remove the file, + return RM_USER_DECLINED. If not ignoring missing files and we + cannot lstat FILENAME, then return RM_ERROR. + + Depending on MODE, ask whether to `descend into' or to `remove' the + directory FILENAME. MODE is ignored when FILENAME is not a directory. + Set *IS_EMPTY to T_YES if FILENAME is an empty directory, and it is + appropriate to try to remove it with rmdir (e.g. recursive mode). + Don't even try to set *IS_EMPTY when MODE == PA_REMOVE_DIR. */ +static enum RM_status +prompt (int fd_cwd, Dirstack_state const *ds, char const *filename, + struct stat *sbuf, + struct rm_options const *x, enum Prompt_action mode, + Ternary *is_empty) +{ + int write_protected = 0; + + *is_empty = T_UNKNOWN; + + if (x->interactive == RMI_NEVER) + return RM_OK; + + if (!x->ignore_missing_files + & ((x->interactive == RMI_ALWAYS) | x->stdin_tty)) + write_protected = write_protected_non_symlink (fd_cwd, filename, ds, sbuf); + + if (write_protected || x->interactive == RMI_ALWAYS) + { + if (write_protected <= 0 + && cache_fstatat (fd_cwd, filename, sbuf, AT_SYMLINK_NOFOLLOW) != 0) + { + /* This happens, e.g., with `rm '''. */ + write_protected = errno; + } + + if (write_protected <= 0) + { + /* Using permissions doesn't make sense for symlinks. */ + if (S_ISLNK (sbuf->st_mode) && x->interactive != RMI_ALWAYS) + return RM_OK; + + if (S_ISDIR (sbuf->st_mode) && !x->recursive) + write_protected = EISDIR; + } + + char const *quoted_name = quote (full_filename (filename)); + + if (0 < write_protected) + { + error (0, write_protected, _("cannot remove %s"), quoted_name); + return RM_ERROR; + } + + /* Issue the prompt. */ + /* FIXME: use a variant of error (instead of fprintf) that doesn't + append a newline. Then we won't have to declare program_name in + this file. */ + if (S_ISDIR (sbuf->st_mode) + && x->recursive + && mode == PA_DESCEND_INTO_DIR + && ((*is_empty = (is_empty_dir (fd_cwd, filename) ? T_YES : T_NO)) + == T_NO)) + fprintf (stderr, + (write_protected + ? _("%s: descend into write-protected directory %s? ") + : _("%s: descend into directory %s? ")), + program_name, quoted_name); + else + { + /* TRANSLATORS: You may find it more convenient to translate + the equivalent of _("%s: remove %s (write-protected) %s? "). + It should avoid grammatical problems with the output + of file_type. */ + fprintf (stderr, + (write_protected + ? _("%s: remove write-protected %s %s? ") + : _("%s: remove %s %s? ")), + program_name, file_type (sbuf), quoted_name); + } + + if (!yesno ()) + return RM_USER_DECLINED; + } + return RM_OK; +} + +/* Return true if FILENAME is a directory (and not a symlink to a directory). + Otherwise, including the case in which lstat fails, return false. + *ST is FILENAME's tstatus. + Do not modify errno. */ +static inline bool +is_dir_lstat (char const *filename, struct stat *st) +{ + int saved_errno = errno; + bool is_dir = + (cache_fstatat (AT_FDCWD, filename, st, AT_SYMLINK_NOFOLLOW) == 0 + && S_ISDIR (st->st_mode)); + errno = saved_errno; + return is_dir; +} + +#if HAVE_STRUCT_DIRENT_D_TYPE + +/* True if the type of the directory entry D is known. */ +# define DT_IS_KNOWN(d) ((d)->d_type != DT_UNKNOWN) + +/* True if the type of the directory entry D must be T. */ +# define DT_MUST_BE(d, t) ((d)->d_type == (t)) + +#else +# define DT_IS_KNOWN(d) false +# define DT_MUST_BE(d, t) false +#endif + +#define DO_UNLINK(Fd_cwd, Filename, X) \ + do \ + { \ + if (unlinkat (Fd_cwd, Filename, 0) == 0) \ + { \ + if ((X)->verbose) \ + printf (_("removed %s\n"), quote (full_filename (Filename))); \ + return RM_OK; \ + } \ + \ + if (ignorable_missing (X, errno)) \ + return RM_OK; \ + } \ + while (0) + +#define DO_RMDIR(Fd_cwd, Filename, X) \ + do \ + { \ + if (unlinkat (Fd_cwd, Filename, AT_REMOVEDIR) == 0) /* rmdir */ \ + { \ + if ((X)->verbose) \ + printf (_("removed directory: %s\n"), \ + quote (full_filename (Filename))); \ + return RM_OK; \ + } \ + \ + if (ignorable_missing (X, errno)) \ + return RM_OK; \ + \ + if (errno == ENOTEMPTY || errno == EEXIST) \ + return RM_NONEMPTY_DIR; \ + } \ + while (0) + +/* When a function like unlink, rmdir, or fstatat fails with an errno + value of ERRNUM, return true if the specified file system object + is guaranteed not to exist; otherwise, return false. */ +static inline bool +nonexistent_file_errno (int errnum) +{ + /* Do not include ELOOP here, since the specified file may indeed + exist, but be (in)accessible only via too long a symlink chain. + Likewise for ENAMETOOLONG, since rm -f ./././.../foo may fail + if the "..." part expands to a long enough sequence of "./"s, + even though ./foo does indeed exist. */ + + switch (errnum) + { + case ENOENT: + case ENOTDIR: + return true; + default: + return false; + } +} + +/* Encapsulate the test for whether the errno value, ERRNUM, is ignorable. */ +static inline bool +ignorable_missing (struct rm_options const *x, int errnum) +{ + return x->ignore_missing_files && nonexistent_file_errno (errnum); +} + +/* Remove the file or directory specified by FILENAME. + Return RM_OK if it is removed, and RM_ERROR or RM_USER_DECLINED if not. + But if FILENAME specifies a non-empty directory, return RM_NONEMPTY_DIR. */ + +static enum RM_status +remove_entry (int fd_cwd, Dirstack_state const *ds, char const *filename, + struct stat *st, + struct rm_options const *x, struct dirent const *dp) +{ + Ternary is_empty_directory; + enum RM_status s = prompt (fd_cwd, ds, filename, st, x, PA_DESCEND_INTO_DIR, + &is_empty_directory); + bool known_to_be_dir = (cache_stat_ok (st) && S_ISDIR (st->st_mode)); + + if (s != RM_OK) + return s; + + /* Why bother with the following if/else block? Because on systems with + an unlink function that *can* unlink directories, we must determine the + type of each entry before removing it. Otherwise, we'd risk unlinking + an entire directory tree simply by unlinking a single directory; then + all the storage associated with that hierarchy would not be freed until + the next fsck. Not nice. To avoid that, on such slightly losing + systems, we need to call lstat to determine the type of each entry, + and that represents extra overhead that -- it turns out -- we can + avoid on non-losing systems, since there, unlink will never remove + a directory. Also, on systems where unlink may unlink directories, + we're forced to allow a race condition: we lstat a non-directory, then + go to unlink it, but in the mean time, a malicious someone could have + replaced it with a directory. */ + + if (cannot_unlink_dir ()) + { + if (known_to_be_dir && ! x->recursive) + { + error (0, EISDIR, _("cannot remove %s"), + quote (full_filename (filename))); + return RM_ERROR; + } + + /* is_empty_directory is set iff it's ok to use rmdir. + Note that it's set only in interactive mode -- in which case it's + an optimization that arranges so that the user is asked just + once whether to remove the directory. */ + if (is_empty_directory == T_YES) + DO_RMDIR (fd_cwd, filename, x); + + /* If we happen to know that FILENAME is a directory, return now + and let the caller remove it -- this saves the overhead of a failed + unlink call. If FILENAME is a command-line argument, then dp is NULL, + so we'll first try to unlink it. Using unlink here is ok, because it + cannot remove a directory. */ + if ((dp && DT_MUST_BE (dp, DT_DIR)) || known_to_be_dir) + return RM_NONEMPTY_DIR; + + DO_UNLINK (fd_cwd, filename, x); + + /* Upon a failed attempt to unlink a directory, most non-Linux systems + set errno to the POSIX-required value EPERM. In that case, change + errno to EISDIR so that we emit a better diagnostic. */ + if (! x->recursive && errno == EPERM && is_dir_lstat (filename, st)) + errno = EISDIR; + + if (! x->recursive + || (cache_stat_ok (st) && !S_ISDIR (st->st_mode))) + { + if (ignorable_missing (x, errno)) + return RM_OK; + + /* Either --recursive is not in effect, or the file cannot be a + directory. Report the unlink problem and fail. */ + error (0, errno, _("cannot remove %s"), + quote (full_filename (filename))); + return RM_ERROR; + } + assert (!cache_stat_ok (st) || S_ISDIR (st->st_mode)); + } + else + { + /* If we don't already know whether FILENAME is a directory, + find out now. Then, if it's a non-directory, we can use + unlink on it. */ + bool is_dir; + + if (cache_statted (st)) + is_dir = known_to_be_dir; + else + { + if (dp && DT_IS_KNOWN (dp)) + is_dir = DT_MUST_BE (dp, DT_DIR); + else + { + if (fstatat (fd_cwd, filename, st, AT_SYMLINK_NOFOLLOW)) + { + if (ignorable_missing (x, errno)) + return RM_OK; + + error (0, errno, _("cannot remove %s"), + quote (full_filename (filename))); + return RM_ERROR; + } + + is_dir = !! S_ISDIR (st->st_mode); + } + } + + if (! is_dir) + { + /* At this point, barring race conditions, FILENAME is known + to be a non-directory, so it's ok to try to unlink it. */ + DO_UNLINK (fd_cwd, filename, x); + + /* unlink failed with some other error code. report it. */ + error (0, errno, _("cannot remove %s"), + quote (full_filename (filename))); + return RM_ERROR; + } + + if (! x->recursive) + { + error (0, EISDIR, _("cannot remove %s"), + quote (full_filename (filename))); + return RM_ERROR; + } + + if (is_empty_directory == T_YES) + { + DO_RMDIR (fd_cwd, filename, x); + /* Don't diagnose any failure here. + It'll be detected when the caller tries another way. */ + } + } + + return RM_NONEMPTY_DIR; +} + +/* Given FD_CWD, the file descriptor for an open directory, + open its subdirectory F (F is already `known' to be a directory, + so if it is no longer one, someone is playing games), return a DIR* + pointer for F, and put F's `stat' data in *SUBDIR_SB. + Upon failure give a diagnostic and return NULL. + If PREV_ERRNO is nonzero, it is the errno value from a preceding failed + unlink- or rmdir-like system call -- use that value instead of ENOTDIR + if an opened file turns out not to be a directory. This is important + when the preceding non-dir-unlink failed due to e.g., EPERM or EACCES. + The caller must use a nonnnull CWD_ERRNO the first + time this function is called for each command-line-specified directory. + If CWD_ERRNO is not null, set *CWD_ERRNO to the appropriate error number + if this function fails to restore the initial working directory. + If it is null, report an error and exit if the working directory + isn't restored. */ +static DIR * +fd_to_subdirp (int fd_cwd, char const *f, + struct rm_options const *x, int prev_errno, + struct stat *subdir_sb, + int *cwd_errno ATTRIBUTE_UNUSED) +{ + int open_flags = O_RDONLY | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK; + int fd_sub = openat_permissive (fd_cwd, f, open_flags, 0, cwd_errno); + int saved_errno; + + /* Record dev/ino of F. We may compare them against saved values + to thwart any attempt to subvert the traversal. They are also used + to detect directory cycles. */ + if (fd_sub < 0) + return NULL; + else if (fstat (fd_sub, subdir_sb) != 0) + saved_errno = errno; + else if (S_ISDIR (subdir_sb->st_mode)) + { + DIR *subdir_dirp = fdopendir (fd_sub); + if (subdir_dirp) + return subdir_dirp; + saved_errno = errno; + } + else + saved_errno = (prev_errno ? prev_errno : ENOTDIR); + + close (fd_sub); + errno = saved_errno; + return NULL; +} + +/* Remove entries in the directory open on DIRP + Upon finding a directory that is both non-empty and that can be chdir'd + into, return RM_OK and set *SUBDIR and fill in SUBDIR_SB, where + SUBDIR is the malloc'd name of the subdirectory if the chdir succeeded, + NULL otherwise (e.g., if opendir failed or if there was no subdirectory). + Likewise, SUBDIR_SB is the result of calling lstat on SUBDIR. + Return RM_OK if all entries are removed. Return RM_ERROR if any + entry cannot be removed. Otherwise, return RM_USER_DECLINED if + the user declines to remove at least one entry. Remove as much as + possible, continuing even if we fail to remove some entries. */ +static enum RM_status +remove_cwd_entries (DIR **dirp, + Dirstack_state *ds, char **subdir, struct stat *subdir_sb, + struct rm_options const *x) +{ + struct AD_ent *top = AD_stack_top (ds); + enum RM_status status = top->status; + size_t n_unlinked_since_opendir_or_last_rewind = 0; + + assert (VALID_STATUS (status)); + *subdir = NULL; + + while (1) + { + struct dirent const *dp; + enum RM_status tmp_status; + const char *f; + + /* Set errno to zero so we can distinguish between a readdir failure + and when readdir simply finds that there are no more entries. */ + errno = 0; + dp = readdir_ignoring_dot_and_dotdot (*dirp); + if (dp == NULL) + { + if (errno) + { + /* fall through */ + } + else if (NEED_REWIND (n_unlinked_since_opendir_or_last_rewind)) + { + /* Call rewinddir if we've called unlink or rmdir so many times + (since the opendir or the previous rewinddir) that this + NULL-return may be the symptom of a buggy readdir. */ + rewinddir (*dirp); + n_unlinked_since_opendir_or_last_rewind = 0; + continue; + } + break; + } + + f = dp->d_name; + + /* Skip files we've already tried/failed to remove. */ + if ( ! AD_is_removable (ds, f)) + continue; + + /* Pass dp->d_type info to remove_entry so the non-glibc + case can decide whether to use unlink or chdir. + Systems without the d_type member will have to endure + the performance hit of first calling lstat F. */ + cache_stat_init (subdir_sb); + tmp_status = remove_entry (dirfd (*dirp), ds, f, subdir_sb, x, dp); + switch (tmp_status) + { + case RM_OK: + /* Count how many files we've unlinked since the initial + opendir or the last rewinddir. On buggy systems, if you + remove too many, readdir returns NULL even though there + remain unprocessed directory entries. */ + ++n_unlinked_since_opendir_or_last_rewind; + break; + + case RM_ERROR: + case RM_USER_DECLINED: + AD_mark_as_unremovable (ds, f); + UPDATE_STATUS (status, tmp_status); + break; + + case RM_NONEMPTY_DIR: + { + DIR *subdir_dirp = fd_to_subdirp (dirfd (*dirp), f, + x, errno, subdir_sb, NULL); + if (subdir_dirp == NULL) + { + status = RM_ERROR; + + /* CAUTION: this test and diagnostic are identical to + those following the other use of fd_to_subdirp. */ + if (ignorable_missing (x, errno)) + { + /* With -f, don't report "file not found". */ + } + else + { + /* Upon fd_to_subdirp failure, try to remove F directly, + in case it's just an empty directory. */ + int saved_errno = errno; + if (unlinkat (dirfd (*dirp), f, AT_REMOVEDIR) == 0) + status = RM_OK; + else + error (0, saved_errno, + _("cannot remove %s"), quote (full_filename (f))); + } + + if (status == RM_ERROR) + AD_mark_as_unremovable (ds, f); + break; + } + + *subdir = xstrdup (f); + if (closedir (*dirp) != 0) + { + error (0, 0, _("failed to close directory %s"), + quote (full_filename ("."))); + status = RM_ERROR; + } + *dirp = subdir_dirp; + + break; + } + } + + /* Record status for this directory. */ + UPDATE_STATUS (top->status, status); + + if (*subdir) + break; + } + + /* Ensure that *dirp is not NULL and that its file descriptor is valid. */ + assert (*dirp != NULL); + assert (0 <= fcntl (dirfd (*dirp), F_GETFD)); + + return status; +} + +/* Do this after each call to AD_push or AD_push_initial. + Because the status = RM_OK bit is too remove-specific to + go into the general-purpose AD_* package. */ +#define AD_INIT_OTHER_MEMBERS() \ + do \ + { \ + AD_stack_top(ds)->status = RM_OK; \ + } \ + while (0) + +/* Remove the hierarchy rooted at DIR. + Do that by changing into DIR, then removing its contents, then + returning to the original working directory and removing DIR itself. + Don't use recursion. Be careful when using chdir ".." that we + return to the same directory from which we came, if necessary. + Return an RM_status value to indicate success or failure. */ + +static enum RM_status +remove_dir (int fd_cwd, Dirstack_state *ds, char const *dir, + struct stat *dir_st, + struct rm_options const *x, int *cwd_errno) +{ + enum RM_status status; + dev_t current_dev = dir_st->st_dev; + + /* There is a race condition in that an attacker could replace the nonempty + directory, DIR, with a symlink between the preceding call to rmdir + (unlinkat, in our caller) and fd_to_subdirp's openat call. But on most + systems, even those without openat, this isn't a problem, since we ensure + that opening a symlink will fail, when that is possible. Otherwise, + fd_to_subdirp's fstat, along with the `fstat' and the dev/ino + comparison in AD_push ensure that we detect it and fail. */ + + DIR *dirp = fd_to_subdirp (fd_cwd, dir, x, 0, dir_st, cwd_errno); + + if (dirp == NULL) + { + /* CAUTION: this test and diagnostic are identical to + those following the other use of fd_to_subdirp. */ + if (ignorable_missing (x, errno)) + { + /* With -f, don't report "file not found". */ + } + else + { + /* Upon fd_to_subdirp failure, try to remove DIR directly, + in case it's just an empty directory. */ + int saved_errno = errno; + if (unlinkat (fd_cwd, dir, AT_REMOVEDIR) == 0) + return RM_OK; + + error (0, saved_errno, + _("cannot remove %s"), quote (full_filename (dir))); + } + + return RM_ERROR; + } + + if (ROOT_DEV_INO_CHECK (x->root_dev_ino, dir_st)) + { + ROOT_DEV_INO_WARN (full_filename (dir)); + status = RM_ERROR; + goto closedir_and_return; + } + + AD_push (dirfd (dirp), ds, dir, dir_st); + AD_INIT_OTHER_MEMBERS (); + + status = RM_OK; + + while (1) + { + char *subdir = NULL; + struct stat subdir_sb; + enum RM_status tmp_status; + + tmp_status = remove_cwd_entries (&dirp, ds, &subdir, &subdir_sb, x); + + if (tmp_status != RM_OK) + { + UPDATE_STATUS (status, tmp_status); + AD_mark_current_as_unremovable (ds); + } + if (subdir) + { + if ( ! x->one_file_system + || subdir_sb.st_dev == current_dev) + { + AD_push (dirfd (dirp), ds, subdir, &subdir_sb); + AD_INIT_OTHER_MEMBERS (); + free (subdir); + continue; + } + + /* Here, --one-file-system is in effect, and with remove_cwd_entries' + traversal into the current directory, (known as SUBDIR, from ..), + DIRP's device number is different from CURRENT_DEV. Arrange not + to do anything more with this hierarchy. */ + error (0, 0, _("skipping %s, since it's on a different device"), + quote (full_filename (subdir))); + free (subdir); + AD_mark_current_as_unremovable (ds); + tmp_status = RM_ERROR; + UPDATE_STATUS (status, tmp_status); + } + + /* Execution reaches this point when we've removed the last + removable entry from the current directory -- or, with + --one-file-system, when the current directory is on a + different file system. */ + { + int fd; + /* The name of the directory that we have just processed, + nominally removing all of its contents. */ + char *empty_dir = AD_pop_and_chdir (dirp, &fd, ds); + dirp = NULL; + assert (fd != AT_FDCWD || AD_stack_height (ds) == 1); + + /* Try to remove EMPTY_DIR only if remove_cwd_entries succeeded. */ + if (tmp_status == RM_OK) + { + /* This does a little more work than necessary when it actually + prompts the user. E.g., we already know that D is a directory + and that it's almost certainly empty, yet we lstat it. + But that's no big deal since we're interactive. */ + struct stat empty_st; + Ternary is_empty; + enum RM_status s = prompt (fd, ds, empty_dir, + cache_stat_init (&empty_st), x, + PA_REMOVE_DIR, &is_empty); + + if (s != RM_OK) + { + free (empty_dir); + status = s; + if (fd != AT_FDCWD) + close (fd); + goto closedir_and_return; + } + + if (unlinkat (fd, empty_dir, AT_REMOVEDIR) == 0) + { + if (x->verbose) + printf (_("removed directory: %s\n"), + quote (full_filename (empty_dir))); + } + else + { + error (0, errno, _("cannot remove directory %s"), + quote (full_filename (empty_dir))); + AD_mark_as_unremovable (ds, empty_dir); + status = RM_ERROR; + UPDATE_STATUS (AD_stack_top(ds)->status, status); + } + } + + free (empty_dir); + + if (fd == AT_FDCWD) + break; + + dirp = fdopendir (fd); + if (dirp == NULL) + { + error (0, errno, _("FATAL: cannot return to .. from %s"), + quote (full_filename ("."))); + close (fd); + longjmp (ds->current_arg_jumpbuf, 1); + } + } + } + + /* If the first/final hash table of unremovable entries was used, + free it here. */ + AD_stack_pop (ds); + + closedir_and_return:; + if (dirp != NULL && closedir (dirp) != 0) + { + error (0, 0, _("failed to close directory %s"), + quote (full_filename ("."))); + status = RM_ERROR; + } + + return status; +} + +/* Remove the file or directory specified by FILENAME. + Return RM_OK if it is removed, and RM_ERROR or RM_USER_DECLINED if not. */ + +static enum RM_status +rm_1 (Dirstack_state *ds, char const *filename, + struct rm_options const *x, int *cwd_errno) +{ + char const *base = last_component (filename); + if (dot_or_dotdot (base)) + { + error (0, 0, _(base == filename + ? "cannot remove directory %s" + : "cannot remove %s directory %s"), + quote_n (0, base), quote_n (1, filename)); + return RM_ERROR; + } + + struct stat st; + cache_stat_init (&st); + cycle_check_init (&ds->cycle_check_state); + if (x->root_dev_ino) + { + if (cache_fstatat (AT_FDCWD, filename, &st, AT_SYMLINK_NOFOLLOW) != 0) + { + if (ignorable_missing (x, errno)) + return RM_OK; + error (0, errno, _("cannot remove %s"), quote (filename)); + return RM_ERROR; + } + if (SAME_INODE (st, *(x->root_dev_ino))) + { + error (0, 0, _("cannot remove root directory %s"), quote (filename)); + return RM_ERROR; + } + } + + AD_push_initial (ds); + AD_INIT_OTHER_MEMBERS (); + + enum RM_status status = remove_entry (AT_FDCWD, ds, filename, &st, x, NULL); + if (status == RM_NONEMPTY_DIR) + { + /* In the event that remove_dir->remove_cwd_entries detects + a directory cycle, arrange to fail, give up on this FILE, but + continue on with any other arguments. */ + if (setjmp (ds->current_arg_jumpbuf)) + status = RM_ERROR; + else + status = remove_dir (AT_FDCWD, ds, filename, &st, x, cwd_errno); + + AD_stack_clear (ds); + } + + ds_clear (ds); + return status; +} + +/* Remove all files and/or directories specified by N_FILES and FILE. + Apply the options in X. */ +extern enum RM_status +rm (size_t n_files, char const *const *file, struct rm_options const *x) +{ + enum RM_status status = RM_OK; + Dirstack_state *ds = ds_init (); + int cwd_errno = 0; + size_t i; + + for (i = 0; i < n_files; i++) + { + if (cwd_errno && IS_RELATIVE_FILE_NAME (file[i])) + { + error (0, 0, _("cannot remove relative-named %s"), quote (file[i])); + status = RM_ERROR; + } + else + { + enum RM_status s = rm_1 (ds, file[i], x, &cwd_errno); + assert (VALID_STATUS (s)); + UPDATE_STATUS (status, s); + } + } + + if (x->require_restore_cwd && cwd_errno) + { + error (0, cwd_errno, + _("cannot restore current working directory")); + status = RM_ERROR; + } + + ds_free (ds); + + return status; +} diff --git a/src/remove.h b/src/remove.h new file mode 100644 index 0000000..ae01e3c --- /dev/null +++ b/src/remove.h @@ -0,0 +1,96 @@ +/* Remove directory entries. + + Copyright (C) 1998, 2000, 2002, 2003, 2004, 2005, 2006, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef REMOVE_H +# define REMOVE_H + +# include "dev-ino.h" + +enum rm_interactive +{ + /* Start with any number larger than 1, so that any legacy tests + against values of 0 or 1 will fail. */ + RMI_ALWAYS = 3, + RMI_SOMETIMES, + RMI_NEVER +}; + +struct rm_options +{ + /* If true, ignore nonexistent files. */ + bool ignore_missing_files; + + /* If true, query the user about whether to remove each file. */ + enum rm_interactive interactive; + + /* If true, do not traverse into (or remove) any directory that is + on a file system (i.e., that has a different device number) other + than that of the corresponding command line argument. Note that + even without this option, rm will fail in the end, due to its + probable inability to remove the mount point. But there, the + diagnostic comes too late -- after removing all contents. */ + bool one_file_system; + + /* If true, recursively remove directories. */ + bool recursive; + + /* Pointer to the device and inode numbers of `/', when --recursive + and preserving `/'. Otherwise NULL. */ + struct dev_ino *root_dev_ino; + + /* If nonzero, stdin is a tty. */ + bool stdin_tty; + + /* If true, display the name of each file removed. */ + bool verbose; + + /* If true, treat the failure by the rm function to restore the + current working directory as a fatal error. I.e., if this field + is true and the rm function cannot restore cwd, it must exit with + a nonzero status. Some applications require that the rm function + restore cwd (e.g., mv) and some others do not (e.g., rm, + in many cases). */ + bool require_restore_cwd; +}; + +enum RM_status +{ + /* These must be listed in order of increasing seriousness. */ + RM_OK = 2, + RM_USER_DECLINED, + RM_ERROR, + RM_NONEMPTY_DIR +}; + +# define VALID_STATUS(S) \ + ((S) == RM_OK || (S) == RM_USER_DECLINED || (S) == RM_ERROR) + +# define UPDATE_STATUS(S, New_value) \ + do \ + { \ + if ((New_value) == RM_ERROR \ + || ((New_value) == RM_USER_DECLINED && (S) == RM_OK)) \ + (S) = (New_value); \ + } \ + while (0) + +enum RM_status rm (size_t n_files, char const *const *file, + struct rm_options const *x); + +#endif diff --git a/src/rm.c b/src/rm.c new file mode 100644 index 0000000..81f81ec --- /dev/null +++ b/src/rm.c @@ -0,0 +1,374 @@ +/* `rm' file deletion utility for GNU. + Copyright (C) 88, 90, 91, 1994-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Paul Rubin, David MacKenzie, and Richard Stallman. + Reworked to use chdir and avoid recursion by Jim Meyering. */ + +/* Implementation overview: + + In the `usual' case, RM saves no state for directories it is processing. + When a removal fails (either due to an error or to an interactive `no' + reply), the failure is noted (see description of `ht' in remove.c's + remove_cwd_entries function) so that when/if the containing directory + is reopened, RM doesn't try to remove the entry again. + + RM may delete arbitrarily deep hierarchies -- even ones in which file + names (from root to leaf) are longer than the system-imposed maximum. + It does this by using chdir to change to each directory in turn before + removing the entries in that directory. + + RM detects directory cycles lazily. See lib/cycle-check.c. + + RM is careful to avoid forming full file names whenever possible. + A full file name is formed only when it is about to be used -- e.g. + in a diagnostic or in an interactive-mode prompt. + + RM minimizes the number of lstat system calls it makes. On systems + that have valid d_type data in directory entries, RM makes only one + lstat call per command line argument -- regardless of the depth of + the hierarchy. */ + +#include +#include +#include +#include +#include + +#include "system.h" +#include "argmatch.h" +#include "error.h" +#include "lstat.h" +#include "quote.h" +#include "quotearg.h" +#include "remove.h" +#include "root-dev-ino.h" +#include "yesno.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "rm" + +#define AUTHORS \ + "Paul Rubin", "David MacKenzie, Richard Stallman", "Jim Meyering" + +/* Name this program was run with. */ +char *program_name; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + INTERACTIVE_OPTION = CHAR_MAX + 1, + ONE_FILE_SYSTEM, + NO_PRESERVE_ROOT, + PRESERVE_ROOT, + PRESUME_INPUT_TTY_OPTION +}; + +enum interactive_type + { + interactive_never, /* 0: no option or --interactive=never */ + interactive_once, /* 1: -I or --interactive=once */ + interactive_always /* 2: default, -i or --interactive=always */ + }; + +static struct option const long_opts[] = +{ + {"directory", no_argument, NULL, 'd'}, + {"force", no_argument, NULL, 'f'}, + {"interactive", optional_argument, NULL, INTERACTIVE_OPTION}, + + {"one-file-system", no_argument, NULL, ONE_FILE_SYSTEM}, + {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT}, + {"preserve-root", no_argument, NULL, PRESERVE_ROOT}, + + /* This is solely for testing. Do not document. */ + /* It is relatively difficult to ensure that there is a tty on stdin. + Since rm acts differently depending on that, without this option, + it'd be harder to test the parts of rm that depend on that setting. */ + {"-presume-input-tty", no_argument, NULL, PRESUME_INPUT_TTY_OPTION}, + + {"recursive", no_argument, NULL, 'r'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +static char const *const interactive_args[] = +{ + "never", "no", "none", + "once", + "always", "yes", NULL +}; +static enum interactive_type const interactive_types[] = +{ + interactive_never, interactive_never, interactive_never, + interactive_once, + interactive_always, interactive_always +}; +ARGMATCH_VERIFY (interactive_args, interactive_types); + +/* Advise the user about invalid usages like "rm -foo" if the file + "-foo" exists, assuming ARGC and ARGV are as with `main'. */ + +static void +diagnose_leading_hyphen (int argc, char **argv) +{ + /* OPTIND is unreliable, so iterate through the arguments looking + for a file name that looks like an option. */ + int i; + + for (i = 1; i < argc; i++) + { + char const *arg = argv[i]; + struct stat st; + + if (arg[0] == '-' && arg[1] && lstat (arg, &st) == 0) + { + fprintf (stderr, + _("Try `%s ./%s' to remove the file %s.\n"), + argv[0], + quotearg_n_style (1, shell_quoting_style, arg), + quote (arg)); + break; + } + } +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... FILE...\n"), program_name); + fputs (_("\ +Remove (unlink) the FILE(s).\n\ +\n\ + -f, --force ignore nonexistent files, never prompt\n\ + -i prompt before every removal\n\ +"), stdout); + fputs (_("\ + -I prompt once before removing more than three files, or\n\ + when removing recursively. Less intrusive than -i,\n\ + while still giving protection against most mistakes\n\ + --interactive[=WHEN] prompt according to WHEN: never, once (-I), or\n\ + always (-i). Without WHEN, prompt always\n\ +"), stdout); + fputs (_("\ + --one-file-system when removing a hierarchy recursively, skip any\n\ + directory that is on a file system different from\n\ + that of the corresponding command line argument\n\ +"), stdout); + fputs (_("\ + --no-preserve-root do not treat `/' specially\n\ + --preserve-root do not remove `/' (default)\n\ + -r, -R, --recursive remove directories and their contents recursively\n\ + -v, --verbose explain what is being done\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +By default, rm does not remove directories. Use the --recursive (-r or -R)\n\ +option to remove each listed directory, too, along with all of its contents.\n\ +"), stdout); + printf (_("\ +\n\ +To remove a file whose name starts with a `-', for example `-foo',\n\ +use one of these commands:\n\ + %s -- -foo\n\ +\n\ + %s ./-foo\n\ +"), + program_name, program_name); + fputs (_("\ +\n\ +Note that if you use rm to remove a file, it is usually possible to recover\n\ +the contents of that file. If you want more assurance that the contents are\n\ +truly unrecoverable, consider using shred.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +static void +rm_option_init (struct rm_options *x) +{ + x->ignore_missing_files = false; + x->interactive = RMI_SOMETIMES; + x->one_file_system = false; + x->recursive = false; + x->root_dev_ino = NULL; + x->stdin_tty = isatty (STDIN_FILENO); + x->verbose = false; + + /* Since this program exits immediately after calling `rm', rm need not + expend unnecessary effort to preserve the initial working directory. */ + x->require_restore_cwd = false; +} + +int +main (int argc, char **argv) +{ + bool preserve_root = true; + struct rm_options x; + bool prompt_once = false; + int c; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + rm_option_init (&x); + + while ((c = getopt_long (argc, argv, "dfirvIR", long_opts, NULL)) != -1) + { + switch (c) + { + case 'd': + /* Ignore this option, for backward compatibility with + coreutils 5.92. FIXME: Some time after 2005, change this + to report an error (or perhaps behave like FreeBSD does) + instead of ignoring the option. */ + break; + + case 'f': + x.interactive = RMI_NEVER; + x.ignore_missing_files = true; + prompt_once = false; + break; + + case 'i': + x.interactive = RMI_ALWAYS; + x.ignore_missing_files = false; + prompt_once = false; + break; + + case 'I': + x.interactive = RMI_NEVER; + x.ignore_missing_files = false; + prompt_once = true; + break; + + case 'r': + case 'R': + x.recursive = true; + break; + + case INTERACTIVE_OPTION: + { + int i; + if (optarg) + i = XARGMATCH ("--interactive", optarg, interactive_args, + interactive_types); + else + i = interactive_always; + switch (i) + { + case interactive_never: + x.interactive = RMI_NEVER; + prompt_once = false; + break; + + case interactive_once: + x.interactive = RMI_SOMETIMES; + x.ignore_missing_files = false; + prompt_once = true; + break; + + case interactive_always: + x.interactive = RMI_ALWAYS; + x.ignore_missing_files = false; + prompt_once = false; + break; + } + break; + } + + case ONE_FILE_SYSTEM: + x.one_file_system = true; + break; + + case NO_PRESERVE_ROOT: + preserve_root = false; + break; + + case PRESERVE_ROOT: + preserve_root = true; + break; + + case PRESUME_INPUT_TTY_OPTION: + x.stdin_tty = true; + break; + + case 'v': + x.verbose = true; + break; + + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + diagnose_leading_hyphen (argc, argv); + usage (EXIT_FAILURE); + } + } + + if (argc <= optind) + { + if (x.ignore_missing_files) + exit (EXIT_SUCCESS); + else + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + } + + if (x.recursive & preserve_root) + { + static struct dev_ino dev_ino_buf; + x.root_dev_ino = get_root_dev_ino (&dev_ino_buf); + if (x.root_dev_ino == NULL) + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quote ("/")); + } + + size_t n_files = argc - optind; + char const *const *file = (char const *const *) argv + optind; + + if (prompt_once && (x.recursive || 3 < n_files)) + { + fprintf (stderr, + (x.recursive + ? _("%s: remove all arguments recursively? ") + : _("%s: remove all arguments? ")), + program_name); + if (!yesno ()) + exit (EXIT_SUCCESS); + } + enum RM_status status = rm (n_files, file, &x); + assert (VALID_STATUS (status)); + exit (status == RM_ERROR ? EXIT_FAILURE : EXIT_SUCCESS); +} diff --git a/src/rmdir.c b/src/rmdir.c new file mode 100644 index 0000000..39063b4 --- /dev/null +++ b/src/rmdir.c @@ -0,0 +1,226 @@ +/* rmdir -- remove directories + + Copyright (C) 90, 91, 1995-2002, 2004, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Options: + -p, --parent 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 + +#include "system.h" +#include "error.h" +#include "quotearg.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "rmdir" + +#define AUTHORS "David MacKenzie" + +/* The name this program was run with. */ +char *program_name; + +/* If true, remove empty parent directories. */ +static bool remove_empty_parents; + +/* If true, don't treat failure to remove a nonempty directory + as an error. */ +static bool ignore_fail_on_non_empty; + +/* If true, output a diagnostic for every directory processed. */ +static bool verbose; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + IGNORE_FAIL_ON_NON_EMPTY_OPTION = CHAR_MAX + 1 +}; + +static struct option const longopts[] = +{ + /* Don't name this `--force' because it's not close enough in meaning + to e.g. rm's -f option. */ + {"ignore-fail-on-non-empty", no_argument, NULL, + IGNORE_FAIL_ON_NON_EMPTY_OPTION}, + + {"path", no_argument, NULL, 'p'}, /* Deprecated. */ + {"parents", no_argument, NULL, 'p'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Return true if ERROR_NUMBER is one of the values associated + with a failed rmdir due to non-empty target directory. */ + +static bool +errno_rmdir_non_empty (int error_number) +{ + return (error_number == RMDIR_ERRNO_NOT_EMPTY); +} + +/* Remove any empty parent directories of DIR. + If DIR contains slash characters, at least one of them + (beginning with the rightmost) is replaced with a NUL byte. + Return true if successful. */ + +static bool +remove_parents (char *dir) +{ + char *slash; + bool ok = true; + + strip_trailing_slashes (dir); + while (1) + { + slash = strrchr (dir, '/'); + if (slash == NULL) + break; + /* Remove any characters after the slash, skipping any extra + slashes in a row. */ + while (slash > dir && *slash == '/') + --slash; + slash[1] = 0; + + /* Give a diagnostic for each attempted removal if --verbose. */ + if (verbose) + error (0, 0, _("removing directory, %s"), dir); + + ok = (rmdir (dir) == 0); + + if (!ok) + { + /* Stop quietly if --ignore-fail-on-non-empty. */ + if (ignore_fail_on_non_empty + && errno_rmdir_non_empty (errno)) + { + ok = true; + } + else + { + error (0, errno, "%s", quotearg_colon (dir)); + } + break; + } + } + return ok; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... DIRECTORY...\n"), program_name); + fputs (_("\ +Remove the DIRECTORY(ies), if they are empty.\n\ +\n\ + --ignore-fail-on-non-empty\n\ + ignore each failure that is solely because a directory\n\ + is non-empty\n\ +"), stdout); + fputs (_("\ + -p, --parents Remove DIRECTORY and its ancestors. E.g., `rmdir -p a/b/c' is\n\ + similar to `rmdir a/b/c a/b a'.\n\ + -v, --verbose output a diagnostic for every directory processed\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + bool ok = true; + int optc; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + remove_empty_parents = false; + + while ((optc = getopt_long (argc, argv, "pv", longopts, NULL)) != -1) + { + switch (optc) + { + case 'p': + remove_empty_parents = true; + break; + case IGNORE_FAIL_ON_NON_EMPTY_OPTION: + ignore_fail_on_non_empty = true; + break; + case 'v': + verbose = true; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + if (optind == argc) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + for (; optind < argc; ++optind) + { + char *dir = argv[optind]; + + /* Give a diagnostic for each attempted removal if --verbose. */ + if (verbose) + error (0, 0, _("removing directory, %s"), dir); + + if (rmdir (dir) != 0) + { + if (ignore_fail_on_non_empty + && errno_rmdir_non_empty (errno)) + continue; + + error (0, errno, "%s", quotearg_colon (dir)); + ok = false; + } + else if (remove_empty_parents) + { + ok &= remove_parents (dir); + } + } + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/seq.c b/src/seq.c new file mode 100644 index 0000000..59dd318 --- /dev/null +++ b/src/seq.c @@ -0,0 +1,375 @@ +/* seq - print sequence of numbers to standard output. + Copyright (C) 1994-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Ulrich Drepper. */ + +#include +#include +#include +#include + +#include "system.h" +#include "c-strtod.h" +#include "error.h" +#include "quote.h" +#include "xstrtod.h" + +/* Roll our own isfinite rather than using , so that we don't + have to worry about linking -lm just for isfinite. */ +#ifndef isfinite +# define isfinite(x) ((x) * 0 == 0) +#endif + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "seq" + +#define AUTHORS "Ulrich Drepper" + +/* If true print all number with equal width. */ +static bool equal_width; + +/* The name that this program was run with. */ +char *program_name; + +/* The string used to separate two numbers. */ +static char const *separator; + +/* The string output after all numbers have been output. + Usually "\n" or "\0". */ +/* FIXME: make this an option. */ +static char const terminator[] = "\n"; + +static struct option const long_options[] = +{ + { "equal-width", no_argument, NULL, 'w'}, + { "format", required_argument, NULL, 'f'}, + { "separator", required_argument, NULL, 's'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + { NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... LAST\n\ + or: %s [OPTION]... FIRST LAST\n\ + or: %s [OPTION]... FIRST INCREMENT LAST\n\ +"), program_name, program_name, program_name); + fputs (_("\ +Print numbers from FIRST to LAST, in steps of INCREMENT.\n\ +\n\ + -f, --format=FORMAT use printf style floating-point FORMAT\n\ + -s, --separator=STRING use STRING to separate numbers (default: \\n)\n\ + -w, --equal-width equalize width by padding with leading zeroes\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +If FIRST or INCREMENT is omitted, it defaults to 1. That is, an\n\ +omitted INCREMENT defaults to 1 even when LAST is smaller than FIRST.\n\ +FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\ +INCREMENT is usually positive if FIRST is smaller than LAST, and\n\ +INCREMENT is usually negative if FIRST is greater than LAST.\n\ +"), stdout); + fputs (_("\ +FORMAT must be suitable for printing one argument of type `double';\n\ +it defaults to %.PRECf if FIRST, INCREMENT, and LAST are all fixed point\n\ +decimal numbers with maximum precision PREC, and to %g otherwise.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* A command-line operand. */ +struct operand +{ + /* Its value, converted to 'long double'. */ + long double value; + + /* Its print width, if it were printed out in a form similar to its + input form. An input like "-.1" is treated like "-0.1", and an + input like "1." is treated like "1", but otherwise widths are + left alone. */ + size_t width; + + /* Number of digits after the decimal point, or INT_MAX if the + number can't easily be expressed as a fixed-point number. */ + int precision; +}; +typedef struct operand operand; + +/* Read a long double value from the command line. + Return if the string is correct else signal error. */ + +static operand +scan_arg (const char *arg) +{ + operand ret; + + if (! xstrtold (arg, NULL, &ret.value, c_strtold)) + { + error (0, 0, _("invalid floating point argument: %s"), arg); + usage (EXIT_FAILURE); + } + + ret.width = strlen (arg); + ret.precision = INT_MAX; + + if (! arg[strcspn (arg, "eExX")] && isfinite (ret.value)) + { + char const *decimal_point = strchr (arg, '.'); + if (! decimal_point) + ret.precision = 0; + else + { + size_t fraction_len = strlen (decimal_point + 1); + if (fraction_len <= INT_MAX) + ret.precision = fraction_len; + ret.width += (fraction_len == 0 + ? -1 + : (decimal_point == arg + || ! ISDIGIT (decimal_point[-1]))); + } + } + + return ret; +} + +/* If FORMAT is a valid printf format for a double argument, return + its long double equivalent, possibly allocated from dynamic + storage; otherwise, return NULL. */ + +static char const * +long_double_format (char const *fmt) +{ + size_t i; + size_t prefix_len; + bool has_L; + + for (i = 0; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i++) + if (! fmt[i]) + return NULL; + + i++; + i += strspn (fmt + i, "-+#0 '"); + i += strspn (fmt + i, "0123456789"); + if (fmt[i] == '.') + { + i++; + i += strspn (fmt + i, "0123456789"); + } + + prefix_len = i; + has_L = (fmt[i] == 'L'); + i += has_L; + if (! strchr ("efgaEFGA", fmt[i])) + return NULL; + + for (i++; ! (fmt[i] == '%' && fmt[i + 1] != '%'); i++) + if (! fmt[i]) + { + size_t format_size = i + 1; + char *ldfmt = xmalloc (format_size + 1); + memcpy (ldfmt, fmt, prefix_len); + ldfmt[prefix_len] = 'L'; + strcpy (ldfmt + prefix_len + 1, fmt + prefix_len + has_L); + return ldfmt; + } + + return NULL; +} + +/* Actually print the sequence of numbers in the specified range, with the + given or default stepping and format. */ + +static void +print_numbers (char const *fmt, + long double first, long double step, long double last) +{ + long double i; + + for (i = 0; /* empty */; i++) + { + long double x = first + i * step; + if (step < 0 ? x < last : last < x) + break; + if (i) + fputs (separator, stdout); + printf (fmt, x); + } + + if (i) + fputs (terminator, stdout); +} + +/* Return the default format given FIRST, STEP, and LAST. */ +static char const * +get_default_format (operand first, operand step, operand last) +{ + static char format_buf[sizeof "%0.Lf" + 2 * INT_STRLEN_BOUND (int)]; + + int prec = MAX (first.precision, step.precision); + + if (prec != INT_MAX && last.precision != INT_MAX) + { + if (equal_width) + { + size_t first_width = first.width + (prec - first.precision); + size_t last_width = last.width + (prec - last.precision); + if (first.width <= first_width + && (last.width < last_width) == (prec < last.precision)) + { + size_t width = MAX (first_width, last_width); + if (width <= INT_MAX) + { + int w = width; + sprintf (format_buf, "%%0%d.%dLf", w, prec); + return format_buf; + } + } + } + else + { + sprintf (format_buf, "%%.%dLf", prec); + return format_buf; + } + } + + return "%Lg"; +} + +int +main (int argc, char **argv) +{ + int optc; + operand first = { 1, 1, 0 }; + operand step = { 1, 1, 0 }; + operand last; + + /* The printf(3) format used for output. */ + char const *format_str = NULL; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + equal_width = false; + separator = "\n"; + + /* We have to handle negative numbers in the command line but this + conflicts with the command line arguments. So explicitly check first + whether the next argument looks like a negative number. */ + while (optind < argc) + { + if (argv[optind][0] == '-' + && ((optc = argv[optind][1]) == '.' || ISDIGIT (optc))) + { + /* means negative number */ + break; + } + + optc = getopt_long (argc, argv, "+f:s:w", long_options, NULL); + if (optc == -1) + break; + + switch (optc) + { + case 'f': + format_str = optarg; + break; + + case 's': + separator = optarg; + break; + + case 'w': + equal_width = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (argc - optind < 1) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + if (3 < argc - optind) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 3])); + usage (EXIT_FAILURE); + } + + if (format_str) + { + char const *f = long_double_format (format_str); + if (! f) + { + error (0, 0, _("invalid format string: %s"), quote (format_str)); + usage (EXIT_FAILURE); + } + format_str = f; + } + + last = scan_arg (argv[optind++]); + + if (optind < argc) + { + first = last; + last = scan_arg (argv[optind++]); + + if (optind < argc) + { + step = last; + last = scan_arg (argv[optind++]); + } + } + + if (format_str != NULL && equal_width) + { + error (0, 0, _("\ +format string may not be specified when printing equal width strings")); + usage (EXIT_FAILURE); + } + + if (format_str == NULL) + format_str = get_default_format (first, step, last); + + print_numbers (format_str, first.value, step.value, last.value); + + exit (EXIT_SUCCESS); +} diff --git a/src/setuidgid.c b/src/setuidgid.c new file mode 100644 index 0000000..3a51225 --- /dev/null +++ b/src/setuidgid.c @@ -0,0 +1,129 @@ +/* setuidgid - run a command with the UID and GID of a specified user + Copyright (C) 2003, 2004, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Jim Meyering */ + +#include +#include +#include +#include +#include +#include + +#include "system.h" + +#include "error.h" +#include "long-options.h" +#include "quote.h" + +#define PROGRAM_NAME "setuidgid" + +/* I wrote this program from scratch, based on the description of + D.J. Bernstein's program: http://cr.yp.to/daemontools/setuidgid.html. */ +#define AUTHORS "Jim Meyering" + +#define SETUIDGID_FAILURE 111 + +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s USERNAME COMMAND [ARGUMENT]...\n\ + or: %s OPTION\n\ +"), + program_name, program_name); + + fputs (_("\ +Drop any supplemental groups, assume the user-ID and group-ID of\n\ +the specified USERNAME, and run COMMAND with any specified ARGUMENTs.\n\ +Exit with status 111 if unable to assume the required user and group ID.\n\ +Otherwise, exit with the exit status of COMMAND.\n\ +This program is useful only when run by root (user ID zero).\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + char const *user_id; + struct passwd *pwd; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (SETUIDGID_FAILURE); + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "+", NULL, NULL) != -1) + usage (SETUIDGID_FAILURE); + + if (argc <= optind + 1) + { + if (argc < optind + 1) + error (0, 0, _("missing operand")); + else + error (0, 0, _("missing operand after %s"), quote (argv[optind])); + usage (SETUIDGID_FAILURE); + } + + user_id = argv[optind]; + pwd = getpwnam (user_id); + if (pwd == NULL) + error (SETUIDGID_FAILURE, errno, + _("unknown user-ID: %s"), quote (user_id)); + +#if HAVE_SETGROUPS + if (setgroups (1, &pwd->pw_gid)) + error (SETUIDGID_FAILURE, errno, _("cannot set supplemental group")); +#endif + + if (setgid (pwd->pw_gid)) + error (SETUIDGID_FAILURE, errno, + _("cannot set group-ID to %lu"), (unsigned long int) pwd->pw_gid); + + if (setuid (pwd->pw_uid)) + error (SETUIDGID_FAILURE, errno, + _("cannot set user-ID to %lu"), (unsigned long int) pwd->pw_uid); + + { + char **cmd = argv + optind + 1; + int exit_status; + execvp (*cmd, cmd); + exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE); + + error (0, errno, _("cannot run command %s"), quote (*cmd)); + exit (exit_status); + } +} diff --git a/src/shred.c b/src/shred.c new file mode 100644 index 0000000..23a4944 --- /dev/null +++ b/src/shred.c @@ -0,0 +1,1214 @@ +/* shred.c - overwrite files and devices to make it harder to recover data + + Copyright (C) 1999-2006 Free Software Foundation, Inc. + Copyright (C) 1997, 1998, 1999 Colin Plumb. + + 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Written by Colin Plumb. */ + +/* TODO: + - use consistent non-capitalization in error messages + - add standard GNU copyleft comment + + - Add -r/-R/--recursive + - Add -i/--interactive + - Reserve -d + - Add -L + - Add an unlink-all option to emulate rm. + */ + +/* + * Do a more secure overwrite of given files or devices, to make it harder + * for even very expensive hardware probing to recover the data. + * + * Although this process is also known as "wiping", I prefer the longer + * name both because I think it is more evocative of what is happening and + * because a longer name conveys a more appropriate sense of deliberateness. + * + * For the theory behind this, see "Secure Deletion of Data from Magnetic + * and Solid-State Memory", on line at + * http://www.cs.auckland.ac.nz/~pgut001/pubs/secure_del.html + * + * Just for the record, reversing one or two passes of disk overwrite + * is not terribly difficult with hardware help. Hook up a good-quality + * digitizing oscilloscope to the output of the head preamplifier and copy + * the high-res digitized data to a computer for some off-line analysis. + * Read the "current" data and average all the pulses together to get an + * "average" pulse on the disk. Subtract this average pulse from all of + * the actual pulses and you can clearly see the "echo" of the previous + * data on the disk. + * + * Real hard drives have to balance the cost of the media, the head, + * and the read circuitry. They use better-quality media than absolutely + * necessary to limit the cost of the read circuitry. By throwing that + * assumption out, and the assumption that you want the data processed + * as fast as the hard drive can spin, you can do better. + * + * If asked to wipe a file, this also unlinks it, renaming it to in a + * clever way to try to leave no trace of the original filename. + * + * This was inspired by a desire to improve on some code titled: + * Wipe V1.0-- Overwrite and delete files. S. 2/3/96 + * but I've rewritten everything here so completely that no trace of + * the original remains. + * + * Thanks to: + * Bob Jenkins, for his good RNG work and patience with the FSF copyright + * paperwork. + * Jim Meyering, for his work merging this into the GNU fileutils while + * still letting me feel a sense of ownership and pride. Getting me to + * tolerate the GNU brace style was quite a feat of diplomacy. + * Paul Eggert, for lots of useful discussion and code. I disagree with + * an awful lot of his suggestions, but they're disagreements worth having. + * + * Things to think about: + * - Security: Is there any risk to the race + * between overwriting and unlinking a file? Will it do anything + * drastically bad if told to attack a named pipe or socket? + */ + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "shred" + +#define AUTHORS "Colin Plumb" + +#include + +#include +#include +#include +#include +#include + +#include "system.h" +#include "xstrtol.h" +#include "error.h" +#include "fcntl--.h" +#include "getpagesize.h" +#include "human.h" +#include "inttostr.h" +#include "quotearg.h" /* For quotearg_colon */ +#include "quote.h" /* For quotearg_colon */ +#include "randint.h" +#include "randread.h" + +/* Default number of times to overwrite. */ +enum { DEFAULT_PASSES = 25 }; + +/* How many seconds to wait before checking whether to output another + verbose output line. */ +enum { VERBOSE_UPDATE = 5 }; + +/* Sector size and corresponding mask, for recovering after write failures. + The size must be a power of 2. */ +enum { SECTOR_SIZE = 512 }; +enum { SECTOR_MASK = SECTOR_SIZE - 1 }; +verify (0 < SECTOR_SIZE && (SECTOR_SIZE & SECTOR_MASK) == 0); + +struct Options +{ + bool force; /* -f flag: chmod files if necessary */ + size_t n_iterations; /* -n flag: Number of iterations */ + off_t size; /* -s flag: size of file */ + bool remove_file; /* -u flag: remove file after shredding */ + bool verbose; /* -v flag: Print progress */ + bool exact; /* -x flag: Do not round up file size */ + bool zero_fill; /* -z flag: Add a final zero pass */ +}; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + RANDOM_SOURCE_OPTION = CHAR_MAX + 1 +}; + +static struct option const long_opts[] = +{ + {"exact", no_argument, NULL, 'x'}, + {"force", no_argument, NULL, 'f'}, + {"iterations", required_argument, NULL, 'n'}, + {"size", required_argument, NULL, 's'}, + {"random-source", required_argument, NULL, RANDOM_SOURCE_OPTION}, + {"remove", no_argument, NULL, 'u'}, + {"verbose", no_argument, NULL, 'v'}, + {"zero", no_argument, NULL, 'z'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Global variable for error printing purposes */ +char const *program_name; /* Initialized before any possible use */ + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTIONS] FILE [...]\n"), program_name); + fputs (_("\ +Overwrite the specified FILE(s) repeatedly, in order to make it harder\n\ +for even very expensive hardware probing to recover the data.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + printf (_("\ + -f, --force change permissions to allow writing if necessary\n\ + -n, --iterations=N Overwrite N times instead of the default (%d)\n\ + --random-source=FILE get random bytes from FILE (default /dev/urandom)\n\ + -s, --size=N shred this many bytes (suffixes like K, M, G accepted)\n\ +"), DEFAULT_PASSES); + fputs (_("\ + -u, --remove truncate and remove file after overwriting\n\ + -v, --verbose show progress\n\ + -x, --exact do not round file sizes up to the next full block;\n\ + this is the default for non-regular files\n\ + -z, --zero add a final overwrite with zeros to hide shredding\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +If FILE is -, shred standard output.\n\ +\n\ +Delete FILE(s) if --remove (-u) is specified. The default is not to remove\n\ +the files because it is common to operate on device files like /dev/hda,\n\ +and those files usually should not be removed. When operating on regular\n\ +files, most people use the --remove option.\n\ +\n\ +"), stdout); + fputs (_("\ +CAUTION: Note that shred relies on a very important assumption:\n\ +that the file system overwrites data in place. This is the traditional\n\ +way to do things, but many modern file system designs do not satisfy this\n\ +assumption. The following are examples of file systems on which shred is\n\ +not effective, or is not guaranteed to be effective in all file system modes:\n\ +\n\ +"), stdout); + fputs (_("\ +* log-structured or journaled file systems, such as those supplied with\n\ +AIX and Solaris (and JFS, ReiserFS, XFS, Ext3, etc.)\n\ +\n\ +* file systems that write redundant data and carry on even if some writes\n\ +fail, such as RAID-based file systems\n\ +\n\ +* file systems that make snapshots, such as Network Appliance's NFS server\n\ +\n\ +"), stdout); + fputs (_("\ +* file systems that cache in temporary locations, such as NFS\n\ +version 3 clients\n\ +\n\ +* compressed file systems\n\ +\n\ +"), stdout); + fputs (_("\ +In the case of ext3 file systems, the above disclaimer applies\n\ +(and shred is thus of limited effectiveness) only in data=journal mode,\n\ +which journals file data in addition to just metadata. In both the\n\ +data=ordered (default) and data=writeback modes, shred works as usual.\n\ +Ext3 journaling modes can be changed by adding the data=something option\n\ +to the mount options for a particular file system in the /etc/fstab file,\n\ +as documented in the mount man page (man mount).\n\ +\n\ +"), stdout); + fputs (_("\ +In addition, file system backups and remote mirrors may contain copies\n\ +of the file that cannot be removed, and that will allow a shredded file\n\ +to be recovered later.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + + +/* + * Fill a buffer with a fixed pattern. + * + * The buffer must be at least 3 bytes long, even if + * size is less. Larger sizes are filled exactly. + */ +static void +fillpattern (int type, unsigned char *r, size_t size) +{ + size_t i; + unsigned int bits = type & 0xfff; + + bits |= bits << 12; + r[0] = (bits >> 4) & 255; + r[1] = (bits >> 8) & 255; + r[2] = bits & 255; + for (i = 3; i < size / 2; i *= 2) + memcpy (r + i, r, i); + if (i < size) + memcpy (r + i, r, size - i); + + /* Invert the first bit of every sector. */ + if (type & 0x1000) + for (i = 0; i < size; i += SECTOR_SIZE) + r[i] ^= 0x80; +} + +/* + * Generate a 6-character (+ nul) pass name string + * FIXME: allow translation of "random". + */ +#define PASS_NAME_SIZE 7 +static void +passname (unsigned char const *data, char name[PASS_NAME_SIZE]) +{ + if (data) + sprintf (name, "%02x%02x%02x", data[0], data[1], data[2]); + else + memcpy (name, "random", PASS_NAME_SIZE); +} + +/* Request that all data for FD be transferred to the corresponding + storage device. QNAME is the file name (quoted for colons). + Report any errors found. Return 0 on success, -1 + (setting errno) on failure. It is not an error if fdatasync and/or + fsync is not supported for this file, or if the file is not a + writable file descriptor. */ +static int +dosync (int fd, char const *qname) +{ + int err; + +#if HAVE_FDATASYNC + if (fdatasync (fd) == 0) + return 0; + err = errno; + if (err != EINVAL && err != EBADF) + { + error (0, err, _("%s: fdatasync failed"), qname); + errno = err; + return -1; + } +#endif + + if (fsync (fd) == 0) + return 0; + err = errno; + if (err != EINVAL && err != EBADF) + { + error (0, err, _("%s: fsync failed"), qname); + errno = err; + return -1; + } + + sync (); + return 0; +} + +/* Turn on or off direct I/O mode for file descriptor FD, if possible. + Try to turn it on if ENABLE is true. Otherwise, try to turn it off. */ +static void +direct_mode (int fd, bool enable) +{ + if (O_DIRECT) + { + int fd_flags = fcntl (fd, F_GETFL); + if (0 < fd_flags) + { + int new_flags = (enable + ? (fd_flags | O_DIRECT) + : (fd_flags & ~O_DIRECT)); + if (new_flags != fd_flags) + fcntl (fd, F_SETFL, new_flags); + } + } + +#if HAVE_DIRECTIO && defined DIRECTIO_ON && defined DIRECTIO_OFF + /* This is Solaris-specific. See the following for details: + http://docs.sun.com/db/doc/816-0213/6m6ne37so?q=directio&a=view */ + directio (fd, enable ? DIRECTIO_ON : DIRECTIO_OFF); +#endif +} + +/* + * Do pass number k of n, writing "size" bytes of the given pattern "type" + * to the file descriptor fd. Qname, k and n are passed in only for verbose + * progress message purposes. If n == 0, no progress messages are printed. + * + * If *sizep == -1, the size is unknown, and it will be filled in as soon + * as writing fails. + * + * Return 1 on write error, -1 on other error, 0 on success. + */ +static int +dopass (int fd, char const *qname, off_t *sizep, int type, + struct randread_source *s, unsigned long int k, unsigned long int n) +{ + off_t size = *sizep; + off_t offset; /* Current file posiiton */ + time_t thresh IF_LINT (= 0); /* Time to maybe print next status update */ + time_t now = 0; /* Current time */ + size_t lim; /* Amount of data to try writing */ + size_t soff; /* Offset into buffer for next write */ + ssize_t ssize; /* Return value from write */ + + /* Fill pattern buffer. Aligning it to a 32-bit boundary speeds up randread + in some cases. */ + typedef uint32_t fill_pattern_buffer[3 * 1024]; + union + { + fill_pattern_buffer buffer; + char c[sizeof (fill_pattern_buffer)]; + unsigned char u[sizeof (fill_pattern_buffer)]; + } r; + + off_t sizeof_r = sizeof r; + char pass_string[PASS_NAME_SIZE]; /* Name of current pass */ + bool write_error = false; + bool first_write = true; + + /* Printable previous offset into the file */ + char previous_offset_buf[LONGEST_HUMAN_READABLE + 1]; + char const *previous_human_offset IF_LINT (= 0); + + if (lseek (fd, 0, SEEK_SET) == -1) + { + error (0, errno, _("%s: cannot rewind"), qname); + return -1; + } + + /* Constant fill patterns need only be set up once. */ + if (type >= 0) + { + lim = (0 <= size && size < sizeof_r ? size : sizeof r); + fillpattern (type, r.u, lim); + passname (r.u, pass_string); + } + else + { + passname (0, pass_string); + } + + /* Set position if first status update */ + if (n) + { + error (0, 0, _("%s: pass %lu/%lu (%s)..."), qname, k, n, pass_string); + thresh = time (NULL) + VERBOSE_UPDATE; + previous_human_offset = ""; + } + + offset = 0; + for (;;) + { + /* How much to write this time? */ + lim = sizeof r; + if (0 <= size && size - offset < sizeof_r) + { + if (size < offset) + break; + lim = size - offset; + if (!lim) + break; + } + if (type < 0) + randread (s, &r, lim); + /* Loop to retry partial writes. */ + for (soff = 0; soff < lim; soff += ssize, first_write = false) + { + ssize = write (fd, r.c + soff, lim - soff); + if (ssize <= 0) + { + if (size < 0 && (ssize == 0 || errno == ENOSPC)) + { + /* Ah, we have found the end of the file */ + *sizep = size = offset + soff; + break; + } + else + { + int errnum = errno; + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + + /* If the first write of the first pass for a given file + has just failed with EINVAL, turn off direct mode I/O + and try again. This works around a bug in linux-2.4 + whereby opening with O_DIRECT would succeed for some + file system types (e.g., ext3), but any attempt to + access a file through the resulting descriptor would + fail with EINVAL. */ + if (k == 1 && first_write && errno == EINVAL) + { + direct_mode (fd, false); + ssize = 0; + continue; + } + error (0, errnum, _("%s: error writing at offset %s"), + qname, umaxtostr (offset + soff, buf)); + + /* 'shred' is often used on bad media, before throwing it + out. Thus, it shouldn't give up on bad blocks. This + code works because lim is always a multiple of + SECTOR_SIZE, except at the end. */ + verify (sizeof r % SECTOR_SIZE == 0); + if (errnum == EIO && 0 <= size && (soff | SECTOR_MASK) < lim) + { + size_t soff1 = (soff | SECTOR_MASK) + 1; + if (lseek (fd, offset + soff1, SEEK_SET) != -1) + { + /* Arrange to skip this block. */ + ssize = soff1 - soff; + write_error = true; + continue; + } + error (0, errno, _("%s: lseek failed"), qname); + } + return -1; + } + } + } + + /* Okay, we have written "soff" bytes. */ + + if (offset + soff < offset) + { + error (0, 0, _("%s: file too large"), qname); + return -1; + } + + offset += soff; + + /* Time to print progress? */ + if (n + && ((offset == size && *previous_human_offset) + || thresh <= (now = time (NULL)))) + { + char offset_buf[LONGEST_HUMAN_READABLE + 1]; + char size_buf[LONGEST_HUMAN_READABLE + 1]; + int human_progress_opts = (human_autoscale | human_SI + | human_base_1024 | human_B); + char const *human_offset + = human_readable (offset, offset_buf, + human_floor | human_progress_opts, 1, 1); + + if (offset == size + || !STREQ (previous_human_offset, human_offset)) + { + if (size < 0) + error (0, 0, _("%s: pass %lu/%lu (%s)...%s"), + qname, k, n, pass_string, human_offset); + else + { + uintmax_t off = offset; + int percent = (size == 0 + ? 100 + : (off <= TYPE_MAXIMUM (uintmax_t) / 100 + ? off * 100 / size + : off / (size / 100))); + char const *human_size + = human_readable (size, size_buf, + human_ceiling | human_progress_opts, + 1, 1); + if (offset == size) + human_offset = human_size; + error (0, 0, _("%s: pass %lu/%lu (%s)...%s/%s %d%%"), + qname, k, n, pass_string, human_offset, human_size, + percent); + } + + strcpy (previous_offset_buf, human_offset); + previous_human_offset = previous_offset_buf; + thresh = now + VERBOSE_UPDATE; + + /* + * Force periodic syncs to keep displayed progress accurate + * FIXME: Should these be present even if -v is not enabled, + * to keep the buffer cache from filling with dirty pages? + * It's a common problem with programs that do lots of writes, + * like mkfs. + */ + if (dosync (fd, qname) != 0) + { + if (errno != EIO) + return -1; + write_error = true; + } + } + } + } + + /* Force what we just wrote to hit the media. */ + if (dosync (fd, qname) != 0) + { + if (errno != EIO) + return -1; + write_error = true; + } + + return write_error; +} + +/* + * The passes start and end with a random pass, and the passes in between + * are done in random order. The idea is to deprive someone trying to + * reverse the process of knowledge of the overwrite patterns, so they + * have the additional step of figuring out what was done to the disk + * before they can try to reverse or cancel it. + * + * First, all possible 1-bit patterns. There are two of them. + * Then, all possible 2-bit patterns. There are four, but the two + * which are also 1-bit patterns can be omitted. + * Then, all possible 3-bit patterns. Likewise, 8-2 = 6. + * Then, all possible 4-bit patterns. 16-4 = 12. + * + * The basic passes are: + * 1-bit: 0x000, 0xFFF + * 2-bit: 0x555, 0xAAA + * 3-bit: 0x249, 0x492, 0x924, 0x6DB, 0xB6D, 0xDB6 (+ 1-bit) + * 100100100100 110110110110 + * 9 2 4 D B 6 + * 4-bit: 0x111, 0x222, 0x333, 0x444, 0x666, 0x777, + * 0x888, 0x999, 0xBBB, 0xCCC, 0xDDD, 0xEEE (+ 1-bit, 2-bit) + * Adding three random passes at the beginning, middle and end + * produces the default 25-pass structure. + * + * The next extension would be to 5-bit and 6-bit patterns. + * There are 30 uncovered 5-bit patterns and 64-8-2 = 46 uncovered + * 6-bit patterns, so they would increase the time required + * significantly. 4-bit patterns are enough for most purposes. + * + * The main gotcha is that this would require a trickier encoding, + * since lcm(2,3,4) = 12 bits is easy to fit into an int, but + * lcm(2,3,4,5) = 60 bits is not. + * + * One extension that is included is to complement the first bit in each + * 512-byte block, to alter the phase of the encoded data in the more + * complex encodings. This doesn't apply to MFM, so the 1-bit patterns + * are considered part of the 3-bit ones and the 2-bit patterns are + * considered part of the 4-bit patterns. + * + * + * How does the generalization to variable numbers of passes work? + * + * Here's how... + * Have an ordered list of groups of passes. Each group is a set. + * Take as many groups as will fit, plus a random subset of the + * last partial group, and place them into the passes list. + * Then shuffle the passes list into random order and use that. + * + * One extra detail: if we can't include a large enough fraction of the + * last group to be interesting, then just substitute random passes. + * + * If you want more passes than the entire list of groups can + * provide, just start repeating from the beginning of the list. + */ +static int const + patterns[] = +{ + -2, /* 2 random passes */ + 2, 0x000, 0xFFF, /* 1-bit */ + 2, 0x555, 0xAAA, /* 2-bit */ + -1, /* 1 random pass */ + 6, 0x249, 0x492, 0x6DB, 0x924, 0xB6D, 0xDB6, /* 3-bit */ + 12, 0x111, 0x222, 0x333, 0x444, 0x666, 0x777, + 0x888, 0x999, 0xBBB, 0xCCC, 0xDDD, 0xEEE, /* 4-bit */ + -1, /* 1 random pass */ + /* The following patterns have the frst bit per block flipped */ + 8, 0x1000, 0x1249, 0x1492, 0x16DB, 0x1924, 0x1B6D, 0x1DB6, 0x1FFF, + 14, 0x1111, 0x1222, 0x1333, 0x1444, 0x1555, 0x1666, 0x1777, + 0x1888, 0x1999, 0x1AAA, 0x1BBB, 0x1CCC, 0x1DDD, 0x1EEE, + -1, /* 1 random pass */ + 0 /* End */ +}; + +/* + * Generate a random wiping pass pattern with num passes. + * This is a two-stage process. First, the passes to include + * are chosen, and then they are shuffled into the desired + * order. + */ +static void +genpattern (int *dest, size_t num, struct randint_source *s) +{ + size_t randpasses; + int const *p; + int *d; + size_t n; + size_t accum, top, swap; + int k; + + if (!num) + return; + + /* Stage 1: choose the passes to use */ + p = patterns; + randpasses = 0; + d = dest; /* Destination for generated pass list */ + n = num; /* Passes remaining to fill */ + + for (;;) + { + k = *p++; /* Block descriptor word */ + if (!k) + { /* Loop back to the beginning */ + p = patterns; + } + else if (k < 0) + { /* -k random passes */ + k = -k; + if ((size_t) k >= n) + { + randpasses += n; + n = 0; + break; + } + randpasses += k; + n -= k; + } + else if ((size_t) k <= n) + { /* Full block of patterns */ + memcpy (d, p, k * sizeof (int)); + p += k; + d += k; + n -= k; + } + else if (n < 2 || 3 * n < (size_t) k) + { /* Finish with random */ + randpasses += n; + break; + } + else + { /* Pad out with k of the n available */ + do + { + if (n == (size_t) k || randint_choose (s, k) < n) + { + *d++ = *p; + n--; + } + p++; + } + while (n); + break; + } + } + top = num - randpasses; /* Top of initialized data */ + /* assert (d == dest+top); */ + + /* + * We now have fixed patterns in the dest buffer up to + * "top", and we need to scramble them, with "randpasses" + * random passes evenly spaced among them. + * + * We want one at the beginning, one at the end, and + * evenly spaced in between. To do this, we basically + * use Bresenham's line draw (a.k.a DDA) algorithm + * to draw a line with slope (randpasses-1)/(num-1). + * (We use a positive accumulator and count down to + * do this.) + * + * So for each desired output value, we do the following: + * - If it should be a random pass, copy the pass type + * to top++, out of the way of the other passes, and + * set the current pass to -1 (random). + * - If it should be a normal pattern pass, choose an + * entry at random between here and top-1 (inclusive) + * and swap the current entry with that one. + */ + randpasses--; /* To speed up later math */ + accum = randpasses; /* Bresenham DDA accumulator */ + for (n = 0; n < num; n++) + { + if (accum <= randpasses) + { + accum += num - 1; + dest[top++] = dest[n]; + dest[n] = -1; + } + else + { + swap = n + randint_choose (s, top - n); + k = dest[n]; + dest[n] = dest[swap]; + dest[swap] = k; + } + accum -= randpasses; + } + /* assert (top == num); */ +} + +/* + * The core routine to actually do the work. This overwrites the first + * size bytes of the given fd. Return true if successful. + */ +static bool +do_wipefd (int fd, char const *qname, struct randint_source *s, + struct Options const *flags) +{ + size_t i; + struct stat st; + off_t size; /* Size to write, size to read */ + unsigned long int n; /* Number of passes for printing purposes */ + int *passarray; + bool ok = true; + struct randread_source *rs; + + n = 0; /* dopass takes n -- 0 to mean "don't print progress" */ + if (flags->verbose) + n = flags->n_iterations + flags->zero_fill; + + if (fstat (fd, &st)) + { + error (0, errno, _("%s: fstat failed"), qname); + return false; + } + + /* If we know that we can't possibly shred the file, give up now. + Otherwise, we may go into a infinite loop writing data before we + find that we can't rewind the device. */ + if ((S_ISCHR (st.st_mode) && isatty (fd)) + || S_ISFIFO (st.st_mode) + || S_ISSOCK (st.st_mode)) + { + error (0, 0, _("%s: invalid file type"), qname); + return false; + } + + direct_mode (fd, true); + + /* Allocate pass array */ + passarray = xnmalloc (flags->n_iterations, sizeof *passarray); + + size = flags->size; + if (size == -1) + { + /* Accept a length of zero only if it's a regular file. + For any other type of file, try to get the size another way. */ + if (S_ISREG (st.st_mode)) + { + size = st.st_size; + if (size < 0) + { + error (0, 0, _("%s: file has negative size"), qname); + return false; + } + } + else + { + size = lseek (fd, 0, SEEK_END); + if (size <= 0) + { + /* We are unable to determine the length, up front. + Let dopass do that as part of its first iteration. */ + size = -1; + } + } + + /* Allow `rounding up' only for regular files. */ + if (0 <= size && !(flags->exact) && S_ISREG (st.st_mode)) + { + size += ST_BLKSIZE (st) - 1 - (size - 1) % ST_BLKSIZE (st); + + /* If in rounding up, we've just overflowed, use the maximum. */ + if (size < 0) + size = TYPE_MAXIMUM (off_t); + } + } + + /* Schedule the passes in random order. */ + genpattern (passarray, flags->n_iterations, s); + + rs = randint_get_source (s); + + /* Do the work */ + for (i = 0; i < flags->n_iterations; i++) + { + int err = dopass (fd, qname, &size, passarray[i], rs, i + 1, n); + if (err) + { + if (err < 0) + { + memset (passarray, 0, flags->n_iterations * sizeof (int)); + free (passarray); + return false; + } + ok = false; + } + } + + memset (passarray, 0, flags->n_iterations * sizeof (int)); + free (passarray); + + if (flags->zero_fill) + { + int err = dopass (fd, qname, &size, 0, rs, flags->n_iterations + 1, n); + if (err) + { + if (err < 0) + return false; + ok = false; + } + } + + /* Okay, now deallocate the data. The effect of ftruncate on + non-regular files is unspecified, so don't worry about any + errors reported for them. */ + if (flags->remove_file && ftruncate (fd, 0) != 0 + && S_ISREG (st.st_mode)) + { + error (0, errno, _("%s: error truncating"), qname); + return false; + } + + return ok; +} + +/* A wrapper with a little more checking for fds on the command line */ +static bool +wipefd (int fd, char const *qname, struct randint_source *s, + struct Options const *flags) +{ + int fd_flags = fcntl (fd, F_GETFL); + + if (fd_flags < 0) + { + error (0, errno, _("%s: fcntl failed"), qname); + return false; + } + if (fd_flags & O_APPEND) + { + error (0, 0, _("%s: cannot shred append-only file descriptor"), qname); + return false; + } + return do_wipefd (fd, qname, s, flags); +} + +/* --- Name-wiping code --- */ + +/* Characters allowed in a file name - a safe universal set. */ +static char const nameset[] = +"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_."; + +/* Increment NAME (with LEN bytes). NAME must be a big-endian base N + number with the digits taken from nameset. Return true if + successful if not (because NAME already has the greatest possible + value. */ + +static bool +incname (char *name, size_t len) +{ + while (len--) + { + char const *p = strchr (nameset, name[len]); + + /* If this character has a successor, use it. */ + if (p[1]) + { + name[len] = p[1]; + return true; + } + + /* Otherwise, set this digit to 0 and increment the prefix. */ + name[len] = nameset[0]; + } + + return false; +} + +/* + * Repeatedly rename a file with shorter and shorter names, + * to obliterate all traces of the file name on any system that + * adds a trailing delimiter to on-disk file names and reuses + * the same directory slot. Finally, unlink it. + * The passed-in filename is modified in place to the new filename. + * (Which is unlinked if this function succeeds, but is still present if + * it fails for some reason.) + * + * The main loop is written carefully to not get stuck if all possible + * names of a given length are occupied. It counts down the length from + * the original to 0. While the length is non-zero, it tries to find an + * unused file name of the given length. It continues until either the + * name is available and the rename succeeds, or it runs out of names + * to try (incname wraps and returns 1). Finally, it unlinks the file. + * + * The unlink is Unix-specific, as ANSI-standard remove has more + * portability problems with C libraries making it "safe". rename + * is ANSI-standard. + * + * To force the directory data out, we try to open the directory and + * invoke fdatasync and/or fsync on it. This is non-standard, so don't + * insist that it works: just fall back to a global sync in that case. + * This is fairly significantly Unix-specific. Of course, on any + * file system with synchronous metadata updates, this is unnecessary. + */ +static bool +wipename (char *oldname, char const *qoldname, struct Options const *flags) +{ + char *newname = xstrdup (oldname); + char *base = last_component (newname); + size_t len = base_len (base); + char *dir = dir_name (newname); + char *qdir = xstrdup (quotearg_colon (dir)); + bool first = true; + bool ok = true; + + int dir_fd = open (dir, O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK); + + if (flags->verbose) + error (0, 0, _("%s: removing"), qoldname); + + while (len) + { + memset (base, nameset[0], len); + base[len] = 0; + do + { + struct stat st; + if (lstat (newname, &st) < 0) + { + if (rename (oldname, newname) == 0) + { + if (0 <= dir_fd && dosync (dir_fd, qdir) != 0) + ok = false; + if (flags->verbose) + { + /* + * People seem to understand this better than talking + * about renaming oldname. newname doesn't need + * quoting because we picked it. oldname needs to + * be quoted only the first time. + */ + char const *old = (first ? qoldname : oldname); + error (0, 0, _("%s: renamed to %s"), old, newname); + first = false; + } + memcpy (oldname + (base - newname), base, len + 1); + break; + } + else + { + /* The rename failed: give up on this length. */ + break; + } + } + else + { + /* newname exists, so increment BASE so we use another */ + } + } + while (incname (base, len)); + len--; + } + if (unlink (oldname) != 0) + { + error (0, errno, _("%s: failed to remove"), qoldname); + ok = false; + } + else if (flags->verbose) + error (0, 0, _("%s: removed"), qoldname); + if (0 <= dir_fd) + { + if (dosync (dir_fd, qdir) != 0) + ok = false; + if (close (dir_fd) != 0) + { + error (0, errno, _("%s: failed to close"), qdir); + ok = false; + } + } + free (newname); + free (dir); + free (qdir); + return ok; +} + +/* + * Finally, the function that actually takes a filename and grinds + * it into hamburger. + * + * FIXME + * Detail to note: since we do not restore errno to EACCES after + * a failed chmod, we end up printing the error code from the chmod. + * This is actually the error that stopped us from proceeding, so + * it's arguably the right one, and in practice it'll be either EACCES + * again or EPERM, which both give similar error messages. + * Does anyone disagree? + */ +static bool +wipefile (char *name, char const *qname, + struct randint_source *s, struct Options const *flags) +{ + bool ok; + int fd; + + fd = open (name, O_WRONLY | O_NOCTTY | O_BINARY); + if (fd < 0 + && (errno == EACCES && flags->force) + && chmod (name, S_IWUSR) == 0) + fd = open (name, O_WRONLY | O_NOCTTY | O_BINARY); + if (fd < 0) + { + error (0, errno, _("%s: failed to open for writing"), qname); + return false; + } + + ok = do_wipefd (fd, qname, s, flags); + if (close (fd) != 0) + { + error (0, errno, _("%s: failed to close"), qname); + ok = false; + } + if (ok && flags->remove_file) + ok = wipename (name, qname, flags); + return ok; +} + + +/* Buffers for random data. */ +static struct randint_source *randint_source; + +/* Just on general principles, wipe buffers containing information + that may be related to the possibly-pseudorandom values used during + shredding. */ +static void +clear_random_data (void) +{ + randint_all_free (randint_source); +} + + +int +main (int argc, char **argv) +{ + bool ok = true; + struct Options flags = { 0, }; + char **file; + int n_files; + int c; + int i; + char const *random_source = NULL; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + flags.n_iterations = DEFAULT_PASSES; + flags.size = -1; + + while ((c = getopt_long (argc, argv, "fn:s:uvxz", long_opts, NULL)) != -1) + { + switch (c) + { + case 'f': + flags.force = true; + break; + + case 'n': + { + uintmax_t tmp; + if (xstrtoumax (optarg, NULL, 10, &tmp, NULL) != LONGINT_OK + || MIN (UINT32_MAX, SIZE_MAX / sizeof (int)) < tmp) + { + error (EXIT_FAILURE, 0, _("%s: invalid number of passes"), + quotearg_colon (optarg)); + } + flags.n_iterations = tmp; + } + break; + + case RANDOM_SOURCE_OPTION: + if (random_source && !STREQ (random_source, optarg)) + error (EXIT_FAILURE, 0, _("multiple random sources specified")); + random_source = optarg; + break; + + case 'u': + flags.remove_file = true; + break; + + case 's': + { + uintmax_t tmp; + if (xstrtoumax (optarg, NULL, 0, &tmp, "cbBkKMGTPEZY0") + != LONGINT_OK) + { + error (EXIT_FAILURE, 0, _("%s: invalid file size"), + quotearg_colon (optarg)); + } + flags.size = tmp; + } + break; + + case 'v': + flags.verbose = true; + break; + + case 'x': + flags.exact = true; + break; + + case 'z': + flags.zero_fill = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + file = argv + optind; + n_files = argc - optind; + + if (n_files == 0) + { + error (0, 0, _("missing file operand")); + usage (EXIT_FAILURE); + } + + randint_source = randint_all_new (random_source, SIZE_MAX); + if (! randint_source) + error (EXIT_FAILURE, errno, "%s", quotearg_colon (random_source)); + atexit (clear_random_data); + + for (i = 0; i < n_files; i++) + { + char *qname = xstrdup (quotearg_colon (file[i])); + if (STREQ (file[i], "-")) + { + ok &= wipefd (STDOUT_FILENO, qname, randint_source, &flags); + } + else + { + /* Plain filename - Note that this overwrites *argv! */ + ok &= wipefile (file[i], qname, randint_source, &flags); + } + free (qname); + } + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} +/* + * vim:sw=2:sts=2: + */ diff --git a/src/shuf.c b/src/shuf.c new file mode 100644 index 0000000..bfc9f30 --- /dev/null +++ b/src/shuf.c @@ -0,0 +1,421 @@ +/* Shuffle lines of text. + + Copyright (C) 2006, 2007 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + Written by Paul Eggert. */ + +#include + +#include +#include +#include "system.h" + +#include "error.h" +#include "getopt.h" +#include "quote.h" +#include "quotearg.h" +#include "randint.h" +#include "randperm.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "shuf" + +#define AUTHORS "Paul Eggert" + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]\n\ + or: %s -e [OPTION]... [ARG]...\n\ + or: %s -i LO-HI [OPTION]...\n\ +"), + program_name, program_name, program_name); + fputs (_("\ +Write a random permutation of the input lines to standard output.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -e, --echo treat each ARG as an input line\n\ + -i, --input-range=LO-HI treat each number LO through HI as an input line\n\ + -n, --head-lines=LINES output at most LINES lines\n\ + -o, --output=FILE write result to FILE instead of standard output\n\ + --random-source=FILE get random bytes from FILE (default /dev/urandom)\n\ + -z, --zero-terminated end lines with 0 byte, not newline\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +With no FILE, or when FILE is -, read standard input.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + + exit (status); +} + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + RANDOM_SOURCE_OPTION = CHAR_MAX + 1 +}; + +static struct option const long_opts[] = +{ + {"echo", no_argument, NULL, 'e'}, + {"input-range", required_argument, NULL, 'i'}, + {"head-count", required_argument, NULL, 'n'}, + {"output", required_argument, NULL, 'o'}, + {"random-source", required_argument, NULL, RANDOM_SOURCE_OPTION}, + {"zero-terminated", no_argument, NULL, 'z'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {0, 0, 0, 0}, +}; + +static bool +input_numbers_option_used (size_t lo_input, size_t hi_input) +{ + return ! (lo_input == SIZE_MAX && hi_input == 0); +} + +static void +input_from_argv (char **operand, int n_operands, char eolbyte) +{ + char *p; + size_t size = n_operands; + int i; + + for (i = 0; i < n_operands; i++) + size += strlen (operand[i]); + p = xmalloc (size); + + for (i = 0; i < n_operands; i++) + { + char *p1 = stpcpy (p, operand[i]); + operand[i] = p; + p = p1; + *p++ = eolbyte; + } + + operand[n_operands] = p; +} + +/* Return the start of the next line after LINE. The current line + ends in EOLBYTE, and is guaranteed to end before LINE + N. */ + +static char * +next_line (char *line, char eolbyte, size_t n) +{ + char *p = memchr (line, eolbyte, n); + return p + 1; +} + +/* Read data from file IN. Input lines are delimited by EOLBYTE; + silently append a trailing EOLBYTE if the file ends in some other + byte. Store a pointer to the resulting array of lines into *PLINE. + Return the number of lines read. Report an error and exit on + failure. */ + +static size_t +read_input (FILE *in, char eolbyte, char ***pline) +{ + char *p; + char *buf = NULL; + char *lim; + size_t alloc = 0; + size_t used = 0; + size_t next_alloc = (1 << 13) + 1; + size_t bytes_to_read; + size_t nread; + char **line; + size_t i; + size_t n_lines; + int fread_errno; + struct stat instat; + + if (fstat (fileno (in), &instat) == 0 && S_ISREG (instat.st_mode)) + { + off_t file_size = instat.st_size; + off_t current_offset = ftello (in); + if (0 <= current_offset) + { + off_t remaining_size = + (current_offset < file_size ? file_size - current_offset : 0); + if (SIZE_MAX - 2 < remaining_size) + xalloc_die (); + next_alloc = remaining_size + 2; + } + } + + do + { + if (alloc <= used + 1) + { + if (alloc == SIZE_MAX) + xalloc_die (); + alloc = next_alloc; + next_alloc = alloc * 2; + if (next_alloc < alloc) + next_alloc = SIZE_MAX; + buf = xrealloc (buf, alloc); + } + + bytes_to_read = alloc - used - 1; + nread = fread (buf + used, sizeof (char), bytes_to_read, in); + used += nread; + } + while (nread == bytes_to_read); + + fread_errno = errno; + + if (used && buf[used - 1] != eolbyte) + buf[used++] = eolbyte; + + lim = buf + used; + + n_lines = 0; + for (p = buf; p < lim; p = next_line (p, eolbyte, lim - p)) + n_lines++; + + *pline = line = xnmalloc (n_lines + 1, sizeof *line); + + line[0] = p = buf; + for (i = 1; i <= n_lines; i++) + line[i] = p = next_line (p, eolbyte, lim - p); + + errno = fread_errno; + return n_lines; +} + +static int +write_permuted_output (size_t n_lines, char * const *line, size_t lo_input, + size_t const *permutation) +{ + size_t i; + + if (line) + for (i = 0; i < n_lines; i++) + { + char * const *p = line + permutation[i]; + size_t len = p[1] - p[0]; + if (fwrite (p[0], sizeof *p[0], len, stdout) != len) + return -1; + } + else + for (i = 0; i < n_lines; i++) + { + unsigned long int n = lo_input + permutation[i]; + if (printf ("%lu\n", n) < 0) + return -1; + } + + return 0; +} + +int +main (int argc, char **argv) +{ + bool echo = false; + size_t lo_input = SIZE_MAX; + size_t hi_input = 0; + size_t head_lines = SIZE_MAX; + char const *outfile = NULL; + char *random_source = NULL; + char eolbyte = '\n'; + char **input_lines = NULL; + + int optc; + int n_operands; + char **operand; + size_t n_lines; + char **line; + struct randint_source *randint_source; + size_t *permutation; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "ei:n:o:z", long_opts, NULL)) != -1) + switch (optc) + { + case 'e': + echo = true; + break; + + case 'i': + { + unsigned long int argval = 0; + char *p = strchr (optarg, '-'); + char const *hi_optarg = optarg; + bool invalid = !p; + + if (input_numbers_option_used (lo_input, hi_input)) + error (EXIT_FAILURE, 0, _("multiple -i options specified")); + + if (p) + { + *p = '\0'; + invalid = ((xstrtoul (optarg, NULL, 10, &argval, NULL) + != LONGINT_OK) + || SIZE_MAX < argval); + *p = '-'; + lo_input = argval; + hi_optarg = p + 1; + } + + invalid |= ((xstrtoul (hi_optarg, NULL, 10, &argval, NULL) + != LONGINT_OK) + || SIZE_MAX < argval); + hi_input = argval; + n_lines = hi_input - lo_input + 1; + invalid |= ((lo_input <= hi_input) == (n_lines == 0)); + if (invalid) + error (EXIT_FAILURE, 0, _("invalid input range %s"), + quote (optarg)); + } + break; + + case 'n': + { + unsigned long int argval; + strtol_error e = xstrtoul (optarg, NULL, 10, &argval, NULL); + + if (e == LONGINT_OK) + head_lines = MIN (head_lines, argval); + else if (e != LONGINT_OVERFLOW) + error (EXIT_FAILURE, 0, _("invalid line count %s"), + quote (optarg)); + } + break; + + case 'o': + if (outfile && !STREQ (outfile, optarg)) + error (EXIT_FAILURE, 0, _("multiple output files specified")); + outfile = optarg; + break; + + case RANDOM_SOURCE_OPTION: + if (random_source && !STREQ (random_source, optarg)) + error (EXIT_FAILURE, 0, _("multiple random sources specified")); + random_source = optarg; + break; + + case 'z': + eolbyte = '\0'; + break; + + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + + n_operands = argc - optind; + operand = argv + optind; + + if (echo) + { + if (input_numbers_option_used (lo_input, hi_input)) + error (EXIT_FAILURE, 0, _("cannot combine -e and -i options")); + input_from_argv (operand, n_operands, eolbyte); + n_lines = n_operands; + line = operand; + } + else if (input_numbers_option_used (lo_input, hi_input)) + { + if (n_operands) + { + error (0, 0, _("extra operand %s\n"), quote (operand[0])); + usage (EXIT_FAILURE); + } + n_lines = hi_input - lo_input + 1; + line = NULL; + } + else + { + switch (n_operands) + { + case 0: + break; + + case 1: + if (! (STREQ (operand[0], "-") || freopen (operand[0], "r", stdin))) + error (EXIT_FAILURE, errno, "%s", operand[0]); + break; + + default: + error (0, 0, _("extra operand %s"), quote (operand[1])); + usage (EXIT_FAILURE); + } + + n_lines = read_input (stdin, eolbyte, &input_lines); + line = input_lines; + } + + head_lines = MIN (head_lines, n_lines); + + randint_source = randint_all_new (random_source, + randperm_bound (head_lines, n_lines)); + if (! randint_source) + error (EXIT_FAILURE, errno, "%s", quotearg_colon (random_source)); + + /* Close stdin now, rather than earlier, so that randint_all_new + doesn't have to worry about opening something other than + stdin. */ + if (! (echo || input_numbers_option_used (lo_input, hi_input)) + && (ferror (stdin) || fclose (stdin) != 0)) + error (EXIT_FAILURE, errno, _("read error")); + + permutation = randperm_new (randint_source, head_lines, n_lines); + + if (outfile && ! freopen (outfile, "w", stdout)) + error (EXIT_FAILURE, errno, "%s", quotearg_colon (outfile)); + if (write_permuted_output (head_lines, line, lo_input, permutation) != 0) + error (EXIT_FAILURE, errno, _("write error")); + +#ifdef lint + free (permutation); + randint_all_free (randint_source); + if (input_lines) + { + free (input_lines[0]); + free (input_lines); + } +#endif + + return EXIT_SUCCESS; +} diff --git a/src/sleep.c b/src/sleep.c new file mode 100644 index 0000000..730b19d --- /dev/null +++ b/src/sleep.c @@ -0,0 +1,152 @@ +/* sleep - delay for a specified amount of time. + Copyright (C) 84, 1991-1997, 1999-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include +#include +#include +#include + +#include "system.h" +#include "c-strtod.h" +#include "error.h" +#include "long-options.h" +#include "quote.h" +#include "xnanosleep.h" +#include "xstrtod.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "sleep" + +#define AUTHORS "Jim Meyering", "Paul Eggert" + +/* The name by which this program was run. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s NUMBER[SUFFIX]...\n\ + or: %s OPTION\n\ +Pause for NUMBER seconds. SUFFIX may be `s' for seconds (the default),\n\ +`m' for minutes, `h' for hours or `d' for days. Unlike most implementations\n\ +that require NUMBER be an integer, here NUMBER may be an arbitrary floating\n\ +point number. Given two or more arguments, pause for the amount of time\n\ +specified by the sum of their values.\n\ +\n\ +"), + program_name, program_name); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Given a floating point value *X, and a suffix character, SUFFIX_CHAR, + scale *X by the multiplier implied by SUFFIX_CHAR. SUFFIX_CHAR may + be the NUL byte or `s' to denote seconds, `m' for minutes, `h' for + hours, or `d' for days. If SUFFIX_CHAR is invalid, don't modify *X + and return false. Otherwise return true. */ + +static bool +apply_suffix (double *x, char suffix_char) +{ + int multiplier; + + switch (suffix_char) + { + case 0: + case 's': + multiplier = 1; + break; + case 'm': + multiplier = 60; + break; + case 'h': + multiplier = 60 * 60; + break; + case 'd': + multiplier = 60 * 60 * 24; + break; + default: + return false; + } + + *x *= multiplier; + + return true; +} + +int +main (int argc, char **argv) +{ + int i; + double seconds = 0.0; + bool ok = true; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (argc == 1) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + for (i = optind; i < argc; i++) + { + double s; + const char *p; + if (! xstrtod (argv[i], &p, &s, c_strtod) + /* Nonnegative interval. */ + || ! (0 <= s) + /* No extra chars after the number and an optional s,m,h,d char. */ + || (*p && *(p+1)) + /* Check any suffix char and update S based on the suffix. */ + || ! apply_suffix (&s, *p)) + { + error (0, 0, _("invalid time interval %s"), quote (argv[i])); + ok = false; + } + + seconds += s; + } + + if (!ok) + usage (EXIT_FAILURE); + + if (xnanosleep (seconds)) + error (EXIT_FAILURE, errno, _("cannot read realtime clock")); + + exit (EXIT_SUCCESS); +} diff --git a/src/sort.c b/src/sort.c new file mode 100644 index 0000000..58ca66a --- /dev/null +++ b/src/sort.c @@ -0,0 +1,3174 @@ +/* sort - sort lines of text (with all kinds of options). + Copyright (C) 1988, 1991-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Written December 1988 by Mike Haertel. + The author may be reached (Email) at the address mike@gnu.ai.mit.edu, + or (US mail) as Mike Haertel c/o Free Software Foundation. + + Ørn E. Hansen added NLS support in 1997. */ + +#include + +#include +#include +#include +#include +#include "system.h" +#include "argmatch.h" +#include "error.h" +#include "hard-locale.h" +#include "hash.h" +#include "inttostr.h" +#include "md5.h" +#include "physmem.h" +#include "posixver.h" +#include "quote.h" +#include "randread.h" +#include "stdio--.h" +#include "stdlib--.h" +#include "strnumcmp.h" +#include "xmemcoll.h" +#include "xmemxfrm.h" +#include "xstrtol.h" + +#if HAVE_SYS_RESOURCE_H +# include +#endif +#ifndef RLIMIT_DATA +struct rlimit { size_t rlim_cur; }; +# define getrlimit(Resource, Rlp) (-1) +#endif + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "sort" + +#define AUTHORS "Mike Haertel", "Paul Eggert" + +#if HAVE_LANGINFO_CODESET +# include +#endif + +/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is + present. */ +#ifndef SA_NOCLDSTOP +# define SA_NOCLDSTOP 0 +/* No sigprocmask. Always 'return' zero. */ +# define sigprocmask(How, Set, Oset) (0) +# define sigset_t int +# if ! HAVE_SIGINTERRUPT +# define siginterrupt(sig, flag) /* empty */ +# endif +#endif + +#ifndef STDC_HEADERS +double strtod (); +#endif + +#define UCHAR_LIM (UCHAR_MAX + 1) + +#ifndef DEFAULT_TMPDIR +# define DEFAULT_TMPDIR "/tmp" +#endif + +/* Exit statuses. */ +enum + { + /* POSIX says to exit with status 1 if invoked with -c and the + input is not properly sorted. */ + SORT_OUT_OF_ORDER = 1, + + /* POSIX says any other irregular exit must exit with a status + code greater than 1. */ + SORT_FAILURE = 2 + }; + +enum + { + /* The number of times we should try to fork a compression process + (we retry if the fork call fails). We don't _need_ to compress + temp files, this is just to reduce disk access, so this number + can be small. */ + MAX_FORK_TRIES_COMPRESS = 2, + + /* The number of times we should try to fork a decompression process. + If we can't fork a decompression process, we can't sort, so this + number should be big. */ + MAX_FORK_TRIES_DECOMPRESS = 8 + }; + +/* The representation of the decimal point in the current locale. */ +static int decimal_point; + +/* Thousands separator; if -1, then there isn't one. */ +static int thousands_sep; + +/* Nonzero if the corresponding locales are hard. */ +static bool hard_LC_COLLATE; +#if HAVE_NL_LANGINFO +static bool hard_LC_TIME; +#endif + +#define NONZERO(x) ((x) != 0) + +/* The kind of blanks for '-b' to skip in various options. */ +enum blanktype { bl_start, bl_end, bl_both }; + +/* The character marking end of line. Default to \n. */ +static char eolchar = '\n'; + +/* Lines are held in core as counted strings. */ +struct line +{ + char *text; /* Text of the line. */ + size_t length; /* Length including final newline. */ + char *keybeg; /* Start of first key. */ + char *keylim; /* Limit of first key. */ +}; + +/* Input buffers. */ +struct buffer +{ + char *buf; /* Dynamically allocated buffer, + partitioned into 3 regions: + - input data; + - unused area; + - an array of lines, in reverse order. */ + size_t used; /* Number of bytes used for input data. */ + size_t nlines; /* Number of lines in the line array. */ + size_t alloc; /* Number of bytes allocated. */ + size_t left; /* Number of bytes left from previous reads. */ + size_t line_bytes; /* Number of bytes to reserve for each line. */ + bool eof; /* An EOF has been read. */ +}; + +struct keyfield +{ + size_t sword; /* Zero-origin 'word' to start at. */ + size_t schar; /* Additional characters to skip. */ + size_t eword; /* Zero-origin first word after field. */ + size_t echar; /* Additional characters in field. */ + bool const *ignore; /* Boolean array of characters to ignore. */ + char const *translate; /* Translation applied to characters. */ + bool skipsblanks; /* Skip leading blanks when finding start. */ + bool skipeblanks; /* Skip leading blanks when finding end. */ + bool numeric; /* Flag for numeric comparison. Handle + strings of digits with optional decimal + point, but no exponential notation. */ + bool random; /* Sort by random hash of key. */ + bool general_numeric; /* Flag for general, numeric comparison. + Handle numbers in exponential notation. */ + bool month; /* Flag for comparison by month name. */ + bool reverse; /* Reverse the sense of comparison. */ + struct keyfield *next; /* Next keyfield to try. */ +}; + +struct month +{ + char const *name; + int val; +}; + +/* The name this program was run with. */ +char *program_name; + +/* FIXME: None of these tables work with multibyte character sets. + Also, there are many other bugs when handling multibyte characters. + One way to fix this is to rewrite `sort' to use wide characters + internally, but doing this with good performance is a bit + tricky. */ + +/* Table of blanks. */ +static bool blanks[UCHAR_LIM]; + +/* Table of non-printing characters. */ +static bool nonprinting[UCHAR_LIM]; + +/* Table of non-dictionary characters (not letters, digits, or blanks). */ +static bool nondictionary[UCHAR_LIM]; + +/* Translation table folding lower case to upper. */ +static char fold_toupper[UCHAR_LIM]; + +#define MONTHS_PER_YEAR 12 + +/* Table mapping month names to integers. + Alphabetic order allows binary search. */ +static struct month monthtab[] = +{ + {"APR", 4}, + {"AUG", 8}, + {"DEC", 12}, + {"FEB", 2}, + {"JAN", 1}, + {"JUL", 7}, + {"JUN", 6}, + {"MAR", 3}, + {"MAY", 5}, + {"NOV", 11}, + {"OCT", 10}, + {"SEP", 9} +}; + +/* During the merge phase, the number of files to merge at once. */ +#define NMERGE 16 + +/* Minimum size for a merge or check buffer. */ +#define MIN_MERGE_BUFFER_SIZE (2 + sizeof (struct line)) + +/* Minimum sort size; the code might not work with smaller sizes. */ +#define MIN_SORT_SIZE (NMERGE * MIN_MERGE_BUFFER_SIZE) + +/* The number of bytes needed for a merge or check buffer, which can + function relatively efficiently even if it holds only one line. If + a longer line is seen, this value is increased. */ +static size_t merge_buffer_size = MAX (MIN_MERGE_BUFFER_SIZE, 256 * 1024); + +/* The approximate maximum number of bytes of main memory to use, as + specified by the user. Zero if the user has not specified a size. */ +static size_t sort_size; + +/* The guessed size for non-regular files. */ +#define INPUT_FILE_SIZE_GUESS (1024 * 1024) + +/* Array of directory names in which any temporary files are to be created. */ +static char const **temp_dirs; + +/* Number of temporary directory names used. */ +static size_t temp_dir_count; + +/* Number of allocated slots in temp_dirs. */ +static size_t temp_dir_alloc; + +/* Flag to reverse the order of all comparisons. */ +static bool reverse; + +/* Flag for stable sort. This turns off the last ditch bytewise + comparison of lines, and instead leaves lines in the same order + they were read if all keys compare equal. */ +static bool stable; + +/* If TAB has this value, blanks separate fields. */ +enum { TAB_DEFAULT = CHAR_MAX + 1 }; + +/* Tab character separating fields. If TAB_DEFAULT, then fields are + separated by the empty string between a non-blank character and a blank + character. */ +static int tab = TAB_DEFAULT; + +/* Flag to remove consecutive duplicate lines from the output. + Only the last of a sequence of equal lines will be output. */ +static bool unique; + +/* Nonzero if any of the input files are the standard input. */ +static bool have_read_stdin; + +/* List of key field comparisons to be tried. */ +static struct keyfield *keylist; + +/* Program used to (de)compress temp files. Must accept -d. */ +static char const *compress_program; + +static void sortlines_temp (struct line *, size_t, struct line *); + +/* Report MESSAGE for FILE, then clean up and exit. + If FILE is null, it represents standard output. */ + +static void die (char const *, char const *) ATTRIBUTE_NORETURN; +static void +die (char const *message, char const *file) +{ + error (0, errno, "%s: %s", message, file ? file : _("standard output")); + exit (SORT_FAILURE); +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + fputs (_("\ +Write sorted concatenation of all FILE(s) to standard output.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ +Ordering options:\n\ +\n\ +"), stdout); + fputs (_("\ + -b, --ignore-leading-blanks ignore leading blanks\n\ + -d, --dictionary-order consider only blanks and alphanumeric characters\n\ + -f, --ignore-case fold lower case to upper case characters\n\ +"), stdout); + fputs (_("\ + -g, --general-numeric-sort compare according to general numerical value\n\ + -i, --ignore-nonprinting consider only printable characters\n\ + -M, --month-sort compare (unknown) < `JAN' < ... < `DEC'\n\ + -n, --numeric-sort compare according to string numerical value\n\ + -R, --random-sort sort by random hash of keys\n\ + --random-source=FILE get random bytes from FILE (default /dev/urandom)\n\ + -r, --reverse reverse the result of comparisons\n\ +\n\ +"), stdout); + fputs (_("\ +Other options:\n\ +\n\ + -c, --check, --check=diagnose-first check for sorted input; do not sort\n\ + -C, --check=quiet, --check=silent like -c, but do not report first bad line\n\ + --compress-program=PROG compress temporaries with PROG;\n\ + decompress them with PROG -d\n\ + -k, --key=POS1[,POS2] start a key at POS1, end it at POS2 (origin 1)\n\ + -m, --merge merge already sorted files; do not sort\n\ +"), stdout); + fputs (_("\ + -o, --output=FILE write result to FILE instead of standard output\n\ + -s, --stable stabilize sort by disabling last-resort comparison\n\ + -S, --buffer-size=SIZE use SIZE for main memory buffer\n\ +"), stdout); + printf (_("\ + -t, --field-separator=SEP use SEP instead of non-blank to blank transition\n\ + -T, --temporary-directory=DIR use DIR for temporaries, not $TMPDIR or %s;\n\ + multiple options specify multiple directories\n\ + -u, --unique with -c, check for strict ordering;\n\ + without -c, output only the first of an equal run\n\ +"), DEFAULT_TMPDIR); + fputs (_("\ + -z, --zero-terminated end lines with 0 byte, not newline\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +POS is F[.C][OPTS], where F is the field number and C the character position\n\ +in the field; both are origin 1. If neither -t nor -b is in effect, characters\n\ +in a field are counted from the beginning of the preceding whitespace. OPTS is\n\ +one or more single-letter ordering options, which override global ordering\n\ +options for that key. If no key is given, use the entire line as the key.\n\ +\n\ +SIZE may be followed by the following multiplicative suffixes:\n\ +"), stdout); + fputs (_("\ +% 1% of memory, b 1, K 1024 (default), and so on for M, G, T, P, E, Z, Y.\n\ +\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +*** WARNING ***\n\ +The locale specified by the environment affects sort order.\n\ +Set LC_ALL=C to get the traditional sort order that uses\n\ +native byte values.\n\ +"), stdout ); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + + exit (status); +} + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + CHECK_OPTION = CHAR_MAX + 1, + COMPRESS_PROGRAM_OPTION, + RANDOM_SOURCE_OPTION +}; + +static char const short_options[] = "-bcCdfgik:mMno:rRsS:t:T:uy:z"; + +static struct option const long_options[] = +{ + {"ignore-leading-blanks", no_argument, NULL, 'b'}, + {"check", optional_argument, NULL, CHECK_OPTION}, + {"compress-program", required_argument, NULL, COMPRESS_PROGRAM_OPTION}, + {"dictionary-order", no_argument, NULL, 'd'}, + {"ignore-case", no_argument, NULL, 'f'}, + {"general-numeric-sort", no_argument, NULL, 'g'}, + {"ignore-nonprinting", no_argument, NULL, 'i'}, + {"key", required_argument, NULL, 'k'}, + {"merge", no_argument, NULL, 'm'}, + {"month-sort", no_argument, NULL, 'M'}, + {"numeric-sort", no_argument, NULL, 'n'}, + {"random-sort", no_argument, NULL, 'R'}, + {"random-source", required_argument, NULL, RANDOM_SOURCE_OPTION}, + {"output", required_argument, NULL, 'o'}, + {"reverse", no_argument, NULL, 'r'}, + {"stable", no_argument, NULL, 's'}, + {"buffer-size", required_argument, NULL, 'S'}, + {"field-separator", required_argument, NULL, 't'}, + {"temporary-directory", required_argument, NULL, 'T'}, + {"unique", no_argument, NULL, 'u'}, + {"zero-terminated", no_argument, NULL, 'z'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0}, +}; + +static char const *const check_args[] = +{ + "quiet", "silent", "diagnose-first", NULL +}; +static char const check_types[] = +{ + 'C', 'C', 'c' +}; +ARGMATCH_VERIFY (check_args, check_types); + +/* The set of signals that are caught. */ +static sigset_t caught_signals; + +/* Critical section status. */ +struct cs_status +{ + bool valid; + sigset_t sigs; +}; + +/* Enter a critical section. */ +static struct cs_status +cs_enter (void) +{ + struct cs_status status; + status.valid = (sigprocmask (SIG_BLOCK, &caught_signals, &status.sigs) == 0); + return status; +} + +/* Leave a critical section. */ +static void +cs_leave (struct cs_status status) +{ + if (status.valid) + { + /* Ignore failure when restoring the signal mask. */ + sigprocmask (SIG_SETMASK, &status.sigs, NULL); + } +} + +/* The list of temporary files. */ +struct tempnode +{ + struct tempnode *volatile next; + pid_t pid; /* If compressed, the pid of compressor, else zero */ + char name[1]; /* Actual size is 1 + file name length. */ +}; +static struct tempnode *volatile temphead; +static struct tempnode *volatile *temptail = &temphead; + +struct sortfile +{ + char const *name; + pid_t pid; /* If compressed, the pid of compressor, else zero */ +}; + +/* A table where we store compression process states. We clean up all + processes in a timely manner so as not to exhaust system resources, + so we store the info on whether the process is still running, or has + been reaped here. */ +static Hash_table *proctab; + +enum { INIT_PROCTAB_SIZE = 47 }; + +enum procstate { ALIVE, ZOMBIE }; + +/* A proctab entry. The COUNT field is there in case we fork a new + compression process that has the same PID as an old zombie process + that is still in the table (because the process to decompress the + temp file it was associated with hasn't started yet). */ +struct procnode +{ + pid_t pid; + enum procstate state; + size_t count; +}; + +static size_t +proctab_hasher (const void *entry, size_t tabsize) +{ + const struct procnode *node = entry; + return node->pid % tabsize; +} + +static bool +proctab_comparator (const void *e1, const void *e2) +{ + const struct procnode *n1 = e1, *n2 = e2; + return n1->pid == n2->pid; +} + +/* The total number of forked processes (compressors and decompressors) + that have not been reaped yet. */ +static size_t nprocs; + +/* The number of child processes we'll allow before we try to reap some. */ +enum { MAX_PROCS_BEFORE_REAP = 2 }; + +/* If 0 < PID, wait for the child process with that PID to exit. + If PID is -1, clean up a random child process which has finished and + return the process ID of that child. If PID is -1 and no processes + have quit yet, return 0 without waiting. */ + +static pid_t +reap (pid_t pid) +{ + int status; + pid_t cpid = waitpid (pid, &status, pid < 0 ? WNOHANG : 0); + + if (cpid < 0) + error (SORT_FAILURE, errno, _("waiting for %s [-d]"), + compress_program); + else if (0 < cpid) + { + if (! WIFEXITED (status) || WEXITSTATUS (status)) + error (SORT_FAILURE, 0, _("%s [-d] terminated abnormally"), + compress_program); + --nprocs; + } + + return cpid; +} + +/* Add the PID of a running compression process to proctab, or update + the entry COUNT and STATE fields if it's already there. This also + creates the table for us the first time it's called. */ + +static void +register_proc (pid_t pid) +{ + struct procnode test, *node; + + if (! proctab) + { + proctab = hash_initialize (INIT_PROCTAB_SIZE, NULL, + proctab_hasher, + proctab_comparator, + free); + if (! proctab) + xalloc_die (); + } + + test.pid = pid; + node = hash_lookup (proctab, &test); + if (node) + { + node->state = ALIVE; + ++node->count; + } + else + { + node = xmalloc (sizeof *node); + node->pid = pid; + node->state = ALIVE; + node->count = 1; + hash_insert (proctab, node); + } +} + +/* This is called when we reap a random process. We don't know + whether we have reaped a compression process or a decompression + process until we look in the table. If there's an ALIVE entry for + it, then we have reaped a compression process, so change the state + to ZOMBIE. Otherwise, it's a decompression processes, so ignore it. */ + +static void +update_proc (pid_t pid) +{ + struct procnode test, *node; + + test.pid = pid; + node = hash_lookup (proctab, &test); + if (node) + node->state = ZOMBIE; +} + +/* This is for when we need to wait for a compression process to exit. + If it has a ZOMBIE entry in the table then it's already dead and has + been reaped. Note that if there's an ALIVE entry for it, it still may + already have died and been reaped if a second process was created with + the same PID. This is probably exceedingly rare, but to be on the safe + side we will have to wait for any compression process with this PID. */ + +static void +wait_proc (pid_t pid) +{ + struct procnode test, *node; + + test.pid = pid; + node = hash_lookup (proctab, &test); + if (node->state == ALIVE) + reap (pid); + + node->state = ZOMBIE; + if (! --node->count) + { + hash_delete (proctab, node); + free (node); + } +} + +/* Keep reaping finished children as long as there are more to reap. + This doesn't block waiting for any of them, it only reaps those + that are already dead. */ + +static void +reap_some (void) +{ + pid_t pid; + + while (0 < nprocs && (pid = reap (-1))) + update_proc (pid); +} + +/* Clean up any remaining temporary files. */ + +static void +cleanup (void) +{ + struct tempnode const *node; + + for (node = temphead; node; node = node->next) + unlink (node->name); + temphead = NULL; +} + +/* Cleanup actions to take when exiting. */ + +static void +exit_cleanup (void) +{ + if (temphead) + { + /* Clean up any remaining temporary files in a critical section so + that a signal handler does not try to clean them too. */ + struct cs_status cs = cs_enter (); + cleanup (); + cs_leave (cs); + } + + close_stdout (); +} + +/* Create a new temporary file, returning its newly allocated tempnode. + Store into *PFD the file descriptor open for writing. */ + +static struct tempnode * +create_temp_file (int *pfd) +{ + static char const slashbase[] = "/sortXXXXXX"; + static size_t temp_dir_index; + int fd; + int saved_errno; + char const *temp_dir = temp_dirs[temp_dir_index]; + size_t len = strlen (temp_dir); + struct tempnode *node = + xmalloc (offsetof (struct tempnode, name) + len + sizeof slashbase); + char *file = node->name; + struct cs_status cs; + + memcpy (file, temp_dir, len); + memcpy (file + len, slashbase, sizeof slashbase); + node->next = NULL; + node->pid = 0; + if (++temp_dir_index == temp_dir_count) + temp_dir_index = 0; + + /* Create the temporary file in a critical section, to avoid races. */ + cs = cs_enter (); + fd = mkstemp (file); + if (0 <= fd) + { + *temptail = node; + temptail = &node->next; + } + saved_errno = errno; + cs_leave (cs); + errno = saved_errno; + + if (fd < 0) + die (_("cannot create temporary file"), file); + + *pfd = fd; + return node; +} + +/* Return a stream for FILE, opened with mode HOW. A null FILE means + standard output; HOW should be "w". When opening for input, "-" + means standard input. To avoid confusion, do not return file + descriptors STDIN_FILENO, STDOUT_FILENO, or STDERR_FILENO when + opening an ordinary FILE. */ + +static FILE * +xfopen (const char *file, const char *how) +{ + FILE *fp; + + if (!file) + fp = stdout; + else if (STREQ (file, "-") && *how == 'r') + { + have_read_stdin = true; + fp = stdin; + } + else + { + fp = fopen (file, how); + if (! fp) + die (_("open failed"), file); + } + + return fp; +} + +/* Close FP, whose name is FILE, and report any errors. */ + +static void +xfclose (FILE *fp, char const *file) +{ + switch (fileno (fp)) + { + case STDIN_FILENO: + /* Allow reading stdin from tty more than once. */ + if (feof (fp)) + clearerr (fp); + break; + + case STDOUT_FILENO: + /* Don't close stdout just yet. close_stdout does that. */ + if (fflush (fp) != 0) + die (_("fflush failed"), file); + break; + + default: + if (fclose (fp) != 0) + die (_("close failed"), file); + break; + } +} + +static void +dup2_or_die (int oldfd, int newfd) +{ + if (dup2 (oldfd, newfd) < 0) + error (SORT_FAILURE, errno, _("dup2 failed")); +} + +/* Fork a child process for piping to and do common cleanup. The + TRIES parameter tells us how many times to try to fork before + giving up. Return the PID of the child or -1 if fork failed. */ + +static pid_t +pipe_fork (int pipefds[2], size_t tries) +{ +#if HAVE_WORKING_FORK + struct tempnode *saved_temphead; + int saved_errno; + unsigned int wait_retry = 1; + pid_t pid IF_LINT (= -1); + struct cs_status cs; + + if (pipe (pipefds) < 0) + return -1; + + while (tries--) + { + /* This is so the child process won't delete our temp files + if it receives a signal before exec-ing. */ + cs = cs_enter (); + saved_temphead = temphead; + temphead = NULL; + + pid = fork (); + saved_errno = errno; + if (pid) + temphead = saved_temphead; + + cs_leave (cs); + errno = saved_errno; + + if (0 <= pid || errno != EAGAIN) + break; + else + { + sleep (wait_retry); + wait_retry *= 2; + reap_some (); + } + } + + if (pid < 0) + { + close (pipefds[0]); + close (pipefds[1]); + } + else if (pid == 0) + { + close (STDIN_FILENO); + close (STDOUT_FILENO); + } + else + ++nprocs; + + return pid; + +#else /* ! HAVE_WORKING_FORK */ + return -1; +#endif +} + +/* Create a temporary file and start a compression program to filter output + to that file. Set *PFP to the file handle and if *PPID is non-NULL, + set it to the PID of the newly-created process. */ + +static char * +create_temp (FILE **pfp, pid_t *ppid) +{ + int tempfd; + struct tempnode *node = create_temp_file (&tempfd); + char *name = node->name; + + if (compress_program) + { + int pipefds[2]; + + node->pid = pipe_fork (pipefds, MAX_FORK_TRIES_COMPRESS); + if (0 < node->pid) + { + close (tempfd); + close (pipefds[0]); + tempfd = pipefds[1]; + + register_proc (node->pid); + } + else if (node->pid == 0) + { + close (pipefds[1]); + dup2_or_die (tempfd, STDOUT_FILENO); + close (tempfd); + dup2_or_die (pipefds[0], STDIN_FILENO); + close (pipefds[0]); + + if (execlp (compress_program, compress_program, (char *) NULL) < 0) + error (SORT_FAILURE, errno, _("couldn't execute %s"), + compress_program); + } + else + node->pid = 0; + } + + *pfp = fdopen (tempfd, "w"); + if (! *pfp) + die (_("couldn't create temporary file"), name); + + if (ppid) + *ppid = node->pid; + + return name; +} + +/* Open a compressed temp file and start a decompression process through + which to filter the input. PID must be the valid processes ID of the + process used to compress the file. */ + +static FILE * +open_temp (const char *name, pid_t pid) +{ + int tempfd, pipefds[2]; + pid_t child_pid; + FILE *fp; + + wait_proc (pid); + + tempfd = open (name, O_RDONLY); + if (tempfd < 0) + die (_("couldn't open temporary file"), name); + + child_pid = pipe_fork (pipefds, MAX_FORK_TRIES_DECOMPRESS); + if (0 < child_pid) + { + close (tempfd); + close (pipefds[1]); + } + else if (child_pid == 0) + { + close (pipefds[0]); + dup2_or_die (tempfd, STDIN_FILENO); + close (tempfd); + dup2_or_die (pipefds[1], STDOUT_FILENO); + close (pipefds[1]); + + if (execlp (compress_program, compress_program, "-d", (char *) NULL) < 0) + error (SORT_FAILURE, errno, _("couldn't execute %s -d"), + compress_program); + } + else + error (SORT_FAILURE, errno, _("couldn't create process for %s -d"), + compress_program); + + fp = fdopen (pipefds[0], "r"); + if (! fp) + die (_("couldn't create temporary file"), name); + + return fp; +} + +static void +write_bytes (const char *buf, size_t n_bytes, FILE *fp, const char *output_file) +{ + if (fwrite (buf, 1, n_bytes, fp) != n_bytes) + die (_("write failed"), output_file); +} + +/* Append DIR to the array of temporary directory names. */ +static void +add_temp_dir (char const *dir) +{ + if (temp_dir_count == temp_dir_alloc) + temp_dirs = X2NREALLOC (temp_dirs, &temp_dir_alloc); + + temp_dirs[temp_dir_count++] = dir; +} + +/* Remove NAME from the list of temporary files. */ + +static void +zaptemp (const char *name) +{ + struct tempnode *volatile *pnode; + struct tempnode *node; + struct tempnode *next; + int unlink_status; + int unlink_errno = 0; + struct cs_status cs; + + for (pnode = &temphead; (node = *pnode)->name != name; pnode = &node->next) + continue; + + /* Unlink the temporary file in a critical section to avoid races. */ + next = node->next; + cs = cs_enter (); + unlink_status = unlink (name); + unlink_errno = errno; + *pnode = next; + cs_leave (cs); + + if (unlink_status != 0) + error (0, unlink_errno, _("warning: cannot remove: %s"), name); + if (! next) + temptail = pnode; + free (node); +} + +#if HAVE_NL_LANGINFO + +static int +struct_month_cmp (const void *m1, const void *m2) +{ + struct month const *month1 = m1; + struct month const *month2 = m2; + return strcmp (month1->name, month2->name); +} + +#endif + +/* Initialize the character class tables. */ + +static void +inittables (void) +{ + size_t i; + + for (i = 0; i < UCHAR_LIM; ++i) + { + blanks[i] = !! isblank (i); + nonprinting[i] = ! isprint (i); + nondictionary[i] = ! isalnum (i) && ! isblank (i); + fold_toupper[i] = toupper (i); + } + +#if HAVE_NL_LANGINFO + /* If we're not in the "C" locale, read different names for months. */ + if (hard_LC_TIME) + { + for (i = 0; i < MONTHS_PER_YEAR; i++) + { + char const *s; + size_t s_len; + size_t j; + char *name; + + s = (char *) nl_langinfo (ABMON_1 + i); + s_len = strlen (s); + monthtab[i].name = name = xmalloc (s_len + 1); + monthtab[i].val = i + 1; + + for (j = 0; j < s_len; j++) + name[j] = fold_toupper[to_uchar (s[j])]; + name[j] = '\0'; + } + qsort ((void *) monthtab, MONTHS_PER_YEAR, + sizeof *monthtab, struct_month_cmp); + } +#endif +} + +/* Specify the amount of main memory to use when sorting. */ +static void +specify_sort_size (char const *s) +{ + uintmax_t n; + char *suffix; + enum strtol_error e = xstrtoumax (s, &suffix, 10, &n, "EgGkKmMPtTYZ"); + + /* The default unit is KiB. */ + if (e == LONGINT_OK && ISDIGIT (suffix[-1])) + { + if (n <= UINTMAX_MAX / 1024) + n *= 1024; + else + e = LONGINT_OVERFLOW; + } + + /* A 'b' suffix means bytes; a '%' suffix means percent of memory. */ + if (e == LONGINT_INVALID_SUFFIX_CHAR && ISDIGIT (suffix[-1]) && ! suffix[1]) + switch (suffix[0]) + { + case 'b': + e = LONGINT_OK; + break; + + case '%': + { + double mem = physmem_total () * n / 100; + + /* Use "<", not "<=", to avoid problems with rounding. */ + if (mem < UINTMAX_MAX) + { + n = mem; + e = LONGINT_OK; + } + else + e = LONGINT_OVERFLOW; + } + break; + } + + if (e == LONGINT_OK) + { + /* If multiple sort sizes are specified, take the maximum, so + that option order does not matter. */ + if (n < sort_size) + return; + + sort_size = n; + if (sort_size == n) + { + sort_size = MAX (sort_size, MIN_SORT_SIZE); + return; + } + + e = LONGINT_OVERFLOW; + } + + STRTOL_FATAL_ERROR (s, _("sort size"), e); +} + +/* Return the default sort size. */ +static size_t +default_sort_size (void) +{ + /* Let MEM be available memory or 1/8 of total memory, whichever + is greater. */ + double avail = physmem_available (); + double total = physmem_total (); + double mem = MAX (avail, total / 8); + struct rlimit rlimit; + + /* Let SIZE be MEM, but no more than the maximum object size or + system resource limits. Avoid the MIN macro here, as it is not + quite right when only one argument is floating point. Don't + bother to check for values like RLIM_INFINITY since in practice + they are not much less than SIZE_MAX. */ + size_t size = SIZE_MAX; + if (mem < size) + size = mem; + if (getrlimit (RLIMIT_DATA, &rlimit) == 0 && rlimit.rlim_cur < size) + size = rlimit.rlim_cur; +#ifdef RLIMIT_AS + if (getrlimit (RLIMIT_AS, &rlimit) == 0 && rlimit.rlim_cur < size) + size = rlimit.rlim_cur; +#endif + + /* Leave a large safety margin for the above limits, as failure can + occur when they are exceeded. */ + size /= 2; + +#ifdef RLIMIT_RSS + /* Leave a 1/16 margin for RSS to leave room for code, stack, etc. + Exceeding RSS is not fatal, but can be quite slow. */ + if (getrlimit (RLIMIT_RSS, &rlimit) == 0 && rlimit.rlim_cur / 16 * 15 < size) + size = rlimit.rlim_cur / 16 * 15; +#endif + + /* Use no less than the minimum. */ + return MAX (size, MIN_SORT_SIZE); +} + +/* Return the sort buffer size to use with the input files identified + by FPS and FILES, which are alternate names of the same files. + NFILES gives the number of input files; NFPS may be less. Assume + that each input line requires LINE_BYTES extra bytes' worth of line + information. Do not exceed the size bound specified by the user + (or a default size bound, if the user does not specify one). */ + +static size_t +sort_buffer_size (FILE *const *fps, size_t nfps, + char *const *files, size_t nfiles, + size_t line_bytes) +{ + /* A bound on the input size. If zero, the bound hasn't been + determined yet. */ + static size_t size_bound; + + /* In the worst case, each input byte is a newline. */ + size_t worst_case_per_input_byte = line_bytes + 1; + + /* Keep enough room for one extra input line and an extra byte. + This extra room might be needed when preparing to read EOF. */ + size_t size = worst_case_per_input_byte + 1; + + size_t i; + + for (i = 0; i < nfiles; i++) + { + struct stat st; + off_t file_size; + size_t worst_case; + + if ((i < nfps ? fstat (fileno (fps[i]), &st) + : STREQ (files[i], "-") ? fstat (STDIN_FILENO, &st) + : stat (files[i], &st)) + != 0) + die (_("stat failed"), files[i]); + + if (S_ISREG (st.st_mode)) + file_size = st.st_size; + else + { + /* The file has unknown size. If the user specified a sort + buffer size, use that; otherwise, guess the size. */ + if (sort_size) + return sort_size; + file_size = INPUT_FILE_SIZE_GUESS; + } + + if (! size_bound) + { + size_bound = sort_size; + if (! size_bound) + size_bound = default_sort_size (); + } + + /* Add the amount of memory needed to represent the worst case + where the input consists entirely of newlines followed by a + single non-newline. Check for overflow. */ + worst_case = file_size * worst_case_per_input_byte + 1; + if (file_size != worst_case / worst_case_per_input_byte + || size_bound - size <= worst_case) + return size_bound; + size += worst_case; + } + + return size; +} + +/* Initialize BUF. Reserve LINE_BYTES bytes for each line; LINE_BYTES + must be at least sizeof (struct line). Allocate ALLOC bytes + initially. */ + +static void +initbuf (struct buffer *buf, size_t line_bytes, size_t alloc) +{ + /* Ensure that the line array is properly aligned. If the desired + size cannot be allocated, repeatedly halve it until allocation + succeeds. The smaller allocation may hurt overall performance, + but that's better than failing. */ + for (;;) + { + alloc += sizeof (struct line) - alloc % sizeof (struct line); + buf->buf = malloc (alloc); + if (buf->buf) + break; + alloc /= 2; + if (alloc <= line_bytes + 1) + xalloc_die (); + } + + buf->line_bytes = line_bytes; + buf->alloc = alloc; + buf->used = buf->left = buf->nlines = 0; + buf->eof = false; +} + +/* Return one past the limit of the line array. */ + +static inline struct line * +buffer_linelim (struct buffer const *buf) +{ + return (struct line *) (buf->buf + buf->alloc); +} + +/* Return a pointer to the first character of the field specified + by KEY in LINE. */ + +static char * +begfield (const struct line *line, const struct keyfield *key) +{ + char *ptr = line->text, *lim = ptr + line->length - 1; + size_t sword = key->sword; + size_t schar = key->schar; + size_t remaining_bytes; + + /* The leading field separator itself is included in a field when -t + is absent. */ + + if (tab != TAB_DEFAULT) + while (ptr < lim && sword--) + { + while (ptr < lim && *ptr != tab) + ++ptr; + if (ptr < lim) + ++ptr; + } + else + while (ptr < lim && sword--) + { + while (ptr < lim && blanks[to_uchar (*ptr)]) + ++ptr; + while (ptr < lim && !blanks[to_uchar (*ptr)]) + ++ptr; + } + + if (key->skipsblanks) + while (ptr < lim && blanks[to_uchar (*ptr)]) + ++ptr; + + /* Advance PTR by SCHAR (if possible), but no further than LIM. */ + remaining_bytes = lim - ptr; + if (schar < remaining_bytes) + ptr += schar; + else + ptr = lim; + + return ptr; +} + +/* Return the limit of (a pointer to the first character after) the field + in LINE specified by KEY. */ + +static char * +limfield (const struct line *line, const struct keyfield *key) +{ + char *ptr = line->text, *lim = ptr + line->length - 1; + size_t eword = key->eword, echar = key->echar; + size_t remaining_bytes; + + /* Move PTR past EWORD fields or to one past the last byte on LINE, + whichever comes first. If there are more than EWORD fields, leave + PTR pointing at the beginning of the field having zero-based index, + EWORD. If a delimiter character was specified (via -t), then that + `beginning' is the first character following the delimiting TAB. + Otherwise, leave PTR pointing at the first `blank' character after + the preceding field. */ + if (tab != TAB_DEFAULT) + while (ptr < lim && eword--) + { + while (ptr < lim && *ptr != tab) + ++ptr; + if (ptr < lim && (eword | echar)) + ++ptr; + } + else + while (ptr < lim && eword--) + { + while (ptr < lim && blanks[to_uchar (*ptr)]) + ++ptr; + while (ptr < lim && !blanks[to_uchar (*ptr)]) + ++ptr; + } + +#ifdef POSIX_UNSPECIFIED + /* The following block of code makes GNU sort incompatible with + standard Unix sort, so it's ifdef'd out for now. + The POSIX spec isn't clear on how to interpret this. + FIXME: request clarification. + + From: kwzh@gnu.ai.mit.edu (Karl Heuer) + Date: Thu, 30 May 96 12:20:41 -0400 + [Translated to POSIX 1003.1-2001 terminology by Paul Eggert.] + + [...]I believe I've found another bug in `sort'. + + $ cat /tmp/sort.in + a b c 2 d + pq rs 1 t + $ textutils-1.15/src/sort -k1.7,1.7 skipeblanks) + while (ptr < lim && blanks[to_uchar (*ptr)]) + ++ptr; + + /* Advance PTR by ECHAR (if possible), but no further than LIM. */ + remaining_bytes = lim - ptr; + if (echar < remaining_bytes) + ptr += echar; + else + ptr = lim; + + return ptr; +} + +/* Fill BUF reading from FP, moving buf->left bytes from the end + of buf->buf to the beginning first. If EOF is reached and the + file wasn't terminated by a newline, supply one. Set up BUF's line + table too. FILE is the name of the file corresponding to FP. + Return true if some input was read. */ + +static bool +fillbuf (struct buffer *buf, FILE *fp, char const *file) +{ + struct keyfield const *key = keylist; + char eol = eolchar; + size_t line_bytes = buf->line_bytes; + size_t mergesize = merge_buffer_size - MIN_MERGE_BUFFER_SIZE; + + if (buf->eof) + return false; + + if (buf->used != buf->left) + { + memmove (buf->buf, buf->buf + buf->used - buf->left, buf->left); + buf->used = buf->left; + buf->nlines = 0; + } + + for (;;) + { + char *ptr = buf->buf + buf->used; + struct line *linelim = buffer_linelim (buf); + struct line *line = linelim - buf->nlines; + size_t avail = (char *) linelim - buf->nlines * line_bytes - ptr; + char *line_start = buf->nlines ? line->text + line->length : buf->buf; + + while (line_bytes + 1 < avail) + { + /* Read as many bytes as possible, but do not read so many + bytes that there might not be enough room for the + corresponding line array. The worst case is when the + rest of the input file consists entirely of newlines, + except that the last byte is not a newline. */ + size_t readsize = (avail - 1) / (line_bytes + 1); + size_t bytes_read = fread (ptr, 1, readsize, fp); + char *ptrlim = ptr + bytes_read; + char *p; + avail -= bytes_read; + + if (bytes_read != readsize) + { + if (ferror (fp)) + die (_("read failed"), file); + if (feof (fp)) + { + buf->eof = true; + if (buf->buf == ptrlim) + return false; + if (ptrlim[-1] != eol) + *ptrlim++ = eol; + } + } + + /* Find and record each line in the just-read input. */ + while ((p = memchr (ptr, eol, ptrlim - ptr))) + { + ptr = p + 1; + line--; + line->text = line_start; + line->length = ptr - line_start; + mergesize = MAX (mergesize, line->length); + avail -= line_bytes; + + if (key) + { + /* Precompute the position of the first key for + efficiency. */ + line->keylim = (key->eword == SIZE_MAX + ? p + : limfield (line, key)); + + if (key->sword != SIZE_MAX) + line->keybeg = begfield (line, key); + else + { + if (key->skipsblanks) + while (blanks[to_uchar (*line_start)]) + line_start++; + line->keybeg = line_start; + } + } + + line_start = ptr; + } + + ptr = ptrlim; + if (buf->eof) + break; + } + + buf->used = ptr - buf->buf; + buf->nlines = buffer_linelim (buf) - line; + if (buf->nlines != 0) + { + buf->left = ptr - line_start; + merge_buffer_size = mergesize + MIN_MERGE_BUFFER_SIZE; + return true; + } + + /* The current input line is too long to fit in the buffer. + Double the buffer size and try again. */ + buf->buf = X2REALLOC (buf->buf, &buf->alloc); + } +} + +/* Compare strings A and B as numbers without explicitly converting them to + machine numbers. Comparatively slow for short strings, but asymptotically + hideously fast. */ + +static int +numcompare (const char *a, const char *b) +{ + while (blanks[to_uchar (*a)]) + a++; + while (blanks[to_uchar (*b)]) + b++; + + return strnumcmp (a, b, decimal_point, thousands_sep); +} + +static int +general_numcompare (const char *sa, const char *sb) +{ + /* FIXME: add option to warn about failed conversions. */ + /* FIXME: maybe add option to try expensive FP conversion + only if A and B can't be compared more cheaply/accurately. */ + + char *ea; + char *eb; + double a = strtod (sa, &ea); + double b = strtod (sb, &eb); + + /* Put conversion errors at the start of the collating sequence. */ + if (sa == ea) + return sb == eb ? 0 : -1; + if (sb == eb) + return 1; + + /* Sort numbers in the usual way, where -0 == +0. Put NaNs after + conversion errors but before numbers; sort them by internal + bit-pattern, for lack of a more portable alternative. */ + return (a < b ? -1 + : a > b ? 1 + : a == b ? 0 + : b == b ? -1 + : a == a ? 1 + : memcmp ((char *) &a, (char *) &b, sizeof a)); +} + +/* Return an integer in 1..12 of the month name MONTH with length LEN. + Return 0 if the name in S is not recognized. */ + +static int +getmonth (char const *month, size_t len) +{ + size_t lo = 0; + size_t hi = MONTHS_PER_YEAR; + char const *monthlim = month + len; + + for (;;) + { + if (month == monthlim) + return 0; + if (!blanks[to_uchar (*month)]) + break; + ++month; + } + + do + { + size_t ix = (lo + hi) / 2; + char const *m = month; + char const *n = monthtab[ix].name; + + for (;; m++, n++) + { + if (!*n) + return monthtab[ix].val; + if (m == monthlim || fold_toupper[to_uchar (*m)] < to_uchar (*n)) + { + hi = ix; + break; + } + else if (fold_toupper[to_uchar (*m)] > to_uchar (*n)) + { + lo = ix + 1; + break; + } + } + } + while (lo < hi); + + return 0; +} + +/* A source of random data. */ +static struct randread_source *randread_source; + +/* Return the Ith randomly-generated state. The caller must invoke + random_state (H) for all H less than I before invoking random_state + (I). */ + +static struct md5_ctx +random_state (size_t i) +{ + /* An array of states resulting from the random data, and counts of + its used and allocated members. */ + static struct md5_ctx *state; + static size_t used; + static size_t allocated; + + struct md5_ctx *s = &state[i]; + + if (used <= i) + { + unsigned char buf[MD5_DIGEST_SIZE]; + + used++; + + if (allocated <= i) + { + state = X2NREALLOC (state, &allocated); + s = &state[i]; + } + + randread (randread_source, buf, sizeof buf); + md5_init_ctx (s); + md5_process_bytes (buf, sizeof buf, s); + } + + return *s; +} + +/* Compare the hashes of TEXTA with length LENGTHA to those of TEXTB + with length LENGTHB. Return negative if less, zero if equal, + positive if greater. */ + +static int +cmp_hashes (char const *texta, size_t lena, + char const *textb, size_t lenb) +{ + /* Try random hashes until a pair of hashes disagree. But if the + first pair of random hashes agree, check whether the keys are + identical and if so report no difference. */ + int diff; + size_t i; + for (i = 0; ; i++) + { + uint32_t dig[2][MD5_DIGEST_SIZE / sizeof (uint32_t)]; + struct md5_ctx s[2]; + s[0] = s[1] = random_state (i); + md5_process_bytes (texta, lena, &s[0]); md5_finish_ctx (&s[0], dig[0]); + md5_process_bytes (textb, lenb, &s[1]); md5_finish_ctx (&s[1], dig[1]); + diff = memcmp (dig[0], dig[1], sizeof dig[0]); + if (diff != 0) + break; + if (i == 0 && lena == lenb && memcmp (texta, textb, lena) == 0) + break; + } + + return diff; +} + +/* Compare the keys TEXTA (of length LENA) and TEXTB (of length LENB) + using one or more random hash functions. */ + +static int +compare_random (char *restrict texta, size_t lena, + char *restrict textb, size_t lenb) +{ + int diff; + + if (! hard_LC_COLLATE) + diff = cmp_hashes (texta, lena, textb, lenb); + else + { + /* Transform the text into the basis of comparison, so that byte + strings that would otherwise considered to be equal are + considered equal here even if their bytes differ. */ + + char *buf = NULL; + char stackbuf[4000]; + size_t tlena = xmemxfrm (stackbuf, sizeof stackbuf, texta, lena); + bool a_fits = tlena <= sizeof stackbuf; + size_t tlenb = xmemxfrm ((a_fits ? stackbuf + tlena : NULL), + (a_fits ? sizeof stackbuf - tlena : 0), + textb, lenb); + + if (a_fits && tlena + tlenb <= sizeof stackbuf) + buf = stackbuf; + else + { + /* Adding 1 to the buffer size lets xmemxfrm run a bit + faster by avoiding the need for an extra buffer copy. */ + buf = xmalloc (tlena + tlenb + 1); + xmemxfrm (buf, tlena + 1, texta, lena); + xmemxfrm (buf + tlena, tlenb + 1, textb, lenb); + } + + diff = cmp_hashes (buf, tlena, buf + tlena, tlenb); + + if (buf != stackbuf) + free (buf); + } + + return diff; +} + +/* Compare two lines A and B trying every key in sequence until there + are no more keys or a difference is found. */ + +static int +keycompare (const struct line *a, const struct line *b) +{ + struct keyfield const *key = keylist; + + /* For the first iteration only, the key positions have been + precomputed for us. */ + char *texta = a->keybeg; + char *textb = b->keybeg; + char *lima = a->keylim; + char *limb = b->keylim; + + int diff; + + for (;;) + { + char const *translate = key->translate; + bool const *ignore = key->ignore; + + /* Find the lengths. */ + size_t lena = lima <= texta ? 0 : lima - texta; + size_t lenb = limb <= textb ? 0 : limb - textb; + + /* Actually compare the fields. */ + + if (key->random) + diff = compare_random (texta, lena, textb, lenb); + else if (key->numeric | key->general_numeric) + { + char savea = *lima, saveb = *limb; + + *lima = *limb = '\0'; + diff = ((key->numeric ? numcompare : general_numcompare) + (texta, textb)); + *lima = savea, *limb = saveb; + } + else if (key->month) + diff = getmonth (texta, lena) - getmonth (textb, lenb); + /* Sorting like this may become slow, so in a simple locale the user + can select a faster sort that is similar to ascii sort. */ + else if (hard_LC_COLLATE) + { + if (ignore || translate) + { + char buf[4000]; + size_t size = lena + 1 + lenb + 1; + char *copy_a = (size <= sizeof buf ? buf : xmalloc (size)); + char *copy_b = copy_a + lena + 1; + size_t new_len_a, new_len_b, i; + + /* Ignore and/or translate chars before comparing. */ + for (new_len_a = new_len_b = i = 0; i < MAX (lena, lenb); i++) + { + if (i < lena) + { + copy_a[new_len_a] = (translate + ? translate[to_uchar (texta[i])] + : texta[i]); + if (!ignore || !ignore[to_uchar (texta[i])]) + ++new_len_a; + } + if (i < lenb) + { + copy_b[new_len_b] = (translate + ? translate[to_uchar (textb[i])] + : textb [i]); + if (!ignore || !ignore[to_uchar (textb[i])]) + ++new_len_b; + } + } + + diff = xmemcoll (copy_a, new_len_a, copy_b, new_len_b); + + if (sizeof buf < size) + free (copy_a); + } + else if (lena == 0) + diff = - NONZERO (lenb); + else if (lenb == 0) + goto greater; + else + diff = xmemcoll (texta, lena, textb, lenb); + } + else if (ignore) + { +#define CMP_WITH_IGNORE(A, B) \ + do \ + { \ + for (;;) \ + { \ + while (texta < lima && ignore[to_uchar (*texta)]) \ + ++texta; \ + while (textb < limb && ignore[to_uchar (*textb)]) \ + ++textb; \ + if (! (texta < lima && textb < limb)) \ + break; \ + diff = to_uchar (A) - to_uchar (B); \ + if (diff) \ + goto not_equal; \ + ++texta; \ + ++textb; \ + } \ + \ + diff = (texta < lima) - (textb < limb); \ + } \ + while (0) + + if (translate) + CMP_WITH_IGNORE (translate[to_uchar (*texta)], + translate[to_uchar (*textb)]); + else + CMP_WITH_IGNORE (*texta, *textb); + } + else if (lena == 0) + diff = - NONZERO (lenb); + else if (lenb == 0) + goto greater; + else + { + if (translate) + { + while (texta < lima && textb < limb) + { + diff = (to_uchar (translate[to_uchar (*texta++)]) + - to_uchar (translate[to_uchar (*textb++)])); + if (diff) + goto not_equal; + } + } + else + { + diff = memcmp (texta, textb, MIN (lena, lenb)); + if (diff) + goto not_equal; + } + diff = lena < lenb ? -1 : lena != lenb; + } + + if (diff) + goto not_equal; + + key = key->next; + if (! key) + break; + + /* Find the beginning and limit of the next field. */ + if (key->eword != SIZE_MAX) + lima = limfield (a, key), limb = limfield (b, key); + else + lima = a->text + a->length - 1, limb = b->text + b->length - 1; + + if (key->sword != SIZE_MAX) + texta = begfield (a, key), textb = begfield (b, key); + else + { + texta = a->text, textb = b->text; + if (key->skipsblanks) + { + while (texta < lima && blanks[to_uchar (*texta)]) + ++texta; + while (textb < limb && blanks[to_uchar (*textb)]) + ++textb; + } + } + } + + return 0; + + greater: + diff = 1; + not_equal: + return key->reverse ? -diff : diff; +} + +/* Compare two lines A and B, returning negative, zero, or positive + depending on whether A compares less than, equal to, or greater than B. */ + +static int +compare (const struct line *a, const struct line *b) +{ + int diff; + size_t alen, blen; + + /* First try to compare on the specified keys (if any). + The only two cases with no key at all are unadorned sort, + and unadorned sort -r. */ + if (keylist) + { + diff = keycompare (a, b); + if (diff | unique | stable) + return diff; + } + + /* If the keys all compare equal (or no keys were specified) + fall through to the default comparison. */ + alen = a->length - 1, blen = b->length - 1; + + if (alen == 0) + diff = - NONZERO (blen); + else if (blen == 0) + diff = 1; + else if (hard_LC_COLLATE) + diff = xmemcoll (a->text, alen, b->text, blen); + else if (! (diff = memcmp (a->text, b->text, MIN (alen, blen)))) + diff = alen < blen ? -1 : alen != blen; + + return reverse ? -diff : diff; +} + +/* Check that the lines read from FILE_NAME come in order. Return + true if they are in order. If CHECKONLY == 'c', also print a + diagnostic (FILE_NAME, line number, contents of line) to stderr if + they are not in order. */ + +static bool +check (char const *file_name, char checkonly) +{ + FILE *fp = xfopen (file_name, "r"); + struct buffer buf; /* Input buffer. */ + struct line temp; /* Copy of previous line. */ + size_t alloc = 0; + uintmax_t line_number = 0; + struct keyfield const *key = keylist; + bool nonunique = ! unique; + bool ordered = true; + + initbuf (&buf, sizeof (struct line), + MAX (merge_buffer_size, sort_size)); + temp.text = NULL; + + while (fillbuf (&buf, fp, file_name)) + { + struct line const *line = buffer_linelim (&buf); + struct line const *linebase = line - buf.nlines; + + /* Make sure the line saved from the old buffer contents is + less than or equal to the first line of the new buffer. */ + if (alloc && nonunique <= compare (&temp, line - 1)) + { + found_disorder: + { + if (checkonly == 'c') + { + struct line const *disorder_line = line - 1; + uintmax_t disorder_line_number = + buffer_linelim (&buf) - disorder_line + line_number; + char hr_buf[INT_BUFSIZE_BOUND (uintmax_t)]; + fprintf (stderr, _("%s: %s:%s: disorder: "), + program_name, file_name, + umaxtostr (disorder_line_number, hr_buf)); + write_bytes (disorder_line->text, disorder_line->length, + stderr, _("standard error")); + } + + ordered = false; + break; + } + } + + /* Compare each line in the buffer with its successor. */ + while (linebase < --line) + if (nonunique <= compare (line, line - 1)) + goto found_disorder; + + line_number += buf.nlines; + + /* Save the last line of the buffer. */ + if (alloc < line->length) + { + do + { + alloc *= 2; + if (! alloc) + { + alloc = line->length; + break; + } + } + while (alloc < line->length); + + temp.text = xrealloc (temp.text, alloc); + } + memcpy (temp.text, line->text, line->length); + temp.length = line->length; + if (key) + { + temp.keybeg = temp.text + (line->keybeg - line->text); + temp.keylim = temp.text + (line->keylim - line->text); + } + } + + xfclose (fp, file_name); + free (buf.buf); + free (temp.text); + return ordered; +} + +/* Merge lines from FILES onto OFP. NTEMPS is the number of temporary + files (all of which are at the start of the FILES array), and + NFILES is the number of files; 0 <= NTEMPS <= NFILES <= NMERGE. + Close input and output files before returning. + OUTPUT_FILE gives the name of the output file. If it is NULL, + the output file is standard output. If OFP is NULL, the output + file has not been opened yet (or written to, if standard output). */ + +static void +mergefps (struct sortfile *files, size_t ntemps, size_t nfiles, + FILE *ofp, char const *output_file) +{ + FILE *fps[NMERGE]; /* Input streams for each file. */ + struct buffer buffer[NMERGE]; /* Input buffers for each file. */ + struct line saved; /* Saved line storage for unique check. */ + struct line const *savedline = NULL; + /* &saved if there is a saved line. */ + size_t savealloc = 0; /* Size allocated for the saved line. */ + struct line const *cur[NMERGE]; /* Current line in each line table. */ + struct line const *base[NMERGE]; /* Base of each line table. */ + size_t ord[NMERGE]; /* Table representing a permutation of fps, + such that cur[ord[0]] is the smallest line + and will be next output. */ + size_t i; + size_t j; + size_t t; + struct keyfield const *key = keylist; + saved.text = NULL; + + /* Read initial lines from each input file. */ + for (i = 0; i < nfiles; ) + { + fps[i] = (files[i].pid + ? open_temp (files[i].name, files[i].pid) + : xfopen (files[i].name, "r")); + initbuf (&buffer[i], sizeof (struct line), + MAX (merge_buffer_size, sort_size / nfiles)); + if (fillbuf (&buffer[i], fps[i], files[i].name)) + { + struct line const *linelim = buffer_linelim (&buffer[i]); + cur[i] = linelim - 1; + base[i] = linelim - buffer[i].nlines; + i++; + } + else + { + /* fps[i] is empty; eliminate it from future consideration. */ + xfclose (fps[i], files[i].name); + if (i < ntemps) + { + ntemps--; + zaptemp (files[i].name); + } + free (buffer[i].buf); + --nfiles; + for (j = i; j < nfiles; ++j) + files[j] = files[j + 1]; + } + } + + if (! ofp) + ofp = xfopen (output_file, "w"); + + /* Set up the ord table according to comparisons among input lines. + Since this only reorders two items if one is strictly greater than + the other, it is stable. */ + for (i = 0; i < nfiles; ++i) + ord[i] = i; + for (i = 1; i < nfiles; ++i) + if (0 < compare (cur[ord[i - 1]], cur[ord[i]])) + t = ord[i - 1], ord[i - 1] = ord[i], ord[i] = t, i = 0; + + /* Repeatedly output the smallest line until no input remains. */ + while (nfiles) + { + struct line const *smallest = cur[ord[0]]; + + /* If uniquified output is turned on, output only the first of + an identical series of lines. */ + if (unique) + { + if (savedline && compare (savedline, smallest)) + { + savedline = NULL; + write_bytes (saved.text, saved.length, ofp, output_file); + } + if (!savedline) + { + savedline = &saved; + if (savealloc < smallest->length) + { + do + if (! savealloc) + { + savealloc = smallest->length; + break; + } + while ((savealloc *= 2) < smallest->length); + + saved.text = xrealloc (saved.text, savealloc); + } + saved.length = smallest->length; + memcpy (saved.text, smallest->text, saved.length); + if (key) + { + saved.keybeg = + saved.text + (smallest->keybeg - smallest->text); + saved.keylim = + saved.text + (smallest->keylim - smallest->text); + } + } + } + else + write_bytes (smallest->text, smallest->length, ofp, output_file); + + /* Check if we need to read more lines into core. */ + if (base[ord[0]] < smallest) + cur[ord[0]] = smallest - 1; + else + { + if (fillbuf (&buffer[ord[0]], fps[ord[0]], files[ord[0]].name)) + { + struct line const *linelim = buffer_linelim (&buffer[ord[0]]); + cur[ord[0]] = linelim - 1; + base[ord[0]] = linelim - buffer[ord[0]].nlines; + } + else + { + /* We reached EOF on fps[ord[0]]. */ + for (i = 1; i < nfiles; ++i) + if (ord[i] > ord[0]) + --ord[i]; + --nfiles; + xfclose (fps[ord[0]], files[ord[0]].name); + if (ord[0] < ntemps) + { + ntemps--; + zaptemp (files[ord[0]].name); + } + free (buffer[ord[0]].buf); + for (i = ord[0]; i < nfiles; ++i) + { + fps[i] = fps[i + 1]; + files[i] = files[i + 1]; + buffer[i] = buffer[i + 1]; + cur[i] = cur[i + 1]; + base[i] = base[i + 1]; + } + for (i = 0; i < nfiles; ++i) + ord[i] = ord[i + 1]; + continue; + } + } + + /* The new line just read in may be larger than other lines + already in main memory; push it back in the queue until we + encounter a line larger than it. Optimize for the common + case where the new line is smallest. */ + { + size_t lo = 1; + size_t hi = nfiles; + size_t probe = lo; + size_t ord0 = ord[0]; + size_t count_of_smaller_lines; + + while (lo < hi) + { + int cmp = compare (cur[ord0], cur[ord[probe]]); + if (cmp < 0 || (cmp == 0 && ord0 < ord[probe])) + hi = probe; + else + lo = probe + 1; + probe = (lo + hi) / 2; + } + + count_of_smaller_lines = lo - 1; + for (j = 0; j < count_of_smaller_lines; j++) + ord[j] = ord[j + 1]; + ord[count_of_smaller_lines] = ord0; + } + + /* Free up some resources every once in a while. */ + if (MAX_PROCS_BEFORE_REAP < nprocs) + reap_some (); + } + + if (unique && savedline) + { + write_bytes (saved.text, saved.length, ofp, output_file); + free (saved.text); + } + + xfclose (ofp, output_file); +} + +/* Merge into T the two sorted arrays of lines LO (with NLO members) + and HI (with NHI members). T, LO, and HI point just past their + respective arrays, and the arrays are in reverse order. NLO and + NHI must be positive, and HI - NHI must equal T - (NLO + NHI). */ + +static inline void +mergelines (struct line *t, + struct line const *lo, size_t nlo, + struct line const *hi, size_t nhi) +{ + for (;;) + if (compare (lo - 1, hi - 1) <= 0) + { + *--t = *--lo; + if (! --nlo) + { + /* HI - NHI equalled T - (NLO + NHI) when this function + began. Therefore HI must equal T now, and there is no + need to copy from HI to T. */ + return; + } + } + else + { + *--t = *--hi; + if (! --nhi) + { + do + *--t = *--lo; + while (--nlo); + + return; + } + } +} + +/* Sort the array LINES with NLINES members, using TEMP for temporary space. + NLINES must be at least 2. + The input and output arrays are in reverse order, and LINES and + TEMP point just past the end of their respective arrays. + + Use a recursive divide-and-conquer algorithm, in the style + suggested by Knuth volume 3 (2nd edition), exercise 5.2.4-23. Use + the optimization suggested by exercise 5.2.4-10; this requires room + for only 1.5*N lines, rather than the usual 2*N lines. Knuth + writes that this memory optimization was originally published by + D. A. Bell, Comp J. 1 (1958), 75. */ + +static void +sortlines (struct line *lines, size_t nlines, struct line *temp) +{ + if (nlines == 2) + { + if (0 < compare (&lines[-1], &lines[-2])) + { + struct line tmp = lines[-1]; + lines[-1] = lines[-2]; + lines[-2] = tmp; + } + } + else + { + size_t nlo = nlines / 2; + size_t nhi = nlines - nlo; + struct line *lo = lines; + struct line *hi = lines - nlo; + struct line *sorted_lo = temp; + + sortlines (hi, nhi, temp); + if (1 < nlo) + sortlines_temp (lo, nlo, sorted_lo); + else + sorted_lo[-1] = lo[-1]; + + mergelines (lines, sorted_lo, nlo, hi, nhi); + } +} + +/* Like sortlines (LINES, NLINES, TEMP), except output into TEMP + rather than sorting in place. */ + +static void +sortlines_temp (struct line *lines, size_t nlines, struct line *temp) +{ + if (nlines == 2) + { + /* Declare `swap' as int, not bool, to work around a bug + + in the IBM xlc 6.0.0.0 compiler in 64-bit mode. */ + int swap = (0 < compare (&lines[-1], &lines[-2])); + temp[-1] = lines[-1 - swap]; + temp[-2] = lines[-2 + swap]; + } + else + { + size_t nlo = nlines / 2; + size_t nhi = nlines - nlo; + struct line *lo = lines; + struct line *hi = lines - nlo; + struct line *sorted_hi = temp - nlo; + + sortlines_temp (hi, nhi, sorted_hi); + if (1 < nlo) + sortlines (lo, nlo, temp); + + mergelines (temp, lo, nlo, sorted_hi, nhi); + } +} + +/* Scan through FILES[NTEMPS .. NFILES-1] looking for a file that is + the same as OUTFILE. If found, merge the found instances (and perhaps + some other files) into a temporary file so that it can in turn be + merged into OUTFILE without destroying OUTFILE before it is completely + read. Return the new value of NFILES, which differs from the old if + some merging occurred. + + This test ensures that an otherwise-erroneous use like + "sort -m -o FILE ... FILE ..." copies FILE before writing to it. + It's not clear that POSIX requires this nicety. + Detect common error cases, but don't try to catch obscure cases like + "cat ... FILE ... | sort -m -o FILE" + where traditional "sort" doesn't copy the input and where + people should know that they're getting into trouble anyway. + Catching these obscure cases would slow down performance in + common cases. */ + +static size_t +avoid_trashing_input (struct sortfile *files, size_t ntemps, + size_t nfiles, char const *outfile) +{ + size_t i; + bool got_outstat = false; + struct stat outstat; + + for (i = ntemps; i < nfiles; i++) + { + bool is_stdin = STREQ (files[i].name, "-"); + bool same; + struct stat instat; + + if (outfile && STREQ (outfile, files[i].name) && !is_stdin) + same = true; + else + { + if (! got_outstat) + { + if ((outfile + ? stat (outfile, &outstat) + : fstat (STDOUT_FILENO, &outstat)) + != 0) + break; + got_outstat = true; + } + + same = (((is_stdin + ? fstat (STDIN_FILENO, &instat) + : stat (files[i].name, &instat)) + == 0) + && SAME_INODE (instat, outstat)); + } + + if (same) + { + FILE *tftp; + pid_t pid; + char *temp = create_temp (&tftp, &pid); + mergefps (&files[i],0, nfiles - i, tftp, temp); + files[i].name = temp; + files[i].pid = pid; + return i + 1; + } + } + + return nfiles; +} + +/* Merge the input FILES. NTEMPS is the number of files at the + start of FILES that are temporary; it is zero at the top level. + NFILES is the total number of files. Put the output in + OUTPUT_FILE; a null OUTPUT_FILE stands for standard output. */ + +static void +merge (struct sortfile *files, size_t ntemps, size_t nfiles, + char const *output_file) +{ + while (NMERGE < nfiles) + { + /* Number of input files processed so far. */ + size_t in; + + /* Number of output files generated so far. */ + size_t out; + + /* nfiles % NMERGE; this counts input files that are left over + after all full-sized merges have been done. */ + size_t remainder; + + /* Number of easily-available slots at the next loop iteration. */ + size_t cheap_slots; + + /* Do as many NMERGE-size merges as possible. */ + for (out = in = 0; out < nfiles / NMERGE; out++, in += NMERGE) + { + FILE *tfp; + pid_t pid; + char *temp = create_temp (&tfp, &pid); + size_t nt = MIN (ntemps, NMERGE); + ntemps -= nt; + mergefps (&files[in], nt, NMERGE, tfp, temp); + files[out].name = temp; + files[out].pid = pid; + } + + remainder = nfiles - in; + cheap_slots = NMERGE - out % NMERGE; + + if (cheap_slots < remainder) + { + /* So many files remain that they can't all be put into the last + NMERGE-sized output window. Do one more merge. Merge as few + files as possible, to avoid needless I/O. */ + size_t nshortmerge = remainder - cheap_slots + 1; + FILE *tfp; + pid_t pid; + char *temp = create_temp (&tfp, &pid); + size_t nt = MIN (ntemps, nshortmerge); + ntemps -= nt; + mergefps (&files[in], nt, nshortmerge, tfp, temp); + files[out].name = temp; + files[out++].pid = pid; + in += nshortmerge; + } + + /* Put the remaining input files into the last NMERGE-sized output + window, so they will be merged in the next pass. */ + memmove(&files[out], &files[in], (nfiles - in) * sizeof *files); + ntemps += out; + nfiles -= in - out; + } + + nfiles = avoid_trashing_input (files, ntemps, nfiles, output_file); + mergefps (files, ntemps, nfiles, NULL, output_file); +} + +/* Sort NFILES FILES onto OUTPUT_FILE. */ + +static void +sort (char * const *files, size_t nfiles, char const *output_file) +{ + struct buffer buf; + size_t ntemps = 0; + bool output_file_created = false; + + buf.alloc = 0; + + while (nfiles) + { + char const *temp_output; + char const *file = *files; + FILE *fp = xfopen (file, "r"); + FILE *tfp; + size_t bytes_per_line = (2 * sizeof (struct line) + - sizeof (struct line) / 2); + + if (! buf.alloc) + initbuf (&buf, bytes_per_line, + sort_buffer_size (&fp, 1, files, nfiles, bytes_per_line)); + buf.eof = false; + files++; + nfiles--; + + while (fillbuf (&buf, fp, file)) + { + struct line *line; + struct line *linebase; + + if (buf.eof && nfiles + && (bytes_per_line + 1 + < (buf.alloc - buf.used - bytes_per_line * buf.nlines))) + { + /* End of file, but there is more input and buffer room. + Concatenate the next input file; this is faster in + the usual case. */ + buf.left = buf.used; + break; + } + + line = buffer_linelim (&buf); + linebase = line - buf.nlines; + if (1 < buf.nlines) + sortlines (line, buf.nlines, linebase); + if (buf.eof && !nfiles && !ntemps && !buf.left) + { + xfclose (fp, file); + tfp = xfopen (output_file, "w"); + temp_output = output_file; + output_file_created = true; + } + else + { + ++ntemps; + temp_output = create_temp (&tfp, NULL); + } + + do + { + line--; + write_bytes (line->text, line->length, tfp, temp_output); + if (unique) + while (linebase < line && compare (line, line - 1) == 0) + line--; + } + while (linebase < line); + + xfclose (tfp, temp_output); + + /* Free up some resources every once in a while. */ + if (MAX_PROCS_BEFORE_REAP < nprocs) + reap_some (); + + if (output_file_created) + goto finish; + } + xfclose (fp, file); + } + + finish: + free (buf.buf); + + if (! output_file_created) + { + size_t i; + struct tempnode *node = temphead; + struct sortfile *tempfiles = xnmalloc (ntemps, sizeof *tempfiles); + for (i = 0; node; i++) + { + tempfiles[i].name = node->name; + tempfiles[i].pid = node->pid; + node = node->next; + } + merge (tempfiles, ntemps, ntemps, output_file); + free (tempfiles); + } +} + +/* Insert a malloc'd copy of key KEY_ARG at the end of the key list. */ + +static void +insertkey (struct keyfield *key_arg) +{ + struct keyfield **p; + struct keyfield *key = xmemdup (key_arg, sizeof *key); + + for (p = &keylist; *p; p = &(*p)->next) + continue; + *p = key; + key->next = NULL; +} + +/* Report a bad field specification SPEC, with extra info MSGID. */ + +static void badfieldspec (char const *, char const *) + ATTRIBUTE_NORETURN; +static void +badfieldspec (char const *spec, char const *msgid) +{ + error (SORT_FAILURE, 0, _("%s: invalid field specification %s"), + _(msgid), quote (spec)); + abort (); +} + +/* Report incompatible options. */ + +static void incompatible_options (char const *) ATTRIBUTE_NORETURN; +static void +incompatible_options (char const *opts) +{ + error (SORT_FAILURE, 0, _("options `-%s' are incompatible"), opts); + abort (); +} + +/* Check compatibility of ordering options. */ + +static void +check_ordering_compatibility (void) +{ + struct keyfield const *key; + + for (key = keylist; key; key = key->next) + if ((1 < (key->random + key->numeric + key->general_numeric + key->month + + !!key->ignore)) + || (key->random && key->translate)) + { + char opts[7]; + char *p = opts; + if (key->ignore == nondictionary) + *p++ = 'd'; + if (key->translate) + *p++ = 'f'; + if (key->general_numeric) + *p++ = 'g'; + if (key->ignore == nonprinting) + *p++ = 'i'; + if (key->month) + *p++ = 'M'; + if (key->numeric) + *p++ = 'n'; + if (key->random) + *p++ = 'R'; + *p = '\0'; + incompatible_options (opts); + } +} + +/* Parse the leading integer in STRING and store the resulting value + (which must fit into size_t) into *VAL. Return the address of the + suffix after the integer. If the value is too large, silently + substitute SIZE_MAX. If MSGID is NULL, return NULL after + failure; otherwise, report MSGID and exit on failure. */ + +static char const * +parse_field_count (char const *string, size_t *val, char const *msgid) +{ + char *suffix; + uintmax_t n; + + switch (xstrtoumax (string, &suffix, 10, &n, "")) + { + case LONGINT_OK: + case LONGINT_INVALID_SUFFIX_CHAR: + *val = n; + if (*val == n) + break; + /* Fall through. */ + case LONGINT_OVERFLOW: + case LONGINT_OVERFLOW | LONGINT_INVALID_SUFFIX_CHAR: + *val = SIZE_MAX; + break; + + case LONGINT_INVALID: + if (msgid) + error (SORT_FAILURE, 0, _("%s: invalid count at start of %s"), + _(msgid), quote (string)); + return NULL; + } + + return suffix; +} + +/* Handle interrupts and hangups. */ + +static void +sighandler (int sig) +{ + if (! SA_NOCLDSTOP) + signal (sig, SIG_IGN); + + cleanup (); + + signal (sig, SIG_DFL); + raise (sig); +} + +/* Set the ordering options for KEY specified in S. + Return the address of the first character in S that + is not a valid ordering option. + BLANKTYPE is the kind of blanks that 'b' should skip. */ + +static char * +set_ordering (const char *s, struct keyfield *key, enum blanktype blanktype) +{ + while (*s) + { + switch (*s) + { + case 'b': + if (blanktype == bl_start || blanktype == bl_both) + key->skipsblanks = true; + if (blanktype == bl_end || blanktype == bl_both) + key->skipeblanks = true; + break; + case 'd': + key->ignore = nondictionary; + break; + case 'f': + key->translate = fold_toupper; + break; + case 'g': + key->general_numeric = true; + break; + case 'i': + /* Option order should not matter, so don't let -i override + -d. -d implies -i, but -i does not imply -d. */ + if (! key->ignore) + key->ignore = nonprinting; + break; + case 'M': + key->month = true; + break; + case 'n': + key->numeric = true; + break; + case 'R': + key->random = true; + break; + case 'r': + key->reverse = true; + break; + default: + return (char *) s; + } + ++s; + } + return (char *) s; +} + +static struct keyfield * +key_init (struct keyfield *key) +{ + memset (key, 0, sizeof *key); + key->eword = SIZE_MAX; + return key; +} + +int +main (int argc, char **argv) +{ + struct keyfield *key; + struct keyfield key_buf; + struct keyfield gkey; + char const *s; + int c = 0; + char checkonly = 0; + bool mergeonly = false; + char *random_source = NULL; + bool need_random = false; + size_t nfiles = 0; + bool posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL); + bool obsolete_usage = (posix2_version () < 200112); + char **files; + char const *outfile = NULL; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (SORT_FAILURE); + + hard_LC_COLLATE = hard_locale (LC_COLLATE); +#if HAVE_NL_LANGINFO + hard_LC_TIME = hard_locale (LC_TIME); +#endif + + /* Get locale's representation of the decimal point. */ + { + struct lconv const *locale = localeconv (); + + /* If the locale doesn't define a decimal point, or if the decimal + point is multibyte, use the C locale's decimal point. FIXME: + add support for multibyte decimal points. */ + decimal_point = to_uchar (locale->decimal_point[0]); + if (! decimal_point || locale->decimal_point[1]) + decimal_point = '.'; + + /* FIXME: add support for multibyte thousands separators. */ + thousands_sep = to_uchar (*locale->thousands_sep); + if (! thousands_sep || locale->thousands_sep[1]) + thousands_sep = -1; + } + + have_read_stdin = false; + inittables (); + + { + size_t i; + static int const sig[] = + { + /* The usual suspects. */ + SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, +#ifdef SIGPOLL + SIGPOLL, +#endif +#ifdef SIGPROF + SIGPROF, +#endif +#ifdef SIGVTALRM + SIGVTALRM, +#endif +#ifdef SIGXCPU + SIGXCPU, +#endif +#ifdef SIGXFSZ + SIGXFSZ, +#endif + }; + enum { nsigs = sizeof sig / sizeof sig[0] }; + +#if SA_NOCLDSTOP + struct sigaction act; + + sigemptyset (&caught_signals); + for (i = 0; i < nsigs; i++) + { + sigaction (sig[i], NULL, &act); + if (act.sa_handler != SIG_IGN) + sigaddset (&caught_signals, sig[i]); + } + + act.sa_handler = sighandler; + act.sa_mask = caught_signals; + act.sa_flags = 0; + + for (i = 0; i < nsigs; i++) + if (sigismember (&caught_signals, sig[i])) + sigaction (sig[i], &act, NULL); +#else + for (i = 0; i < nsigs; i++) + if (signal (sig[i], SIG_IGN) != SIG_IGN) + { + signal (sig[i], sighandler); + siginterrupt (sig[i], 1); + } +#endif + } + + /* The signal mask is known, so it is safe to invoke exit_cleanup. */ + atexit (exit_cleanup); + + gkey.sword = gkey.eword = SIZE_MAX; + gkey.ignore = NULL; + gkey.translate = NULL; + gkey.numeric = gkey.general_numeric = gkey.random = false; + gkey.month = gkey.reverse = false; + gkey.skipsblanks = gkey.skipeblanks = false; + + files = xnmalloc (argc, sizeof *files); + + for (;;) + { + /* Parse an operand as a file after "--" was seen; or if + pedantic and a file was seen, unless the POSIX version + predates 1003.1-2001 and -c was not seen and the operand is + "-o FILE" or "-oFILE". */ + + if (c == -1 + || (posixly_correct && nfiles != 0 + && ! (obsolete_usage + && ! checkonly + && optind != argc + && argv[optind][0] == '-' && argv[optind][1] == 'o' + && (argv[optind][2] || optind + 1 != argc))) + || ((c = getopt_long (argc, argv, short_options, + long_options, NULL)) + == -1)) + { + if (argc <= optind) + break; + files[nfiles++] = argv[optind++]; + } + else switch (c) + { + case 1: + key = NULL; + if (optarg[0] == '+') + { + bool minus_pos_usage = (optind != argc && argv[optind][0] == '-' + && ISDIGIT (argv[optind][1])); + obsolete_usage |= minus_pos_usage & ~posixly_correct; + if (obsolete_usage) + { + /* Treat +POS1 [-POS2] as a key if possible; but silently + treat an operand as a file if it is not a valid +POS1. */ + key = key_init (&key_buf); + s = parse_field_count (optarg + 1, &key->sword, NULL); + if (s && *s == '.') + s = parse_field_count (s + 1, &key->schar, NULL); + if (! (key->sword | key->schar)) + key->sword = SIZE_MAX; + if (! s || *set_ordering (s, key, bl_start)) + { + free (key); + key = NULL; + } + else + { + if (minus_pos_usage) + { + char const *optarg1 = argv[optind++]; + s = parse_field_count (optarg1 + 1, &key->eword, + N_("invalid number after `-'")); + if (*s == '.') + s = parse_field_count (s + 1, &key->echar, + N_("invalid number after `.'")); + if (*set_ordering (s, key, bl_end)) + badfieldspec (optarg1, + N_("stray character in field spec")); + } + insertkey (key); + } + } + } + if (! key) + files[nfiles++] = optarg; + break; + + case 'b': + case 'd': + case 'f': + case 'g': + case 'i': + case 'M': + case 'n': + case 'r': + case 'R': + { + char str[2]; + str[0] = c; + str[1] = '\0'; + set_ordering (str, &gkey, bl_both); + } + break; + + case CHECK_OPTION: + c = (optarg + ? XARGMATCH ("--check", optarg, check_args, check_types) + : 'c'); + /* Fall through. */ + case 'c': + case 'C': + if (checkonly && checkonly != c) + incompatible_options ("cC"); + checkonly = c; + break; + + case COMPRESS_PROGRAM_OPTION: + if (compress_program && strcmp (compress_program, optarg) != 0) + error (SORT_FAILURE, 0, _("multiple compress programs specified")); + compress_program = optarg; + break; + + case 'k': + key = key_init (&key_buf); + + /* Get POS1. */ + s = parse_field_count (optarg, &key->sword, + N_("invalid number at field start")); + if (! key->sword--) + { + /* Provoke with `sort -k0' */ + badfieldspec (optarg, N_("field number is zero")); + } + if (*s == '.') + { + s = parse_field_count (s + 1, &key->schar, + N_("invalid number after `.'")); + if (! key->schar--) + { + /* Provoke with `sort -k1.0' */ + badfieldspec (optarg, N_("character offset is zero")); + } + } + if (! (key->sword | key->schar)) + key->sword = SIZE_MAX; + s = set_ordering (s, key, bl_start); + if (*s != ',') + { + key->eword = SIZE_MAX; + key->echar = 0; + } + else + { + /* Get POS2. */ + s = parse_field_count (s + 1, &key->eword, + N_("invalid number after `,'")); + if (! key->eword--) + { + /* Provoke with `sort -k1,0' */ + badfieldspec (optarg, N_("field number is zero")); + } + if (*s == '.') + s = parse_field_count (s + 1, &key->echar, + N_("invalid number after `.'")); + else + { + /* `-k 2,3' is equivalent to `+1 -3'. */ + key->eword++; + } + s = set_ordering (s, key, bl_end); + } + if (*s) + badfieldspec (optarg, N_("stray character in field spec")); + insertkey (key); + break; + + case 'm': + mergeonly = true; + break; + + case 'o': + if (outfile && !STREQ (outfile, optarg)) + error (SORT_FAILURE, 0, _("multiple output files specified")); + outfile = optarg; + break; + + case RANDOM_SOURCE_OPTION: + if (random_source && !STREQ (random_source, optarg)) + error (SORT_FAILURE, 0, _("multiple random sources specified")); + random_source = optarg; + break; + + case 's': + stable = true; + break; + + case 'S': + specify_sort_size (optarg); + break; + + case 't': + { + char newtab = optarg[0]; + if (! newtab) + error (SORT_FAILURE, 0, _("empty tab")); + if (optarg[1]) + { + if (STREQ (optarg, "\\0")) + newtab = '\0'; + else + { + /* Provoke with `sort -txx'. Complain about + "multi-character tab" instead of "multibyte tab", so + that the diagnostic's wording does not need to be + changed once multibyte characters are supported. */ + error (SORT_FAILURE, 0, _("multi-character tab %s"), + quote (optarg)); + } + } + if (tab != TAB_DEFAULT && tab != newtab) + error (SORT_FAILURE, 0, _("incompatible tabs")); + tab = newtab; + } + break; + + case 'T': + add_temp_dir (optarg); + break; + + case 'u': + unique = true; + break; + + case 'y': + /* Accept and ignore e.g. -y0 for compatibility with Solaris 2.x + through Solaris 7. It is also accepted by many non-Solaris + "sort" implementations, e.g., AIX 5.2, HP-UX 11i v2, IRIX 6.5. + -y is marked as obsolete starting with Solaris 8 (1999), but is + still accepted as of Solaris 10 prerelease (2004). + + Solaris 2.5.1 "sort -y 100" reads the input file "100", but + emulate Solaris 8 and 9 "sort -y 100" which ignores the "100", + and which in general ignores the argument after "-y" if it + consists entirely of digits (it can even be empty). */ + if (optarg == argv[optind - 1]) + { + char const *p; + for (p = optarg; ISDIGIT (*p); p++) + continue; + optind -= (*p != '\0'); + } + break; + + case 'z': + eolchar = 0; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (SORT_FAILURE); + } + } + + /* Inheritance of global options to individual keys. */ + for (key = keylist; key; key = key->next) + { + if (! (key->ignore || key->translate + || (key->skipsblanks | key->reverse + | key->skipeblanks | key->month | key->numeric + | key->general_numeric + | key->random))) + { + key->ignore = gkey.ignore; + key->translate = gkey.translate; + key->skipsblanks = gkey.skipsblanks; + key->skipeblanks = gkey.skipeblanks; + key->month = gkey.month; + key->numeric = gkey.numeric; + key->general_numeric = gkey.general_numeric; + key->random = gkey.random; + key->reverse = gkey.reverse; + } + + need_random |= key->random; + } + + if (!keylist && (gkey.ignore || gkey.translate + || (gkey.skipsblanks | gkey.skipeblanks | gkey.month + | gkey.numeric | gkey.general_numeric + | gkey.random))) + { + insertkey (&gkey); + need_random |= gkey.random; + } + + check_ordering_compatibility (); + + reverse = gkey.reverse; + + if (need_random) + { + randread_source = randread_new (random_source, MD5_DIGEST_SIZE); + if (! randread_source) + die (_("open failed"), random_source); + } + + if (temp_dir_count == 0) + { + char const *tmp_dir = getenv ("TMPDIR"); + add_temp_dir (tmp_dir ? tmp_dir : DEFAULT_TMPDIR); + } + + if (nfiles == 0) + { + static char *minus = "-"; + nfiles = 1; + free (files); + files = − + } + + if (checkonly) + { + if (nfiles > 1) + error (SORT_FAILURE, 0, _("extra operand %s not allowed with -%c"), + quote (files[1]), checkonly); + + if (outfile) + { + static char opts[] = {0, 'o', 0}; + opts[0] = checkonly; + incompatible_options (opts); + } + + /* POSIX requires that sort return 1 IFF invoked with -c or -C and the + input is not properly sorted. */ + exit (check (files[0], checkonly) ? EXIT_SUCCESS : SORT_OUT_OF_ORDER); + } + + if (mergeonly) + { + struct sortfile *sortfiles = xcalloc (nfiles, sizeof *sortfiles); + size_t i; + + for (i = 0; i < nfiles; ++i) + sortfiles[i].name = files[i]; + + merge (sortfiles, 0, nfiles, outfile); + } + else + sort (files, nfiles, outfile); + + if (have_read_stdin && fclose (stdin) == EOF) + die (_("close failed"), "-"); + + exit (EXIT_SUCCESS); +} diff --git a/src/split.c b/src/split.c new file mode 100644 index 0000000..1f0f3d7 --- /dev/null +++ b/src/split.c @@ -0,0 +1,582 @@ +/* split.c -- split a file into pieces. + Copyright (C) 1988, 1991, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* By tege@sics.se, with rms. + + To do: + * Implement -t CHAR or -t REGEX to specify break characters other + than newline. */ + +#include + +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "fd-reopen.h" +#include "fcntl--.h" +#include "getpagesize.h" +#include "full-read.h" +#include "full-write.h" +#include "inttostr.h" +#include "quote.h" +#include "safe-read.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "split" + +#define AUTHORS "Torbjorn Granlund", "Richard M. Stallman" + +#define DEFAULT_SUFFIX_LENGTH 2 + +/* The name this program was run with. */ +char *program_name; + +/* Base name of output files. */ +static char const *outbase; + +/* Name of output files. */ +static char *outfile; + +/* Pointer to the end of the prefix in OUTFILE. + Suffixes are inserted here. */ +static char *outfile_mid; + +/* Length of OUTFILE's suffix. */ +static size_t suffix_length = DEFAULT_SUFFIX_LENGTH; + +/* Alphabet of characters to use in suffix. */ +static char const *suffix_alphabet = "abcdefghijklmnopqrstuvwxyz"; + +/* Name of input file. May be "-". */ +static char *infile; + +/* Descriptor on which output file is open. */ +static int output_desc; + +/* If true, print a diagnostic on standard error just before each + output file is opened. */ +static bool verbose; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + VERBOSE_OPTION = CHAR_MAX + 1 +}; + +static struct option const longopts[] = +{ + {"bytes", required_argument, NULL, 'b'}, + {"lines", required_argument, NULL, 'l'}, + {"line-bytes", required_argument, NULL, 'C'}, + {"suffix-length", required_argument, NULL, 'a'}, + {"numeric-suffixes", no_argument, NULL, 'd'}, + {"verbose", no_argument, NULL, VERBOSE_OPTION}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [INPUT [PREFIX]]\n\ +"), + program_name); + fputs (_("\ +Output fixed-size pieces of INPUT to PREFIXaa, PREFIXab, ...; default\n\ +size is 1000 lines, and default PREFIX is `x'. With no INPUT, or when INPUT\n\ +is -, read standard input.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fprintf (stdout, _("\ + -a, --suffix-length=N use suffixes of length N (default %d)\n\ + -b, --bytes=SIZE put SIZE bytes per output file\n\ + -C, --line-bytes=SIZE put at most SIZE bytes of lines per output file\n\ + -d, --numeric-suffixes use numeric suffixes instead of alphabetic\n\ + -l, --lines=NUMBER put NUMBER lines per output file\n\ +"), DEFAULT_SUFFIX_LENGTH); + fputs (_("\ + --verbose print a diagnostic to standard error just\n\ + before each output file is opened\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +SIZE may have a multiplier suffix: b for 512, k for 1K, m for 1 Meg.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Compute the next sequential output file name and store it into the + string `outfile'. */ + +static void +next_file_name (void) +{ + /* Index in suffix_alphabet of each character in the suffix. */ + static size_t *sufindex; + + if (! outfile) + { + /* Allocate and initialize the first file name. */ + + size_t outbase_length = strlen (outbase); + size_t outfile_length = outbase_length + suffix_length; + if (outfile_length + 1 < outbase_length) + xalloc_die (); + outfile = xmalloc (outfile_length + 1); + outfile_mid = outfile + outbase_length; + memcpy (outfile, outbase, outbase_length); + memset (outfile_mid, suffix_alphabet[0], suffix_length); + outfile[outfile_length] = 0; + sufindex = xcalloc (suffix_length, sizeof *sufindex); + +#if ! _POSIX_NO_TRUNC && HAVE_PATHCONF && defined _PC_NAME_MAX + /* POSIX requires that if the output file name is too long for + its directory, `split' must fail without creating any files. + This must be checked for explicitly on operating systems that + silently truncate file names. */ + { + char *dir = dir_name (outfile); + long name_max = pathconf (dir, _PC_NAME_MAX); + if (0 <= name_max && name_max < base_len (last_component (outfile))) + error (EXIT_FAILURE, ENAMETOOLONG, "%s", outfile); + free (dir); + } +#endif + } + else + { + /* Increment the suffix in place, if possible. */ + + size_t i = suffix_length; + while (i-- != 0) + { + sufindex[i]++; + outfile_mid[i] = suffix_alphabet[sufindex[i]]; + if (outfile_mid[i]) + return; + sufindex[i] = 0; + outfile_mid[i] = suffix_alphabet[sufindex[i]]; + } + error (EXIT_FAILURE, 0, _("Output file suffixes exhausted")); + } +} + +/* Write BYTES bytes at BP to an output file. + If NEW_FILE_FLAG is true, open the next output file. + Otherwise add to the same output file already in use. */ + +static void +cwrite (bool new_file_flag, const char *bp, size_t bytes) +{ + if (new_file_flag) + { + if (output_desc >= 0 && close (output_desc) < 0) + error (EXIT_FAILURE, errno, "%s", outfile); + + next_file_name (); + if (verbose) + fprintf (stderr, _("creating file %s\n"), quote (outfile)); + output_desc = open (outfile, + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP + | S_IROTH | S_IWOTH)); + if (output_desc < 0) + error (EXIT_FAILURE, errno, "%s", outfile); + } + if (full_write (output_desc, bp, bytes) != bytes) + error (EXIT_FAILURE, errno, "%s", outfile); +} + +/* Split into pieces of exactly N_BYTES bytes. + Use buffer BUF, whose size is BUFSIZE. */ + +static void +bytes_split (uintmax_t n_bytes, char *buf, size_t bufsize) +{ + size_t n_read; + bool new_file_flag = true; + size_t to_read; + uintmax_t to_write = n_bytes; + char *bp_out; + + do + { + n_read = full_read (STDIN_FILENO, buf, bufsize); + if (n_read == SAFE_READ_ERROR) + error (EXIT_FAILURE, errno, "%s", infile); + bp_out = buf; + to_read = n_read; + for (;;) + { + if (to_read < to_write) + { + if (to_read) /* do not write 0 bytes! */ + { + cwrite (new_file_flag, bp_out, to_read); + to_write -= to_read; + new_file_flag = false; + } + break; + } + else + { + size_t w = to_write; + cwrite (new_file_flag, bp_out, w); + bp_out += w; + to_read -= w; + new_file_flag = true; + to_write = n_bytes; + } + } + } + while (n_read == bufsize); +} + +/* Split into pieces of exactly N_LINES lines. + Use buffer BUF, whose size is BUFSIZE. */ + +static void +lines_split (uintmax_t n_lines, char *buf, size_t bufsize) +{ + size_t n_read; + char *bp, *bp_out, *eob; + bool new_file_flag = true; + uintmax_t n = 0; + + do + { + n_read = full_read (STDIN_FILENO, buf, bufsize); + if (n_read == SAFE_READ_ERROR) + error (EXIT_FAILURE, errno, "%s", infile); + bp = bp_out = buf; + eob = bp + n_read; + *eob = '\n'; + for (;;) + { + bp = memchr (bp, '\n', eob - bp + 1); + if (bp == eob) + { + if (eob != bp_out) /* do not write 0 bytes! */ + { + size_t len = eob - bp_out; + cwrite (new_file_flag, bp_out, len); + new_file_flag = false; + } + break; + } + + ++bp; + if (++n >= n_lines) + { + cwrite (new_file_flag, bp_out, bp - bp_out); + bp_out = bp; + new_file_flag = true; + n = 0; + } + } + } + while (n_read == bufsize); +} + +/* Split into pieces that are as large as possible while still not more + than N_BYTES bytes, and are split on line boundaries except + where lines longer than N_BYTES bytes occur. + FIXME: Allow N_BYTES to be any uintmax_t value, and don't require a + buffer of size N_BYTES, in case N_BYTES is very large. */ + +static void +line_bytes_split (size_t n_bytes) +{ + size_t n_read; + char *bp; + bool eof = false; + size_t n_buffered = 0; + char *buf = xmalloc (n_bytes); + + do + { + /* Fill up the full buffer size from the input file. */ + + n_read = full_read (STDIN_FILENO, buf + n_buffered, n_bytes - n_buffered); + if (n_read == SAFE_READ_ERROR) + error (EXIT_FAILURE, errno, "%s", infile); + + n_buffered += n_read; + if (n_buffered != n_bytes) + eof = true; + + /* Find where to end this chunk. */ + bp = buf + n_buffered; + if (n_buffered == n_bytes) + { + while (bp > buf && bp[-1] != '\n') + bp--; + } + + /* If chunk has no newlines, use all the chunk. */ + if (bp == buf) + bp = buf + n_buffered; + + /* Output the chars as one output file. */ + cwrite (true, buf, bp - buf); + + /* Discard the chars we just output; move rest of chunk + down to be the start of the next chunk. Source and + destination probably overlap. */ + n_buffered -= bp - buf; + if (n_buffered > 0) + memmove (buf, bp, n_buffered); + } + while (!eof); + free (buf); +} + +#define FAIL_ONLY_ONE_WAY() \ + do \ + { \ + error (0, 0, _("cannot split in more than one way")); \ + usage (EXIT_FAILURE); \ + } \ + while (0) + +int +main (int argc, char **argv) +{ + struct stat stat_buf; + enum + { + type_undef, type_bytes, type_byteslines, type_lines, type_digits + } split_type = type_undef; + size_t in_blk_size; /* optimal block size of input file device */ + char *buf; /* file i/o buffer */ + size_t page_size = getpagesize (); + uintmax_t n_units; + int c; + int digits_optind = 0; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + /* Parse command line options. */ + + infile = "-"; + outbase = "x"; + + while (1) + { + /* This is the argv-index of the option we will read next. */ + int this_optind = optind ? optind : 1; + + c = getopt_long (argc, argv, "0123456789C:a:b:dl:", longopts, NULL); + if (c == -1) + break; + + switch (c) + { + case 'a': + { + unsigned long tmp; + if (xstrtoul (optarg, NULL, 10, &tmp, "") != LONGINT_OK + || SIZE_MAX / sizeof (size_t) < tmp) + { + error (0, 0, _("%s: invalid suffix length"), optarg); + usage (EXIT_FAILURE); + } + suffix_length = tmp; + } + break; + + case 'b': + if (split_type != type_undef) + FAIL_ONLY_ONE_WAY (); + split_type = type_bytes; + if (xstrtoumax (optarg, NULL, 10, &n_units, "bkm") != LONGINT_OK + || n_units == 0) + { + error (0, 0, _("%s: invalid number of bytes"), optarg); + usage (EXIT_FAILURE); + } + break; + + case 'l': + if (split_type != type_undef) + FAIL_ONLY_ONE_WAY (); + split_type = type_lines; + if (xstrtoumax (optarg, NULL, 10, &n_units, "") != LONGINT_OK + || n_units == 0) + { + error (0, 0, _("%s: invalid number of lines"), optarg); + usage (EXIT_FAILURE); + } + break; + + case 'C': + if (split_type != type_undef) + FAIL_ONLY_ONE_WAY (); + split_type = type_byteslines; + if (xstrtoumax (optarg, NULL, 10, &n_units, "bkm") != LONGINT_OK + || n_units == 0 || SIZE_MAX < n_units) + { + error (0, 0, _("%s: invalid number of bytes"), optarg); + usage (EXIT_FAILURE); + } + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (split_type == type_undef) + { + split_type = type_digits; + n_units = 0; + } + if (split_type != type_undef && split_type != type_digits) + FAIL_ONLY_ONE_WAY (); + if (digits_optind != 0 && digits_optind != this_optind) + n_units = 0; /* More than one number given; ignore other. */ + digits_optind = this_optind; + if (!DECIMAL_DIGIT_ACCUMULATE (n_units, c - '0', uintmax_t)) + { + char buffer[INT_BUFSIZE_BOUND (uintmax_t)]; + error (EXIT_FAILURE, 0, + _("line count option -%s%c... is too large"), + umaxtostr (n_units, buffer), c); + } + break; + + case 'd': + suffix_alphabet = "0123456789"; + break; + + case VERBOSE_OPTION: + verbose = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + /* Handle default case. */ + if (split_type == type_undef) + { + split_type = type_lines; + n_units = 1000; + } + + if (n_units == 0) + { + error (0, 0, _("invalid number of lines: 0")); + usage (EXIT_FAILURE); + } + + /* Get out the filename arguments. */ + + if (optind < argc) + infile = argv[optind++]; + + if (optind < argc) + outbase = argv[optind++]; + + if (optind < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind])); + usage (EXIT_FAILURE); + } + + /* Open the input file. */ + if (! STREQ (infile, "-") + && fd_reopen (STDIN_FILENO, infile, O_RDONLY, 0) < 0) + error (EXIT_FAILURE, errno, _("cannot open %s for reading"), + quote (infile)); + + /* Binary I/O is safer when bytecounts are used. */ + if (O_BINARY && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + + /* No output file is open now. */ + output_desc = -1; + + /* Get the optimal block size of input device and make a buffer. */ + + if (fstat (STDIN_FILENO, &stat_buf) != 0) + error (EXIT_FAILURE, errno, "%s", infile); + in_blk_size = ST_BLKSIZE (stat_buf); + + buf = ptr_align (xmalloc (in_blk_size + 1 + page_size - 1), page_size); + + switch (split_type) + { + case type_digits: + case type_lines: + lines_split (n_units, buf, in_blk_size); + break; + + case type_bytes: + bytes_split (n_units, buf, in_blk_size); + break; + + case type_byteslines: + line_bytes_split (n_units); + break; + + default: + abort (); + } + + if (close (STDIN_FILENO) != 0) + error (EXIT_FAILURE, errno, "%s", infile); + if (output_desc >= 0 && close (output_desc) < 0) + error (EXIT_FAILURE, errno, "%s", outfile); + + exit (EXIT_SUCCESS); +} diff --git a/src/stat.c b/src/stat.c new file mode 100644 index 0000000..ca84236 --- /dev/null +++ b/src/stat.c @@ -0,0 +1,979 @@ +/* stat.c -- display file or file system status + Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + Written by Michael Meskes. */ + +#include + +/* Keep this conditional in sync with the similar conditional in + ../m4/stat-prog.m4. */ +#if (STAT_STATVFS \ + && (HAVE_STRUCT_STATVFS_F_BASETYPE || HAVE_STRUCT_STATVFS_F_FSTYPENAME \ + || (! HAVE_STRUCT_STATFS_F_FSTYPENAME && HAVE_STRUCT_STATVFS_F_TYPE))) +# define USE_STATVFS 1 +#else +# define USE_STATVFS 0 +#endif + +#include +#include +#include +#include +#include +#if USE_STATVFS +# include +#elif HAVE_SYS_VFS_H +# include +#elif HAVE_SYS_MOUNT_H && HAVE_SYS_PARAM_H +/* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs. + It does have statvfs.h, but shouldn't use it, since it doesn't + HAVE_STRUCT_STATVFS_F_BASETYPE. So find a clean way to fix it. */ +/* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */ +# include +# include +# if HAVE_NETINET_IN_H && HAVE_NFS_NFS_CLNT_H && HAVE_NFS_VFS_H +/* Ultrix 4.4 needs these for the declaration of struct statfs. */ +# include +# include +# include +# endif +#elif HAVE_OS_H /* BeOS */ +# include +#endif + +#include "system.h" + +#include "error.h" +#include "filemode.h" +#include "file-type.h" +#include "fs.h" +#include "getopt.h" +#include "inttostr.h" +#include "quote.h" +#include "quotearg.h" +#include "stat-time.h" +#include "strftime.h" +#include "xreadlink.h" + +#define alignof(type) offsetof (struct { char c; type x; }, x) + +#if USE_STATVFS +# define STRUCT_STATVFS struct statvfs +# define STRUCT_STATXFS_F_FSID_IS_INTEGER STRUCT_STATVFS_F_FSID_IS_INTEGER +# define HAVE_STRUCT_STATXFS_F_TYPE HAVE_STRUCT_STATVFS_F_TYPE +# if HAVE_STRUCT_STATVFS_F_NAMEMAX +# define SB_F_NAMEMAX(S) ((S)->f_namemax) +# endif +# define STATFS statvfs +# define STATFS_FRSIZE(S) ((S)->f_frsize) +#else +# define HAVE_STRUCT_STATXFS_F_TYPE HAVE_STRUCT_STATFS_F_TYPE +# if HAVE_STRUCT_STATFS_F_NAMELEN +# define SB_F_NAMEMAX(S) ((S)->f_namelen) +# endif +# define STATFS statfs +# if HAVE_OS_H /* BeOS */ +/* BeOS has a statvfs function, but it does not return sensible values + for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and + f_fstypename. Use 'struct fs_info' instead. */ +static int +statfs (char const *filename, struct fs_info *buf) +{ + dev_t device = dev_for_path (filename); + if (device < 0) + { + errno = (device == B_ENTRY_NOT_FOUND ? ENOENT + : device == B_BAD_VALUE ? EINVAL + : device == B_NAME_TOO_LONG ? ENAMETOOLONG + : device == B_NO_MEMORY ? ENOMEM + : device == B_FILE_ERROR ? EIO + : 0); + return -1; + } + /* If successful, buf->dev will be == device. */ + return fs_stat_dev (device, buf); +} +# define f_fsid dev +# define f_blocks total_blocks +# define f_bfree free_blocks +# define f_bavail free_blocks +# define f_bsize io_size +# define f_files total_nodes +# define f_ffree free_nodes +# define STRUCT_STATVFS struct fs_info +# define STRUCT_STATXFS_F_FSID_IS_INTEGER true +# define STATFS_FRSIZE(S) ((S)->block_size) +# else +# define STRUCT_STATVFS struct statfs +# define STRUCT_STATXFS_F_FSID_IS_INTEGER STRUCT_STATFS_F_FSID_IS_INTEGER +# define STATFS_FRSIZE(S) 0 +# endif +#endif + +#ifdef SB_F_NAMEMAX +# define OUT_NAMEMAX out_uint +#else +/* NetBSD 1.5.2 has neither f_namemax nor f_namelen. */ +# define SB_F_NAMEMAX(S) "*" +# define OUT_NAMEMAX out_string +#endif + +#if HAVE_STRUCT_STATVFS_F_BASETYPE +# define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype +#else +# if HAVE_STRUCT_STATVFS_F_FSTYPENAME || HAVE_STRUCT_STATFS_F_FSTYPENAME +# define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename +# elif HAVE_OS_H /* BeOS */ +# define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name +# endif +#endif + +/* FIXME: these are used by printf.c, too */ +#define isodigit(c) ('0' <= (c) && (c) <= '7') +#define octtobin(c) ((c) - '0') +#define hextobin(c) ((c) >= 'a' && (c) <= 'f' ? (c) - 'a' + 10 : \ + (c) >= 'A' && (c) <= 'F' ? (c) - 'A' + 10 : (c) - '0') + +#define PROGRAM_NAME "stat" + +#define AUTHORS "Michael Meskes" + +enum +{ + PRINTF_OPTION = CHAR_MAX + 1 +}; + +static struct option const long_options[] = { + {"dereference", no_argument, NULL, 'L'}, + {"file-system", no_argument, NULL, 'f'}, + {"filesystem", no_argument, NULL, 'f'}, /* obsolete and undocumented alias */ + {"format", required_argument, NULL, 'c'}, + {"printf", required_argument, NULL, PRINTF_OPTION}, + {"terse", no_argument, NULL, 't'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +char *program_name; + +/* Whether to interpret backslash-escape sequences. + True for --printf=FMT, not for --format=FMT (-c). */ +static bool interpret_backslash_escapes; + +/* The trailing delimiter string: + "" for --printf=FMT, "\n" for --format=FMT (-c). */ +static char const *trailing_delim = ""; + +/* Return the type of the specified file system. + Some systems have statfvs.f_basetype[FSTYPSZ] (AIX, HP-UX, and Solaris). + Others have statvfs.f_fstypename[_VFS_NAMELEN] (NetBSD 3.0). + Others have statfs.f_fstypename[MFSNAMELEN] (NetBSD 1.5.2). + Still others have neither and have to get by with f_type (Linux). + But f_type may only exist in statfs (Cygwin). */ +static char const * +human_fstype (STRUCT_STATVFS const *statfsbuf) +{ +#ifdef STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME + return statfsbuf->STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME; +#else + switch (statfsbuf->f_type) + { +# if defined __linux__ + + /* IMPORTANT NOTE: Each of the following `case S_MAGIC_...:' + statements must be followed by a hexadecimal constant in + a comment. The S_MAGIC_... name and constant are automatically + combined to produce the #define directives in fs.h. */ + + case S_MAGIC_AFFS: /* 0xADFF */ + return "affs"; + case S_MAGIC_DEVPTS: /* 0x1CD1 */ + return "devpts"; + case S_MAGIC_EXT: /* 0x137D */ + return "ext"; + case S_MAGIC_EXT2_OLD: /* 0xEF51 */ + return "ext2"; + case S_MAGIC_EXT2: /* 0xEF53 */ + return "ext2/ext3"; + case S_MAGIC_JFS: /* 0x3153464a */ + return "jfs"; + case S_MAGIC_XFS: /* 0x58465342 */ + return "xfs"; + case S_MAGIC_HPFS: /* 0xF995E849 */ + return "hpfs"; + case S_MAGIC_ISOFS: /* 0x9660 */ + return "isofs"; + case S_MAGIC_ISOFS_WIN: /* 0x4000 */ + return "isofs"; + case S_MAGIC_ISOFS_R_WIN: /* 0x4004 */ + return "isofs"; + case S_MAGIC_MINIX: /* 0x137F */ + return "minix"; + case S_MAGIC_MINIX_30: /* 0x138F */ + return "minix (30 char.)"; + case S_MAGIC_MINIX_V2: /* 0x2468 */ + return "minix v2"; + case S_MAGIC_MINIX_V2_30: /* 0x2478 */ + return "minix v2 (30 char.)"; + case S_MAGIC_MSDOS: /* 0x4d44 */ + return "msdos"; + case S_MAGIC_FAT: /* 0x4006 */ + return "fat"; + case S_MAGIC_NCP: /* 0x564c */ + return "novell"; + case S_MAGIC_NFS: /* 0x6969 */ + return "nfs"; + case S_MAGIC_PROC: /* 0x9fa0 */ + return "proc"; + case S_MAGIC_SMB: /* 0x517B */ + return "smb"; + case S_MAGIC_XENIX: /* 0x012FF7B4 */ + return "xenix"; + case S_MAGIC_SYSV4: /* 0x012FF7B5 */ + return "sysv4"; + case S_MAGIC_SYSV2: /* 0x012FF7B6 */ + return "sysv2"; + case S_MAGIC_COH: /* 0x012FF7B7 */ + return "coh"; + case S_MAGIC_UFS: /* 0x00011954 */ + return "ufs"; + case S_MAGIC_XIAFS: /* 0x012FD16D */ + return "xia"; + case S_MAGIC_NTFS: /* 0x5346544e */ + return "ntfs"; + case S_MAGIC_TMPFS: /* 0x1021994 */ + return "tmpfs"; + case S_MAGIC_REISERFS: /* 0x52654973 */ + return "reiserfs"; + case S_MAGIC_CRAMFS: /* 0x28cd3d45 */ + return "cramfs"; + case S_MAGIC_ROMFS: /* 0x7275 */ + return "romfs"; + case S_MAGIC_RAMFS: /* 0x858458f6 */ + return "ramfs"; + case S_MAGIC_SQUASHFS: /* 0x73717368 */ + return "squashfs"; + case S_MAGIC_SYSFS: /* 0x62656572 */ + return "sysfs"; +# elif __GNU__ + case FSTYPE_UFS: + return "ufs"; + case FSTYPE_NFS: + return "nfs"; + case FSTYPE_GFS: + return "gfs"; + case FSTYPE_LFS: + return "lfs"; + case FSTYPE_SYSV: + return "sysv"; + case FSTYPE_FTP: + return "ftp"; + case FSTYPE_TAR: + return "tar"; + case FSTYPE_AR: + return "ar"; + case FSTYPE_CPIO: + return "cpio"; + case FSTYPE_MSLOSS: + return "msloss"; + case FSTYPE_CPM: + return "cpm"; + case FSTYPE_HFS: + return "hfs"; + case FSTYPE_DTFS: + return "dtfs"; + case FSTYPE_GRFS: + return "grfs"; + case FSTYPE_TERM: + return "term"; + case FSTYPE_DEV: + return "dev"; + case FSTYPE_PROC: + return "proc"; + case FSTYPE_IFSOCK: + return "ifsock"; + case FSTYPE_AFS: + return "afs"; + case FSTYPE_DFS: + return "dfs"; + case FSTYPE_PROC9: + return "proc9"; + case FSTYPE_SOCKET: + return "socket"; + case FSTYPE_MISC: + return "misc"; + case FSTYPE_EXT2FS: + return "ext2/ext3"; + case FSTYPE_HTTP: + return "http"; + case FSTYPE_MEMFS: + return "memfs"; + case FSTYPE_ISO9660: + return "iso9660"; +# endif + default: + { + unsigned long int type = statfsbuf->f_type; + static char buf[sizeof "UNKNOWN (0x%lx)" - 3 + + (sizeof type * CHAR_BIT + 3) / 4]; + sprintf (buf, "UNKNOWN (0x%lx)", type); + return buf; + } + } +#endif +} + +static char * +human_access (struct stat const *statbuf) +{ + static char modebuf[12]; + filemodestring (statbuf, modebuf); + modebuf[10] = 0; + return modebuf; +} + +static char * +human_time (struct timespec t) +{ + static char str[MAX (INT_BUFSIZE_BOUND (intmax_t), + (INT_STRLEN_BOUND (int) /* YYYY */ + + 1 /* because YYYY might equal INT_MAX + 1900 */ + + sizeof "-MM-DD HH:MM:SS.NNNNNNNNN +ZZZZ"))]; + struct tm const *tm = localtime (&t.tv_sec); + if (tm == NULL) + return (TYPE_SIGNED (time_t) + ? imaxtostr (t.tv_sec, str) + : umaxtostr (t.tv_sec, str)); + nstrftime (str, sizeof str, "%Y-%m-%d %H:%M:%S.%N %z", tm, 0, t.tv_nsec); + return str; +} + +static void +out_string (char *pformat, size_t prefix_len, char const *arg) +{ + strcpy (pformat + prefix_len, "s"); + printf (pformat, arg); +} +static void +out_int (char *pformat, size_t prefix_len, intmax_t arg) +{ + strcpy (pformat + prefix_len, PRIdMAX); + printf (pformat, arg); +} +static void +out_uint (char *pformat, size_t prefix_len, uintmax_t arg) +{ + strcpy (pformat + prefix_len, PRIuMAX); + printf (pformat, arg); +} +static void +out_uint_o (char *pformat, size_t prefix_len, uintmax_t arg) +{ + strcpy (pformat + prefix_len, PRIoMAX); + printf (pformat, arg); +} +static void +out_uint_x (char *pformat, size_t prefix_len, uintmax_t arg) +{ + strcpy (pformat + prefix_len, PRIxMAX); + printf (pformat, arg); +} + +/* print statfs info */ +static void +print_statfs (char *pformat, size_t prefix_len, char m, char const *filename, + void const *data) +{ + STRUCT_STATVFS const *statfsbuf = data; + + switch (m) + { + case 'n': + out_string (pformat, prefix_len, filename); + break; + + case 'i': + { +#if STRUCT_STATXFS_F_FSID_IS_INTEGER + uintmax_t fsid = statfsbuf->f_fsid; +#else + typedef unsigned int fsid_word; + verify (alignof (STRUCT_STATVFS) % alignof (fsid_word) == 0); + verify (offsetof (STRUCT_STATVFS, f_fsid) % alignof (fsid_word) == 0); + verify (sizeof statfsbuf->f_fsid % alignof (fsid_word) == 0); + fsid_word const *p = (fsid_word *) &statfsbuf->f_fsid; + + /* Assume a little-endian word order, as that is compatible + with glibc's statvfs implementation. */ + uintmax_t fsid = 0; + int words = sizeof statfsbuf->f_fsid / sizeof *p; + int i; + for (i = 0; i < words && i * sizeof *p < sizeof fsid; i++) + { + uintmax_t u = p[words - 1 - i]; + fsid |= u << (i * CHAR_BIT * sizeof *p); + } +#endif + out_uint_x (pformat, prefix_len, fsid); + } + break; + + case 'l': + OUT_NAMEMAX (pformat, prefix_len, SB_F_NAMEMAX (statfsbuf)); + break; + case 't': +#if HAVE_STRUCT_STATXFS_F_TYPE + out_uint_x (pformat, prefix_len, statfsbuf->f_type); +#else + fputc ('?', stdout); +#endif + break; + case 'T': + out_string (pformat, prefix_len, human_fstype (statfsbuf)); + break; + case 'b': + out_int (pformat, prefix_len, statfsbuf->f_blocks); + break; + case 'f': + out_int (pformat, prefix_len, statfsbuf->f_bfree); + break; + case 'a': + out_int (pformat, prefix_len, statfsbuf->f_bavail); + break; + case 's': + out_uint (pformat, prefix_len, statfsbuf->f_bsize); + break; + case 'S': + { + uintmax_t frsize = STATFS_FRSIZE (statfsbuf); + if (! frsize) + frsize = statfsbuf->f_bsize; + out_uint (pformat, prefix_len, frsize); + } + break; + case 'c': + out_int (pformat, prefix_len, statfsbuf->f_files); + break; + case 'd': + out_int (pformat, prefix_len, statfsbuf->f_ffree); + break; + + default: + fputc ('?', stdout); + break; + } +} + +/* print stat info */ +static void +print_stat (char *pformat, size_t prefix_len, char m, + char const *filename, void const *data) +{ + struct stat *statbuf = (struct stat *) data; + struct passwd *pw_ent; + struct group *gw_ent; + + switch (m) + { + case 'n': + out_string (pformat, prefix_len, filename); + break; + case 'N': + out_string (pformat, prefix_len, quote (filename)); + if (S_ISLNK (statbuf->st_mode)) + { + char *linkname = xreadlink_with_size (filename, statbuf->st_size); + if (linkname == NULL) + { + error (0, errno, _("cannot read symbolic link %s"), + quote (filename)); + return; + } + printf (" -> "); + out_string (pformat, prefix_len, quote (linkname)); + } + break; + case 'd': + out_uint (pformat, prefix_len, statbuf->st_dev); + break; + case 'D': + out_uint_x (pformat, prefix_len, statbuf->st_dev); + break; + case 'i': + out_uint (pformat, prefix_len, statbuf->st_ino); + break; + case 'a': + out_uint_o (pformat, prefix_len, statbuf->st_mode & CHMOD_MODE_BITS); + break; + case 'A': + out_string (pformat, prefix_len, human_access (statbuf)); + break; + case 'f': + out_uint_x (pformat, prefix_len, statbuf->st_mode); + break; + case 'F': + out_string (pformat, prefix_len, file_type (statbuf)); + break; + case 'h': + out_uint (pformat, prefix_len, statbuf->st_nlink); + break; + case 'u': + out_uint (pformat, prefix_len, statbuf->st_uid); + break; + case 'U': + setpwent (); + pw_ent = getpwuid (statbuf->st_uid); + out_string (pformat, prefix_len, + pw_ent ? pw_ent->pw_name : "UNKNOWN"); + break; + case 'g': + out_uint (pformat, prefix_len, statbuf->st_gid); + break; + case 'G': + setgrent (); + gw_ent = getgrgid (statbuf->st_gid); + out_string (pformat, prefix_len, + gw_ent ? gw_ent->gr_name : "UNKNOWN"); + break; + case 't': + out_uint_x (pformat, prefix_len, major (statbuf->st_rdev)); + break; + case 'T': + out_uint_x (pformat, prefix_len, minor (statbuf->st_rdev)); + break; + case 's': + out_uint (pformat, prefix_len, statbuf->st_size); + break; + case 'B': + out_uint (pformat, prefix_len, ST_NBLOCKSIZE); + break; + case 'b': + out_uint (pformat, prefix_len, ST_NBLOCKS (*statbuf)); + break; + case 'o': + out_uint (pformat, prefix_len, statbuf->st_blksize); + break; + case 'x': + out_string (pformat, prefix_len, human_time (get_stat_atime (statbuf))); + break; + case 'X': + if (TYPE_SIGNED (time_t)) + out_int (pformat, prefix_len, statbuf->st_atime); + else + out_uint (pformat, prefix_len, statbuf->st_atime); + break; + case 'y': + out_string (pformat, prefix_len, human_time (get_stat_mtime (statbuf))); + break; + case 'Y': + if (TYPE_SIGNED (time_t)) + out_int (pformat, prefix_len, statbuf->st_mtime); + else + out_uint (pformat, prefix_len, statbuf->st_mtime); + break; + case 'z': + out_string (pformat, prefix_len, human_time (get_stat_ctime (statbuf))); + break; + case 'Z': + if (TYPE_SIGNED (time_t)) + out_int (pformat, prefix_len, statbuf->st_ctime); + else + out_uint (pformat, prefix_len, statbuf->st_ctime); + break; + default: + fputc ('?', stdout); + break; + } +} + +/* Output a single-character \ escape. */ + +static void +print_esc_char (char c) +{ + switch (c) + { + case 'a': /* Alert. */ + c ='\a'; + break; + case 'b': /* Backspace. */ + c ='\b'; + break; + case 'f': /* Form feed. */ + c ='\f'; + break; + case 'n': /* New line. */ + c ='\n'; + break; + case 'r': /* Carriage return. */ + c ='\r'; + break; + case 't': /* Horizontal tab. */ + c ='\t'; + break; + case 'v': /* Vertical tab. */ + c ='\v'; + break; + case '"': + case '\\': + break; + default: + error (0, 0, _("warning: unrecognized escape `\\%c'"), c); + break; + } + putchar (c); +} + +static void +print_it (char const *format, char const *filename, + void (*print_func) (char *, size_t, char, char const *, void const *), + void const *data) +{ + /* Add 2 to accommodate our conversion of the stat `%s' format string + to the longer printf `%llu' one. */ + enum + { + MAX_ADDITIONAL_BYTES = + (MAX (sizeof PRIdMAX, + MAX (sizeof PRIoMAX, MAX (sizeof PRIuMAX, sizeof PRIxMAX))) + - 1) + }; + size_t n_alloc = strlen (format) + MAX_ADDITIONAL_BYTES + 1; + char *dest = xmalloc (n_alloc); + char const *b; + for (b = format; *b; b++) + { + switch (*b) + { + case '%': + { + size_t len = strspn (b + 1, "#-+.I 0123456789"); + char const *fmt_char = b + len + 1; + memcpy (dest, b, len + 1); + + b = fmt_char; + switch (*fmt_char) + { + case '\0': + --b; + /* fall through */ + case '%': + if (0 < len) + { + dest[len + 1] = *fmt_char; + dest[len + 2] = '\0'; + error (EXIT_FAILURE, 0, _("%s: invalid directive"), + quotearg_colon (dest)); + } + putchar ('%'); + break; + default: + print_func (dest, len + 1, *fmt_char, filename, data); + break; + } + break; + } + + case '\\': + if ( ! interpret_backslash_escapes) + { + putchar ('\\'); + break; + } + ++b; + if (isodigit (*b)) + { + int esc_value = octtobin (*b); + int esc_length = 1; /* number of octal digits */ + for (++b; esc_length < 3 && isodigit (*b); + ++esc_length, ++b) + { + esc_value = esc_value * 8 + octtobin (*b); + } + putchar (esc_value); + --b; + } + else if (*b == 'x' && isxdigit (to_uchar (b[1]))) + { + int esc_value = hextobin (b[1]); /* Value of \xhh escape. */ + /* A hexadecimal \xhh escape sequence must have + 1 or 2 hex. digits. */ + ++b; + if (isxdigit (to_uchar (b[1]))) + { + ++b; + esc_value = esc_value * 16 + hextobin (*b); + } + putchar (esc_value); + } + else if (*b == '\0') + { + error (0, 0, _("warning: backslash at end of format")); + putchar ('\\'); + /* Arrange to exit the loop. */ + --b; + } + else + { + print_esc_char (*b); + } + break; + + default: + putchar (*b); + break; + } + } + free (dest); + + fputs (trailing_delim, stdout); +} + +/* Stat the file system and print what we find. */ +static bool +do_statfs (char const *filename, bool terse, char const *format) +{ + STRUCT_STATVFS statfsbuf; + + if (STATFS (filename, &statfsbuf) != 0) + { + error (0, errno, _("cannot read file system information for %s"), + quote (filename)); + return false; + } + + if (format == NULL) + { + format = (terse + ? "%n %i %l %t %s %S %b %f %a %c %d\n" + : " File: \"%n\"\n" + " ID: %-8i Namelen: %-7l Type: %T\n" + "Block size: %-10s Fundamental block size: %S\n" + "Blocks: Total: %-10b Free: %-10f Available: %a\n" + "Inodes: Total: %-10c Free: %d\n"); + } + + print_it (format, filename, print_statfs, &statfsbuf); + return true; +} + +/* stat the file and print what we find */ +static bool +do_stat (char const *filename, bool follow_links, bool terse, + char const *format) +{ + struct stat statbuf; + + if ((follow_links ? stat : lstat) (filename, &statbuf) != 0) + { + error (0, errno, _("cannot stat %s"), quote (filename)); + return false; + } + + if (format == NULL) + { + if (terse) + { + format = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\n"; + } + else + { + /* Temporary hack to match original output until conditional + implemented. */ + if (S_ISBLK (statbuf.st_mode) || S_ISCHR (statbuf.st_mode)) + { + format = + " File: %N\n" + " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n" + "Device: %Dh/%dd\tInode: %-10i Links: %-5h" + " Device type: %t,%T\n" + "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n" + "Access: %x\n" "Modify: %y\n" "Change: %z\n"; + } + else + { + format = + " File: %N\n" + " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n" + "Device: %Dh/%dd\tInode: %-10i Links: %h\n" + "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n" + "Access: %x\n" "Modify: %y\n" "Change: %z\n"; + } + } + } + print_it (format, filename, print_stat, &statbuf); + return true; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION] FILE...\n"), program_name); + fputs (_("\ +Display file or file system status.\n\ +\n\ + -L, --dereference follow links\n\ + -f, --file-system display file system status instead of file status\n\ +"), stdout); + fputs (_("\ + -c --format=FORMAT use the specified FORMAT instead of the default;\n\ + output a newline after each use of FORMAT\n\ + --printf=FORMAT like --format, but interpret backslash escapes,\n\ + and do not output a mandatory trailing newline.\n\ + If you want a newline, include \\n in FORMAT.\n\ + -t, --terse print the information in terse form\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + + fputs (_("\n\ +The valid format sequences for files (without --file-system):\n\ +\n\ + %a Access rights in octal\n\ + %A Access rights in human readable form\n\ + %b Number of blocks allocated (see %B)\n\ + %B The size in bytes of each block reported by %b\n\ +"), stdout); + fputs (_("\ + %d Device number in decimal\n\ + %D Device number in hex\n\ + %f Raw mode in hex\n\ + %F File type\n\ + %g Group ID of owner\n\ + %G Group name of owner\n\ +"), stdout); + fputs (_("\ + %h Number of hard links\n\ + %i Inode number\n\ + %n File name\n\ + %N Quoted file name with dereference if symbolic link\n\ + %o I/O block size\n\ + %s Total size, in bytes\n\ + %t Major device type in hex\n\ + %T Minor device type in hex\n\ +"), stdout); + fputs (_("\ + %u User ID of owner\n\ + %U User name of owner\n\ + %x Time of last access\n\ + %X Time of last access as seconds since Epoch\n\ + %y Time of last modification\n\ + %Y Time of last modification as seconds since Epoch\n\ + %z Time of last change\n\ + %Z Time of last change as seconds since Epoch\n\ +\n\ +"), stdout); + + fputs (_("\ +Valid format sequences for file systems:\n\ +\n\ + %a Free blocks available to non-superuser\n\ + %b Total data blocks in file system\n\ + %c Total file nodes in file system\n\ + %d Free file nodes in file system\n\ + %f Free blocks in file system\n\ +"), stdout); + fputs (_("\ + %i File System ID in hex\n\ + %l Maximum length of filenames\n\ + %n File name\n\ + %s Block size (for faster transfers)\n\ + %S Fundamental block size (for block counts)\n\ + %t Type in hex\n\ + %T Type in human readable form\n\ +"), stdout); + printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char *argv[]) +{ + int c; + int i; + bool follow_links = false; + bool fs = false; + bool terse = false; + char *format = NULL; + bool ok = true; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((c = getopt_long (argc, argv, "c:fLt", long_options, NULL)) != -1) + { + switch (c) + { + case PRINTF_OPTION: + format = optarg; + interpret_backslash_escapes = true; + trailing_delim = ""; + break; + + case 'c': + format = optarg; + interpret_backslash_escapes = false; + trailing_delim = "\n"; + break; + + case 'L': + follow_links = true; + break; + + case 'f': + fs = true; + break; + + case 't': + terse = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (argc == optind) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + for (i = optind; i < argc; i++) + ok &= (fs + ? do_statfs (argv[i], terse, format) + : do_stat (argv[i], follow_links, terse, format)); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/stty.c b/src/stty.c new file mode 100644 index 0000000..33a821d --- /dev/null +++ b/src/stty.c @@ -0,0 +1,1892 @@ +/* stty -- change and print terminal line settings + Copyright (C) 1990-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Usage: stty [-ag] [--all] [--save] [-F device] [--file=device] [setting...] + + Options: + -a, --all Write all current settings to stdout in human-readable form. + -g, --save Write all current settings to stdout in stty-readable form. + -F, --file Open and use the specified device instead of stdin + + If no args are given, write to stdout the baud rate and settings that + have been changed from their defaults. Mode reading and changes + are done on the specified device, or stdin if none was specified. + + David MacKenzie */ + +#include + +#ifdef TERMIOS_NEEDS_XOPEN_SOURCE +# define _XOPEN_SOURCE +#endif + +#include +#include + +#if HAVE_TERMIOS_H +# include +#endif +#if HAVE_STROPTS_H +# include +#endif +#ifdef HAVE_SYS_IOCTL_H +# include +#endif + +#ifdef WINSIZE_IN_PTEM +# include +# include +#endif +#ifdef GWINSZ_IN_SYS_PTY +# include +# include +#endif +#include +#include + +#include "system.h" +#include "error.h" +#include "fd-reopen.h" +#include "quote.h" +#include "vasprintf.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "stty" + +#define AUTHORS "David MacKenzie" + +#ifndef _POSIX_VDISABLE +# define _POSIX_VDISABLE 0 +#endif + +#define Control(c) ((c) & 0x1f) +/* Canonical values for control characters. */ +#ifndef CINTR +# define CINTR Control ('c') +#endif +#ifndef CQUIT +# define CQUIT 28 +#endif +#ifndef CERASE +# define CERASE 127 +#endif +#ifndef CKILL +# define CKILL Control ('u') +#endif +#ifndef CEOF +# define CEOF Control ('d') +#endif +#ifndef CEOL +# define CEOL _POSIX_VDISABLE +#endif +#ifndef CSTART +# define CSTART Control ('q') +#endif +#ifndef CSTOP +# define CSTOP Control ('s') +#endif +#ifndef CSUSP +# define CSUSP Control ('z') +#endif +#if defined VEOL2 && !defined CEOL2 +# define CEOL2 _POSIX_VDISABLE +#endif +/* Some platforms have VSWTC, others VSWTCH. In both cases, this control + character is initialized by CSWTCH, if present. */ +#if defined VSWTC && !defined VSWTCH +# define VSWTCH VSWTC +#endif +/* ISC renamed swtch to susp for termios, but we'll accept either name. */ +#if defined VSUSP && !defined VSWTCH +# define VSWTCH VSUSP +# if defined CSUSP && !defined CSWTCH +# define CSWTCH CSUSP +# endif +#endif +#if defined VSWTCH && !defined CSWTCH +# define CSWTCH _POSIX_VDISABLE +#endif + +/* SunOS 5.3 loses (^Z doesn't work) if `swtch' is the same as `susp'. + So the default is to disable `swtch.' */ +#if defined __sparc__ && defined __svr4__ +# undef CSWTCH +# define CSWTCH _POSIX_VDISABLE +#endif + +#if defined VWERSE && !defined VWERASE /* AIX-3.2.5 */ +# define VWERASE VWERSE +#endif +#if defined VDSUSP && !defined CDSUSP +# define CDSUSP Control ('y') +#endif +#if !defined VREPRINT && defined VRPRNT /* Irix 4.0.5 */ +# define VREPRINT VRPRNT +#endif +#if defined VREPRINT && !defined CRPRNT +# define CRPRNT Control ('r') +#endif +#if defined CREPRINT && !defined CRPRNT +# define CRPRNT Control ('r') +#endif +#if defined VWERASE && !defined CWERASE +# define CWERASE Control ('w') +#endif +#if defined VLNEXT && !defined CLNEXT +# define CLNEXT Control ('v') +#endif +#if defined VDISCARD && !defined VFLUSHO +# define VFLUSHO VDISCARD +#endif +#if defined VFLUSH && !defined VFLUSHO /* Ultrix 4.2 */ +# define VFLUSHO VFLUSH +#endif +#if defined CTLECH && !defined ECHOCTL /* Ultrix 4.3 */ +# define ECHOCTL CTLECH +#endif +#if defined TCTLECH && !defined ECHOCTL /* Ultrix 4.2 */ +# define ECHOCTL TCTLECH +#endif +#if defined CRTKIL && !defined ECHOKE /* Ultrix 4.2 and 4.3 */ +# define ECHOKE CRTKIL +#endif +#if defined VFLUSHO && !defined CFLUSHO +# define CFLUSHO Control ('o') +#endif +#if defined VSTATUS && !defined CSTATUS +# define CSTATUS Control ('t') +#endif + +/* Which speeds to set. */ +enum speed_setting + { + input_speed, output_speed, both_speeds + }; + +/* What to output and how. */ +enum output_type + { + changed, all, recoverable /* Default, -a, -g. */ + }; + +/* Which member(s) of `struct termios' a mode uses. */ +enum mode_type + { + control, input, output, local, combination + }; + +/* Flags for `struct mode_info'. */ +#define SANE_SET 1 /* Set in `sane' mode. */ +#define SANE_UNSET 2 /* Unset in `sane' mode. */ +#define REV 4 /* Can be turned off by prepending `-'. */ +#define OMIT 8 /* Don't display value. */ + +/* Each mode. */ +struct mode_info + { + const char *name; /* Name given on command line. */ + enum mode_type type; /* Which structure element to change. */ + char flags; /* Setting and display options. */ + unsigned long bits; /* Bits to set for this mode. */ + unsigned long mask; /* Other bits to turn off for this mode. */ + }; + +static struct mode_info mode_info[] = +{ + {"parenb", control, REV, PARENB, 0}, + {"parodd", control, REV, PARODD, 0}, + {"cs5", control, 0, CS5, CSIZE}, + {"cs6", control, 0, CS6, CSIZE}, + {"cs7", control, 0, CS7, CSIZE}, + {"cs8", control, 0, CS8, CSIZE}, + {"hupcl", control, REV, HUPCL, 0}, + {"hup", control, REV | OMIT, HUPCL, 0}, + {"cstopb", control, REV, CSTOPB, 0}, + {"cread", control, SANE_SET | REV, CREAD, 0}, + {"clocal", control, REV, CLOCAL, 0}, +#ifdef CRTSCTS + {"crtscts", control, REV, CRTSCTS, 0}, +#endif + + {"ignbrk", input, SANE_UNSET | REV, IGNBRK, 0}, + {"brkint", input, SANE_SET | REV, BRKINT, 0}, + {"ignpar", input, REV, IGNPAR, 0}, + {"parmrk", input, REV, PARMRK, 0}, + {"inpck", input, REV, INPCK, 0}, + {"istrip", input, REV, ISTRIP, 0}, + {"inlcr", input, SANE_UNSET | REV, INLCR, 0}, + {"igncr", input, SANE_UNSET | REV, IGNCR, 0}, + {"icrnl", input, SANE_SET | REV, ICRNL, 0}, + {"ixon", input, REV, IXON, 0}, + {"ixoff", input, SANE_UNSET | REV, IXOFF, 0}, + {"tandem", input, REV | OMIT, IXOFF, 0}, +#ifdef IUCLC + {"iuclc", input, SANE_UNSET | REV, IUCLC, 0}, +#endif +#ifdef IXANY + {"ixany", input, SANE_UNSET | REV, IXANY, 0}, +#endif +#ifdef IMAXBEL + {"imaxbel", input, SANE_SET | REV, IMAXBEL, 0}, +#endif +#ifdef IUTF8 + {"iutf8", input, SANE_UNSET | REV, IUTF8, 0}, +#endif + + {"opost", output, SANE_SET | REV, OPOST, 0}, +#ifdef OLCUC + {"olcuc", output, SANE_UNSET | REV, OLCUC, 0}, +#endif +#ifdef OCRNL + {"ocrnl", output, SANE_UNSET | REV, OCRNL, 0}, +#endif +#ifdef ONLCR + {"onlcr", output, SANE_SET | REV, ONLCR, 0}, +#endif +#ifdef ONOCR + {"onocr", output, SANE_UNSET | REV, ONOCR, 0}, +#endif +#ifdef ONLRET + {"onlret", output, SANE_UNSET | REV, ONLRET, 0}, +#endif +#ifdef OFILL + {"ofill", output, SANE_UNSET | REV, OFILL, 0}, +#endif +#ifdef OFDEL + {"ofdel", output, SANE_UNSET | REV, OFDEL, 0}, +#endif +#ifdef NLDLY + {"nl1", output, SANE_UNSET, NL1, NLDLY}, + {"nl0", output, SANE_SET, NL0, NLDLY}, +#endif +#ifdef CRDLY + {"cr3", output, SANE_UNSET, CR3, CRDLY}, + {"cr2", output, SANE_UNSET, CR2, CRDLY}, + {"cr1", output, SANE_UNSET, CR1, CRDLY}, + {"cr0", output, SANE_SET, CR0, CRDLY}, +#endif +#ifdef TABDLY + {"tab3", output, SANE_UNSET, TAB3, TABDLY}, + {"tab2", output, SANE_UNSET, TAB2, TABDLY}, + {"tab1", output, SANE_UNSET, TAB1, TABDLY}, + {"tab0", output, SANE_SET, TAB0, TABDLY}, +#else +# ifdef OXTABS + {"tab3", output, SANE_UNSET, OXTABS, 0}, +# endif +#endif +#ifdef BSDLY + {"bs1", output, SANE_UNSET, BS1, BSDLY}, + {"bs0", output, SANE_SET, BS0, BSDLY}, +#endif +#ifdef VTDLY + {"vt1", output, SANE_UNSET, VT1, VTDLY}, + {"vt0", output, SANE_SET, VT0, VTDLY}, +#endif +#ifdef FFDLY + {"ff1", output, SANE_UNSET, FF1, FFDLY}, + {"ff0", output, SANE_SET, FF0, FFDLY}, +#endif + + {"isig", local, SANE_SET | REV, ISIG, 0}, + {"icanon", local, SANE_SET | REV, ICANON, 0}, +#ifdef IEXTEN + {"iexten", local, SANE_SET | REV, IEXTEN, 0}, +#endif + {"echo", local, SANE_SET | REV, ECHO, 0}, + {"echoe", local, SANE_SET | REV, ECHOE, 0}, + {"crterase", local, REV | OMIT, ECHOE, 0}, + {"echok", local, SANE_SET | REV, ECHOK, 0}, + {"echonl", local, SANE_UNSET | REV, ECHONL, 0}, + {"noflsh", local, SANE_UNSET | REV, NOFLSH, 0}, +#ifdef XCASE + {"xcase", local, SANE_UNSET | REV, XCASE, 0}, +#endif +#ifdef TOSTOP + {"tostop", local, SANE_UNSET | REV, TOSTOP, 0}, +#endif +#ifdef ECHOPRT + {"echoprt", local, SANE_UNSET | REV, ECHOPRT, 0}, + {"prterase", local, REV | OMIT, ECHOPRT, 0}, +#endif +#ifdef ECHOCTL + {"echoctl", local, SANE_SET | REV, ECHOCTL, 0}, + {"ctlecho", local, REV | OMIT, ECHOCTL, 0}, +#endif +#ifdef ECHOKE + {"echoke", local, SANE_SET | REV, ECHOKE, 0}, + {"crtkill", local, REV | OMIT, ECHOKE, 0}, +#endif + + {"evenp", combination, REV | OMIT, 0, 0}, + {"parity", combination, REV | OMIT, 0, 0}, + {"oddp", combination, REV | OMIT, 0, 0}, + {"nl", combination, REV | OMIT, 0, 0}, + {"ek", combination, OMIT, 0, 0}, + {"sane", combination, OMIT, 0, 0}, + {"cooked", combination, REV | OMIT, 0, 0}, + {"raw", combination, REV | OMIT, 0, 0}, + {"pass8", combination, REV | OMIT, 0, 0}, + {"litout", combination, REV | OMIT, 0, 0}, + {"cbreak", combination, REV | OMIT, 0, 0}, +#ifdef IXANY + {"decctlq", combination, REV | OMIT, 0, 0}, +#endif +#if defined TABDLY || defined OXTABS + {"tabs", combination, REV | OMIT, 0, 0}, +#endif +#if defined XCASE && defined IUCLC && defined OLCUC + {"lcase", combination, REV | OMIT, 0, 0}, + {"LCASE", combination, REV | OMIT, 0, 0}, +#endif + {"crt", combination, OMIT, 0, 0}, + {"dec", combination, OMIT, 0, 0}, + + {NULL, control, 0, 0, 0} +}; + +/* Control character settings. */ +struct control_info + { + const char *name; /* Name given on command line. */ + cc_t saneval; /* Value to set for `stty sane'. */ + size_t offset; /* Offset in c_cc. */ + }; + +/* Control characters. */ + +static struct control_info control_info[] = +{ + {"intr", CINTR, VINTR}, + {"quit", CQUIT, VQUIT}, + {"erase", CERASE, VERASE}, + {"kill", CKILL, VKILL}, + {"eof", CEOF, VEOF}, + {"eol", CEOL, VEOL}, +#ifdef VEOL2 + {"eol2", CEOL2, VEOL2}, +#endif +#ifdef VSWTCH + {"swtch", CSWTCH, VSWTCH}, +#endif + {"start", CSTART, VSTART}, + {"stop", CSTOP, VSTOP}, + {"susp", CSUSP, VSUSP}, +#ifdef VDSUSP + {"dsusp", CDSUSP, VDSUSP}, +#endif +#ifdef VREPRINT + {"rprnt", CRPRNT, VREPRINT}, +#else +# ifdef CREPRINT /* HPUX 10.20 needs this */ + {"rprnt", CRPRNT, CREPRINT}, +# endif +#endif +#ifdef VWERASE + {"werase", CWERASE, VWERASE}, +#endif +#ifdef VLNEXT + {"lnext", CLNEXT, VLNEXT}, +#endif +#ifdef VFLUSHO + {"flush", CFLUSHO, VFLUSHO}, +#endif +#ifdef VSTATUS + {"status", CSTATUS, VSTATUS}, +#endif + + /* These must be last because of the display routines. */ + {"min", 1, VMIN}, + {"time", 0, VTIME}, + {NULL, 0, 0} +}; + +static char const *visible (cc_t ch); +static unsigned long int baud_to_value (speed_t speed); +static bool recover_mode (char const *arg, struct termios *mode); +static int screen_columns (void); +static bool set_mode (struct mode_info *info, bool reversed, + struct termios *mode); +static unsigned long int integer_arg (const char *s, unsigned long int max); +static speed_t string_to_baud (const char *arg); +static tcflag_t *mode_type_flag (enum mode_type type, struct termios *mode); +static void display_all (struct termios *mode, char const *device_name); +static void display_changed (struct termios *mode); +static void display_recoverable (struct termios *mode); +static void display_settings (enum output_type output_type, + struct termios *mode, + const char *device_name); +static void display_speed (struct termios *mode, bool fancy); +static void display_window_size (bool fancy, char const *device_name); +static void sane_mode (struct termios *mode); +static void set_control_char (struct control_info *info, + const char *arg, + struct termios *mode); +static void set_speed (enum speed_setting type, const char *arg, + struct termios *mode); +static void set_window_size (int rows, int cols, char const *device_name); + +/* The width of the screen, for output wrapping. */ +static int max_col; + +/* Current position, to know when to wrap. */ +static int current_col; + +static struct option longopts[] = +{ + {"all", no_argument, NULL, 'a'}, + {"save", no_argument, NULL, 'g'}, + {"file", required_argument, NULL, 'F'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* The name this program was run with. */ +char *program_name; + +static void wrapf (const char *message, ...) + __attribute__ ((__format__ (__printf__, 1, 2))); + +/* Print format string MESSAGE and optional args. + Wrap to next line first if it won't fit. + Print a space first unless MESSAGE will start a new line. */ + +static void +wrapf (const char *message,...) +{ + va_list args; + char *buf; + int buflen; + + va_start (args, message); + buflen = vasprintf (&buf, message, args); + va_end (args); + + if (buflen < 0) + xalloc_die (); + + if (0 < current_col) + { + if (max_col - current_col < buflen) + { + putchar ('\n'); + current_col = 0; + } + else + { + putchar (' '); + current_col++; + } + } + + fputs (buf, stdout); + free (buf); + current_col += buflen; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [-F DEVICE] [--file=DEVICE] [SETTING]...\n\ + or: %s [-F DEVICE] [--file=DEVICE] [-a|--all]\n\ + or: %s [-F DEVICE] [--file=DEVICE] [-g|--save]\n\ +"), + program_name, program_name, program_name); + fputs (_("\ +Print or change terminal characteristics.\n\ +\n\ + -a, --all print all current settings in human-readable form\n\ + -g, --save print all current settings in a stty-readable form\n\ + -F, --file=DEVICE open and use the specified DEVICE instead of stdin\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Optional - before SETTING indicates negation. An * marks non-POSIX\n\ +settings. The underlying system defines which settings are available.\n\ +"), stdout); + fputs (_("\ +\n\ +Special characters:\n\ + * dsusp CHAR CHAR will send a terminal stop signal once input flushed\n\ + eof CHAR CHAR will send an end of file (terminate the input)\n\ + eol CHAR CHAR will end the line\n\ +"), stdout); + fputs (_("\ + * eol2 CHAR alternate CHAR for ending the line\n\ + erase CHAR CHAR will erase the last character typed\n\ + intr CHAR CHAR will send an interrupt signal\n\ + kill CHAR CHAR will erase the current line\n\ +"), stdout); + fputs (_("\ + * lnext CHAR CHAR will enter the next character quoted\n\ + quit CHAR CHAR will send a quit signal\n\ + * rprnt CHAR CHAR will redraw the current line\n\ + start CHAR CHAR will restart the output after stopping it\n\ +"), stdout); + fputs (_("\ + stop CHAR CHAR will stop the output\n\ + susp CHAR CHAR will send a terminal stop signal\n\ + * swtch CHAR CHAR will switch to a different shell layer\n\ + * werase CHAR CHAR will erase the last word typed\n\ +"), stdout); + fputs (_("\ +\n\ +Special settings:\n\ + N set the input and output speeds to N bauds\n\ + * cols N tell the kernel that the terminal has N columns\n\ + * columns N same as cols N\n\ +"), stdout); + fputs (_("\ + ispeed N set the input speed to N\n\ + * line N use line discipline N\n\ + min N with -icanon, set N characters minimum for a completed read\n\ + ospeed N set the output speed to N\n\ +"), stdout); + fputs (_("\ + * rows N tell the kernel that the terminal has N rows\n\ + * size print the number of rows and columns according to the kernel\n\ + speed print the terminal speed\n\ + time N with -icanon, set read timeout of N tenths of a second\n\ +"), stdout); + fputs (_("\ +\n\ +Control settings:\n\ + [-]clocal disable modem control signals\n\ + [-]cread allow input to be received\n\ + * [-]crtscts enable RTS/CTS handshaking\n\ + csN set character size to N bits, N in [5..8]\n\ +"), stdout); + fputs (_("\ + [-]cstopb use two stop bits per character (one with `-')\n\ + [-]hup send a hangup signal when the last process closes the tty\n\ + [-]hupcl same as [-]hup\n\ + [-]parenb generate parity bit in output and expect parity bit in input\n\ + [-]parodd set odd parity (even with `-')\n\ +"), stdout); + fputs (_("\ +\n\ +Input settings:\n\ + [-]brkint breaks cause an interrupt signal\n\ + [-]icrnl translate carriage return to newline\n\ + [-]ignbrk ignore break characters\n\ + [-]igncr ignore carriage return\n\ +"), stdout); + fputs (_("\ + [-]ignpar ignore characters with parity errors\n\ + * [-]imaxbel beep and do not flush a full input buffer on a character\n\ + [-]inlcr translate newline to carriage return\n\ + [-]inpck enable input parity checking\n\ + [-]istrip clear high (8th) bit of input characters\n\ +"), stdout); + fputs (_("\ + * [-]iutf8 assume input characters are UTF-8 encoded\n\ +"), stdout); + fputs (_("\ + * [-]iuclc translate uppercase characters to lowercase\n\ + * [-]ixany let any character restart output, not only start character\n\ + [-]ixoff enable sending of start/stop characters\n\ + [-]ixon enable XON/XOFF flow control\n\ + [-]parmrk mark parity errors (with a 255-0-character sequence)\n\ + [-]tandem same as [-]ixoff\n\ +"), stdout); + fputs (_("\ +\n\ +Output settings:\n\ + * bsN backspace delay style, N in [0..1]\n\ + * crN carriage return delay style, N in [0..3]\n\ + * ffN form feed delay style, N in [0..1]\n\ + * nlN newline delay style, N in [0..1]\n\ +"), stdout); + fputs (_("\ + * [-]ocrnl translate carriage return to newline\n\ + * [-]ofdel use delete characters for fill instead of null characters\n\ + * [-]ofill use fill (padding) characters instead of timing for delays\n\ + * [-]olcuc translate lowercase characters to uppercase\n\ + * [-]onlcr translate newline to carriage return-newline\n\ + * [-]onlret newline performs a carriage return\n\ +"), stdout); + fputs (_("\ + * [-]onocr do not print carriage returns in the first column\n\ + [-]opost postprocess output\n\ + * tabN horizontal tab delay style, N in [0..3]\n\ + * tabs same as tab0\n\ + * -tabs same as tab3\n\ + * vtN vertical tab delay style, N in [0..1]\n\ +"), stdout); + fputs (_("\ +\n\ +Local settings:\n\ + [-]crterase echo erase characters as backspace-space-backspace\n\ + * crtkill kill all line by obeying the echoprt and echoe settings\n\ + * -crtkill kill all line by obeying the echoctl and echok settings\n\ +"), stdout); + fputs (_("\ + * [-]ctlecho echo control characters in hat notation (`^c')\n\ + [-]echo echo input characters\n\ + * [-]echoctl same as [-]ctlecho\n\ + [-]echoe same as [-]crterase\n\ + [-]echok echo a newline after a kill character\n\ +"), stdout); + fputs (_("\ + * [-]echoke same as [-]crtkill\n\ + [-]echonl echo newline even if not echoing other characters\n\ + * [-]echoprt echo erased characters backward, between `\\' and '/'\n\ + [-]icanon enable erase, kill, werase, and rprnt special characters\n\ + [-]iexten enable non-POSIX special characters\n\ +"), stdout); + fputs (_("\ + [-]isig enable interrupt, quit, and suspend special characters\n\ + [-]noflsh disable flushing after interrupt and quit special characters\n\ + * [-]prterase same as [-]echoprt\n\ + * [-]tostop stop background jobs that try to write to the terminal\n\ + * [-]xcase with icanon, escape with `\\' for uppercase characters\n\ +"), stdout); + fputs (_("\ +\n\ +Combination settings:\n\ + * [-]LCASE same as [-]lcase\n\ + cbreak same as -icanon\n\ + -cbreak same as icanon\n\ +"), stdout); + fputs (_("\ + cooked same as brkint ignpar istrip icrnl ixon opost isig\n\ + icanon, eof and eol characters to their default values\n\ + -cooked same as raw\n\ + crt same as echoe echoctl echoke\n\ +"), stdout); + fputs (_("\ + dec same as echoe echoctl echoke -ixany intr ^c erase 0177\n\ + kill ^u\n\ + * [-]decctlq same as [-]ixany\n\ + ek erase and kill characters to their default values\n\ + evenp same as parenb -parodd cs7\n\ +"), stdout); + fputs (_("\ + -evenp same as -parenb cs8\n\ + * [-]lcase same as xcase iuclc olcuc\n\ + litout same as -parenb -istrip -opost cs8\n\ + -litout same as parenb istrip opost cs7\n\ + nl same as -icrnl -onlcr\n\ + -nl same as icrnl -inlcr -igncr onlcr -ocrnl -onlret\n\ +"), stdout); + fputs (_("\ + oddp same as parenb parodd cs7\n\ + -oddp same as -parenb cs8\n\ + [-]parity same as [-]evenp\n\ + pass8 same as -parenb -istrip cs8\n\ + -pass8 same as parenb istrip cs7\n\ +"), stdout); + fputs (_("\ + raw same as -ignbrk -brkint -ignpar -parmrk -inpck -istrip\n\ + -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany\n\ + -imaxbel -opost -isig -icanon -xcase min 1 time 0\n\ + -raw same as cooked\n\ +"), stdout); + fputs (_("\ + sane same as cread -ignbrk brkint -inlcr -igncr icrnl -iutf8\n\ + -ixoff -iuclc -ixany imaxbel opost -olcuc -ocrnl onlcr\n\ + -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0\n\ + isig icanon iexten echo echoe echok -echonl -noflsh\n\ + -xcase -tostop -echoprt echoctl echoke, all special\n\ + characters to their default values.\n\ +"), stdout); + fputs (_("\ +\n\ +Handle the tty line connected to standard input. Without arguments,\n\ +prints baud rate, line discipline, and deviations from stty sane. In\n\ +settings, CHAR is taken literally, or coded as in ^c, 0x37, 0177 or\n\ +127; special values ^- or undef used to disable special characters.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + /* Initialize to all zeroes so there is no risk memcmp will report a + spurious difference in an uninitialized portion of the structure. */ + struct termios mode = { 0, }; + + enum output_type output_type; + int optc; + int argi = 0; + int opti = 1; + bool require_set_attr; + bool speed_was_set; + bool verbose_output; + bool recoverable_output; + int k; + bool noargs = true; + char *file_name = NULL; + const char *device_name; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + output_type = changed; + verbose_output = false; + recoverable_output = false; + + /* Don't print error messages for unrecognized options. */ + opterr = 0; + + /* If any new options are ever added to stty, the short options MUST + NOT allow any ambiguity with the stty settings. For example, the + stty setting "-gagFork" would not be feasible, since it will be + parsed as "-g -a -g -F ork". If you change anything about how + stty parses options, be sure it still works with combinations of + short and long options, --, POSIXLY_CORRECT, etc. */ + + while ((optc = getopt_long (argc - argi, argv + argi, "-agF:", + longopts, NULL)) + != -1) + { + switch (optc) + { + case 'a': + verbose_output = true; + output_type = all; + break; + + case 'g': + recoverable_output = true; + output_type = recoverable; + break; + + case 'F': + if (file_name) + error (EXIT_FAILURE, 0, _("only one device may be specified")); + file_name = optarg; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + noargs = false; + + /* Skip the argument containing this unrecognized option; + the 2nd pass will analyze it. */ + argi += opti; + + /* Restart getopt_long from the first unskipped argument. */ + opti = 1; + optind = 0; + + break; + } + + /* Clear fully-parsed arguments, so they don't confuse the 2nd pass. */ + while (opti < optind) + argv[argi + opti++] = NULL; + } + + /* Specifying both -a and -g gets an error. */ + if (verbose_output & recoverable_output) + error (EXIT_FAILURE, 0, + _("the options for verbose and stty-readable output styles are\n" + "mutually exclusive")); + + /* Specifying any other arguments with -a or -g gets an error. */ + if (!noargs & (verbose_output | recoverable_output)) + error (EXIT_FAILURE, 0, + _("when specifying an output style, modes may not be set")); + + /* FIXME: it'd be better not to open the file until we've verified + that all arguments are valid. Otherwise, we could end up doing + only some of the requested operations and then failing, probably + leaving things in an undesirable state. */ + + if (file_name) + { + int fdflags; + device_name = file_name; + if (fd_reopen (STDIN_FILENO, device_name, O_RDONLY | O_NONBLOCK, 0) < 0) + error (EXIT_FAILURE, errno, "%s", device_name); + if ((fdflags = fcntl (STDIN_FILENO, F_GETFL)) == -1 + || fcntl (STDIN_FILENO, F_SETFL, fdflags & ~O_NONBLOCK) < 0) + error (EXIT_FAILURE, errno, _("%s: couldn't reset non-blocking mode"), + device_name); + } + else + device_name = _("standard input"); + + if (tcgetattr (STDIN_FILENO, &mode)) + error (EXIT_FAILURE, errno, "%s", device_name); + + if (verbose_output | recoverable_output | noargs) + { + max_col = screen_columns (); + current_col = 0; + display_settings (output_type, &mode, device_name); + exit (EXIT_SUCCESS); + } + + speed_was_set = false; + require_set_attr = false; + for (k = 1; k < argc; k++) + { + char const *arg = argv[k]; + bool match_found = false; + bool reversed = false; + int i; + + if (! arg) + continue; + + if (arg[0] == '-') + { + ++arg; + reversed = true; + } + for (i = 0; mode_info[i].name != NULL; ++i) + { + if (STREQ (arg, mode_info[i].name)) + { + match_found = set_mode (&mode_info[i], reversed, &mode); + require_set_attr = true; + break; + } + } + if (!match_found & reversed) + { + error (0, 0, _("invalid argument %s"), quote (arg - 1)); + usage (EXIT_FAILURE); + } + if (!match_found) + { + for (i = 0; control_info[i].name != NULL; ++i) + { + if (STREQ (arg, control_info[i].name)) + { + if (k == argc - 1) + { + error (0, 0, _("missing argument to %s"), quote (arg)); + usage (EXIT_FAILURE); + } + match_found = true; + ++k; + set_control_char (&control_info[i], argv[k], &mode); + require_set_attr = true; + break; + } + } + } + if (!match_found) + { + if (STREQ (arg, "ispeed")) + { + if (k == argc - 1) + { + error (0, 0, _("missing argument to %s"), quote (arg)); + usage (EXIT_FAILURE); + } + ++k; + set_speed (input_speed, argv[k], &mode); + speed_was_set = true; + require_set_attr = true; + } + else if (STREQ (arg, "ospeed")) + { + if (k == argc - 1) + { + error (0, 0, _("missing argument to %s"), quote (arg)); + usage (EXIT_FAILURE); + } + ++k; + set_speed (output_speed, argv[k], &mode); + speed_was_set = true; + require_set_attr = true; + } +#ifdef TIOCGWINSZ + else if (STREQ (arg, "rows")) + { + if (k == argc - 1) + { + error (0, 0, _("missing argument to %s"), quote (arg)); + usage (EXIT_FAILURE); + } + ++k; + set_window_size (integer_arg (argv[k], INT_MAX), -1, + device_name); + } + else if (STREQ (arg, "cols") + || STREQ (arg, "columns")) + { + if (k == argc - 1) + { + error (0, 0, _("missing argument to %s"), quote (arg)); + usage (EXIT_FAILURE); + } + ++k; + set_window_size (-1, integer_arg (argv[k], INT_MAX), + device_name); + } + else if (STREQ (arg, "size")) + { + max_col = screen_columns (); + current_col = 0; + display_window_size (false, device_name); + } +#endif +#ifdef HAVE_C_LINE + else if (STREQ (arg, "line")) + { + unsigned long int value; + if (k == argc - 1) + { + error (0, 0, _("missing argument to %s"), quote (arg)); + usage (EXIT_FAILURE); + } + ++k; + mode.c_line = value = integer_arg (argv[k], ULONG_MAX); + if (mode.c_line != value) + error (0, 0, _("invalid line discipline %s"), quote (argv[k])); + require_set_attr = true; + } +#endif + else if (STREQ (arg, "speed")) + { + max_col = screen_columns (); + display_speed (&mode, false); + } + else if (string_to_baud (arg) != (speed_t) -1) + { + set_speed (both_speeds, arg, &mode); + speed_was_set = true; + require_set_attr = true; + } + else + { + if (! recover_mode (arg, &mode)) + { + error (0, 0, _("invalid argument %s"), quote (arg)); + usage (EXIT_FAILURE); + } + require_set_attr = true; + } + } + } + + if (require_set_attr) + { + /* Initialize to all zeroes so there is no risk memcmp will report a + spurious difference in an uninitialized portion of the structure. */ + struct termios new_mode = { 0, }; + + if (tcsetattr (STDIN_FILENO, TCSADRAIN, &mode)) + error (EXIT_FAILURE, errno, "%s", device_name); + + /* POSIX (according to Zlotnick's book) tcsetattr returns zero if + it performs *any* of the requested operations. This means it + can report `success' when it has actually failed to perform + some proper subset of the requested operations. To detect + this partial failure, get the current terminal attributes and + compare them to the requested ones. */ + + if (tcgetattr (STDIN_FILENO, &new_mode)) + error (EXIT_FAILURE, errno, "%s", device_name); + + /* Normally, one shouldn't use memcmp to compare structures that + may have `holes' containing uninitialized data, but we have been + careful to initialize the storage of these two variables to all + zeroes. One might think it more efficient simply to compare the + modified fields, but that would require enumerating those fields -- + and not all systems have the same fields in this structure. */ + + if (memcmp (&mode, &new_mode, sizeof (mode)) != 0) + { +#ifdef CIBAUD + /* SunOS 4.1.3 (at least) has the problem that after this sequence, + tcgetattr (&m1); tcsetattr (&m1); tcgetattr (&m2); + sometimes (m1 != m2). The only difference is in the four bits + of the c_cflag field corresponding to the baud rate. To save + Sun users a little confusion, don't report an error if this + happens. But suppress the error only if we haven't tried to + set the baud rate explicitly -- otherwise we'd never give an + error for a true failure to set the baud rate. */ + + new_mode.c_cflag &= (~CIBAUD); + if (speed_was_set || memcmp (&mode, &new_mode, sizeof (mode)) != 0) +#endif + { + error (EXIT_FAILURE, 0, + _("%s: unable to perform all requested operations"), + device_name); +#ifdef TESTING + { + size_t i; + printf (_("new_mode: mode\n")); + for (i = 0; i < sizeof (new_mode); i++) + printf ("0x%02x: 0x%02x\n", + *(((unsigned char *) &new_mode) + i), + *(((unsigned char *) &mode) + i)); + } +#endif + } + } + } + + exit (EXIT_SUCCESS); +} + +/* Return false if not applied because not reversible; otherwise + return true. */ + +static bool +set_mode (struct mode_info *info, bool reversed, struct termios *mode) +{ + tcflag_t *bitsp; + + if (reversed && (info->flags & REV) == 0) + return false; + + bitsp = mode_type_flag (info->type, mode); + + if (bitsp == NULL) + { + /* Combination mode. */ + if (STREQ (info->name, "evenp") || STREQ (info->name, "parity")) + { + if (reversed) + mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8; + else + mode->c_cflag = (mode->c_cflag & ~PARODD & ~CSIZE) | PARENB | CS7; + } + else if (STREQ (info->name, "oddp")) + { + if (reversed) + mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8; + else + mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARODD | PARENB; + } + else if (STREQ (info->name, "nl")) + { + if (reversed) + { + mode->c_iflag = (mode->c_iflag | ICRNL) & ~INLCR & ~IGNCR; + mode->c_oflag = (mode->c_oflag +#ifdef ONLCR + | ONLCR +#endif + ) +#ifdef OCRNL + & ~OCRNL +#endif +#ifdef ONLRET + & ~ONLRET +#endif + ; + } + else + { + mode->c_iflag = mode->c_iflag & ~ICRNL; +#ifdef ONLCR + mode->c_oflag = mode->c_oflag & ~ONLCR; +#endif + } + } + else if (STREQ (info->name, "ek")) + { + mode->c_cc[VERASE] = CERASE; + mode->c_cc[VKILL] = CKILL; + } + else if (STREQ (info->name, "sane")) + sane_mode (mode); + else if (STREQ (info->name, "cbreak")) + { + if (reversed) + mode->c_lflag |= ICANON; + else + mode->c_lflag &= ~ICANON; + } + else if (STREQ (info->name, "pass8")) + { + if (reversed) + { + mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB; + mode->c_iflag |= ISTRIP; + } + else + { + mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8; + mode->c_iflag &= ~ISTRIP; + } + } + else if (STREQ (info->name, "litout")) + { + if (reversed) + { + mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB; + mode->c_iflag |= ISTRIP; + mode->c_oflag |= OPOST; + } + else + { + mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8; + mode->c_iflag &= ~ISTRIP; + mode->c_oflag &= ~OPOST; + } + } + else if (STREQ (info->name, "raw") || STREQ (info->name, "cooked")) + { + if ((info->name[0] == 'r' && reversed) + || (info->name[0] == 'c' && !reversed)) + { + /* Cooked mode. */ + mode->c_iflag |= BRKINT | IGNPAR | ISTRIP | ICRNL | IXON; + mode->c_oflag |= OPOST; + mode->c_lflag |= ISIG | ICANON; +#if VMIN == VEOF + mode->c_cc[VEOF] = CEOF; +#endif +#if VTIME == VEOL + mode->c_cc[VEOL] = CEOL; +#endif + } + else + { + /* Raw mode. */ + mode->c_iflag = 0; + mode->c_oflag &= ~OPOST; + mode->c_lflag &= ~(ISIG | ICANON +#ifdef XCASE + | XCASE +#endif + ); + mode->c_cc[VMIN] = 1; + mode->c_cc[VTIME] = 0; + } + } +#ifdef IXANY + else if (STREQ (info->name, "decctlq")) + { + if (reversed) + mode->c_iflag |= IXANY; + else + mode->c_iflag &= ~IXANY; + } +#endif +#ifdef TABDLY + else if (STREQ (info->name, "tabs")) + { + if (reversed) + mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB3; + else + mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB0; + } +#else +# ifdef OXTABS + else if (STREQ (info->name, "tabs")) + { + if (reversed) + mode->c_oflag = mode->c_oflag | OXTABS; + else + mode->c_oflag = mode->c_oflag & ~OXTABS; + } +# endif +#endif +#if defined XCASE && defined IUCLC && defined OLCUC + else if (STREQ (info->name, "lcase") + || STREQ (info->name, "LCASE")) + { + if (reversed) + { + mode->c_lflag &= ~XCASE; + mode->c_iflag &= ~IUCLC; + mode->c_oflag &= ~OLCUC; + } + else + { + mode->c_lflag |= XCASE; + mode->c_iflag |= IUCLC; + mode->c_oflag |= OLCUC; + } + } +#endif + else if (STREQ (info->name, "crt")) + mode->c_lflag |= ECHOE +#ifdef ECHOCTL + | ECHOCTL +#endif +#ifdef ECHOKE + | ECHOKE +#endif + ; + else if (STREQ (info->name, "dec")) + { + mode->c_cc[VINTR] = 3; /* ^C */ + mode->c_cc[VERASE] = 127; /* DEL */ + mode->c_cc[VKILL] = 21; /* ^U */ + mode->c_lflag |= ECHOE +#ifdef ECHOCTL + | ECHOCTL +#endif +#ifdef ECHOKE + | ECHOKE +#endif + ; +#ifdef IXANY + mode->c_iflag &= ~IXANY; +#endif + } + } + else if (reversed) + *bitsp = *bitsp & ~info->mask & ~info->bits; + else + *bitsp = (*bitsp & ~info->mask) | info->bits; + + return true; +} + +static void +set_control_char (struct control_info *info, const char *arg, + struct termios *mode) +{ + unsigned long int value; + + if (STREQ (info->name, "min") || STREQ (info->name, "time")) + value = integer_arg (arg, TYPE_MAXIMUM (cc_t)); + else if (arg[0] == '\0' || arg[1] == '\0') + value = to_uchar (arg[0]); + else if (STREQ (arg, "^-") || STREQ (arg, "undef")) + value = _POSIX_VDISABLE; + else if (arg[0] == '^' && arg[1] != '\0') /* Ignore any trailing junk. */ + { + if (arg[1] == '?') + value = 127; + else + value = to_uchar (arg[1]) & ~0140; /* Non-letters get weird results. */ + } + else + value = integer_arg (arg, TYPE_MAXIMUM (cc_t)); + mode->c_cc[info->offset] = value; +} + +static void +set_speed (enum speed_setting type, const char *arg, struct termios *mode) +{ + speed_t baud; + + baud = string_to_baud (arg); + if (type == input_speed || type == both_speeds) + cfsetispeed (mode, baud); + if (type == output_speed || type == both_speeds) + cfsetospeed (mode, baud); +} + +#ifdef TIOCGWINSZ + +static int +get_win_size (int fd, struct winsize *win) +{ + int err = ioctl (fd, TIOCGWINSZ, (char *) win); + return err; +} + +static void +set_window_size (int rows, int cols, char const *device_name) +{ + struct winsize win; + + if (get_win_size (STDIN_FILENO, &win)) + { + if (errno != EINVAL) + error (EXIT_FAILURE, errno, "%s", device_name); + memset (&win, 0, sizeof (win)); + } + + if (rows >= 0) + win.ws_row = rows; + if (cols >= 0) + win.ws_col = cols; + +# ifdef TIOCSSIZE + /* Alexander Dupuy wrote: + The following code deals with a bug in the SunOS 4.x (and 3.x?) kernel. + This comment from sys/ttold.h describes Sun's twisted logic - a better + test would have been (ts_lines > 64k || ts_cols > 64k || ts_cols == 0). + At any rate, the problem is gone in Solaris 2.x. + + Unfortunately, the old TIOCSSIZE code does collide with TIOCSWINSZ, + but they can be disambiguated by checking whether a "struct ttysize" + structure's "ts_lines" field is greater than 64K or not. If so, + it's almost certainly a "struct winsize" instead. + + At any rate, the bug manifests itself when ws_row == 0; the symptom is + that ws_row is set to ws_col, and ws_col is set to (ws_xpixel<<16) + + ws_ypixel. Since GNU stty sets rows and columns separately, this bug + caused "stty rows 0 cols 0" to set rows to cols and cols to 0, while + "stty cols 0 rows 0" would do the right thing. On a little-endian + machine like the sun386i, the problem is the same, but for ws_col == 0. + + The workaround is to do the ioctl once with row and col = 1 to set the + pixel info, and then do it again using a TIOCSSIZE to set rows/cols. */ + + if (win.ws_row == 0 || win.ws_col == 0) + { + struct ttysize ttysz; + + ttysz.ts_lines = win.ws_row; + ttysz.ts_cols = win.ws_col; + + win.ws_row = 1; + win.ws_col = 1; + + if (ioctl (STDIN_FILENO, TIOCSWINSZ, (char *) &win)) + error (EXIT_FAILURE, errno, "%s", device_name); + + if (ioctl (STDIN_FILENO, TIOCSSIZE, (char *) &ttysz)) + error (EXIT_FAILURE, errno, "%s", device_name); + return; + } +# endif + + if (ioctl (STDIN_FILENO, TIOCSWINSZ, (char *) &win)) + error (EXIT_FAILURE, errno, "%s", device_name); +} + +static void +display_window_size (bool fancy, char const *device_name) +{ + struct winsize win; + + if (get_win_size (STDIN_FILENO, &win)) + { + if (errno != EINVAL) + error (EXIT_FAILURE, errno, "%s", device_name); + if (!fancy) + error (EXIT_FAILURE, 0, + _("%s: no size information for this device"), device_name); + } + else + { + wrapf (fancy ? "rows %d; columns %d;" : "%d %d\n", + win.ws_row, win.ws_col); + if (!fancy) + current_col = 0; + } +} +#endif + +static int +screen_columns (void) +{ +#ifdef TIOCGWINSZ + struct winsize win; + + /* With Solaris 2.[123], this ioctl fails and errno is set to + EINVAL for telnet (but not rlogin) sessions. + On ISC 3.0, it fails for the console and the serial port + (but it works for ptys). + It can also fail on any system when stdout isn't a tty. + In case of any failure, just use the default. */ + if (get_win_size (STDOUT_FILENO, &win) == 0 && 0 < win.ws_col) + return win.ws_col; +#endif + { + /* Use $COLUMNS if it's in [1..INT_MAX]. */ + char *col_string = getenv ("COLUMNS"); + long int n_columns; + if (!(col_string != NULL + && xstrtol (col_string, NULL, 0, &n_columns, "") == LONGINT_OK + && 0 < n_columns + && n_columns <= INT_MAX)) + n_columns = 80; + return n_columns; + } +} + +static tcflag_t * +mode_type_flag (enum mode_type type, struct termios *mode) +{ + switch (type) + { + case control: + return &mode->c_cflag; + + case input: + return &mode->c_iflag; + + case output: + return &mode->c_oflag; + + case local: + return &mode->c_lflag; + + case combination: + return NULL; + + default: + abort (); + } +} + +static void +display_settings (enum output_type output_type, struct termios *mode, + char const *device_name) +{ + switch (output_type) + { + case changed: + display_changed (mode); + break; + + case all: + display_all (mode, device_name); + break; + + case recoverable: + display_recoverable (mode); + break; + } +} + +static void +display_changed (struct termios *mode) +{ + int i; + bool empty_line; + tcflag_t *bitsp; + unsigned long mask; + enum mode_type prev_type = control; + + display_speed (mode, true); +#ifdef HAVE_C_LINE + wrapf ("line = %d;", mode->c_line); +#endif + putchar ('\n'); + current_col = 0; + + empty_line = true; + for (i = 0; !STREQ (control_info[i].name, "min"); ++i) + { + if (mode->c_cc[control_info[i].offset] == control_info[i].saneval) + continue; + /* If swtch is the same as susp, don't print both. */ +#if VSWTCH == VSUSP + if (STREQ (control_info[i].name, "swtch")) + continue; +#endif + /* If eof uses the same slot as min, only print whichever applies. */ +#if VEOF == VMIN + if ((mode->c_lflag & ICANON) == 0 + && (STREQ (control_info[i].name, "eof") + || STREQ (control_info[i].name, "eol"))) + continue; +#endif + + empty_line = false; + wrapf ("%s = %s;", control_info[i].name, + visible (mode->c_cc[control_info[i].offset])); + } + if ((mode->c_lflag & ICANON) == 0) + { + wrapf ("min = %lu; time = %lu;\n", + (unsigned long int) mode->c_cc[VMIN], + (unsigned long int) mode->c_cc[VTIME]); + } + else if (!empty_line) + putchar ('\n'); + current_col = 0; + + empty_line = true; + for (i = 0; mode_info[i].name != NULL; ++i) + { + if (mode_info[i].flags & OMIT) + continue; + if (mode_info[i].type != prev_type) + { + if (!empty_line) + { + putchar ('\n'); + current_col = 0; + empty_line = true; + } + prev_type = mode_info[i].type; + } + + bitsp = mode_type_flag (mode_info[i].type, mode); + mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits; + if ((*bitsp & mask) == mode_info[i].bits) + { + if (mode_info[i].flags & SANE_UNSET) + { + wrapf ("%s", mode_info[i].name); + empty_line = false; + } + } + else if ((mode_info[i].flags & (SANE_SET | REV)) == (SANE_SET | REV)) + { + wrapf ("-%s", mode_info[i].name); + empty_line = false; + } + } + if (!empty_line) + putchar ('\n'); + current_col = 0; +} + +static void +display_all (struct termios *mode, char const *device_name) +{ + int i; + tcflag_t *bitsp; + unsigned long mask; + enum mode_type prev_type = control; + + display_speed (mode, true); +#ifdef TIOCGWINSZ + display_window_size (true, device_name); +#endif +#ifdef HAVE_C_LINE + wrapf ("line = %d;", mode->c_line); +#endif + putchar ('\n'); + current_col = 0; + + for (i = 0; ! STREQ (control_info[i].name, "min"); ++i) + { + /* If swtch is the same as susp, don't print both. */ +#if VSWTCH == VSUSP + if (STREQ (control_info[i].name, "swtch")) + continue; +#endif + /* If eof uses the same slot as min, only print whichever applies. */ +#if VEOF == VMIN + if ((mode->c_lflag & ICANON) == 0 + && (STREQ (control_info[i].name, "eof") + || STREQ (control_info[i].name, "eol"))) + continue; +#endif + wrapf ("%s = %s;", control_info[i].name, + visible (mode->c_cc[control_info[i].offset])); + } +#if VEOF == VMIN + if ((mode->c_lflag & ICANON) == 0) +#endif + wrapf ("min = %lu; time = %lu;", + (unsigned long int) mode->c_cc[VMIN], + (unsigned long int) mode->c_cc[VTIME]); + if (current_col != 0) + putchar ('\n'); + current_col = 0; + + for (i = 0; mode_info[i].name != NULL; ++i) + { + if (mode_info[i].flags & OMIT) + continue; + if (mode_info[i].type != prev_type) + { + putchar ('\n'); + current_col = 0; + prev_type = mode_info[i].type; + } + + bitsp = mode_type_flag (mode_info[i].type, mode); + mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits; + if ((*bitsp & mask) == mode_info[i].bits) + wrapf ("%s", mode_info[i].name); + else if (mode_info[i].flags & REV) + wrapf ("-%s", mode_info[i].name); + } + putchar ('\n'); + current_col = 0; +} + +static void +display_speed (struct termios *mode, bool fancy) +{ + if (cfgetispeed (mode) == 0 || cfgetispeed (mode) == cfgetospeed (mode)) + wrapf (fancy ? "speed %lu baud;" : "%lu\n", + baud_to_value (cfgetospeed (mode))); + else + wrapf (fancy ? "ispeed %lu baud; ospeed %lu baud;" : "%lu %lu\n", + baud_to_value (cfgetispeed (mode)), + baud_to_value (cfgetospeed (mode))); + if (!fancy) + current_col = 0; +} + +static void +display_recoverable (struct termios *mode) +{ + size_t i; + + printf ("%lx:%lx:%lx:%lx", + (unsigned long int) mode->c_iflag, + (unsigned long int) mode->c_oflag, + (unsigned long int) mode->c_cflag, + (unsigned long int) mode->c_lflag); + for (i = 0; i < NCCS; ++i) + printf (":%lx", (unsigned long int) mode->c_cc[i]); + putchar ('\n'); +} + +static bool +recover_mode (char const *arg, struct termios *mode) +{ + size_t i; + int n; + unsigned long int chr; + unsigned long int iflag, oflag, cflag, lflag; + + /* Scan into temporaries since it is too much trouble to figure out + the right format for `tcflag_t'. */ + if (sscanf (arg, "%lx:%lx:%lx:%lx%n", + &iflag, &oflag, &cflag, &lflag, &n) != 4) + return false; + mode->c_iflag = iflag; + mode->c_oflag = oflag; + mode->c_cflag = cflag; + mode->c_lflag = lflag; + if (mode->c_iflag != iflag + || mode->c_oflag != oflag + || mode->c_cflag != cflag + || mode->c_lflag != lflag) + return false; + arg += n; + for (i = 0; i < NCCS; ++i) + { + if (sscanf (arg, ":%lx%n", &chr, &n) != 1) + return false; + mode->c_cc[i] = chr; + if (mode->c_cc[i] != chr) + return false; + arg += n; + } + + /* Fail if there are too many fields. */ + if (*arg != '\0') + return false; + + return true; +} + +struct speed_map +{ + const char *string; /* ASCII representation. */ + speed_t speed; /* Internal form. */ + unsigned long int value; /* Numeric value. */ +}; + +static struct speed_map speeds[] = +{ + {"0", B0, 0}, + {"50", B50, 50}, + {"75", B75, 75}, + {"110", B110, 110}, + {"134", B134, 134}, + {"134.5", B134, 134}, + {"150", B150, 150}, + {"200", B200, 200}, + {"300", B300, 300}, + {"600", B600, 600}, + {"1200", B1200, 1200}, + {"1800", B1800, 1800}, + {"2400", B2400, 2400}, + {"4800", B4800, 4800}, + {"9600", B9600, 9600}, + {"19200", B19200, 19200}, + {"38400", B38400, 38400}, + {"exta", B19200, 19200}, + {"extb", B38400, 38400}, +#ifdef B57600 + {"57600", B57600, 57600}, +#endif +#ifdef B115200 + {"115200", B115200, 115200}, +#endif +#ifdef B230400 + {"230400", B230400, 230400}, +#endif +#ifdef B460800 + {"460800", B460800, 460800}, +#endif +#ifdef B500000 + {"500000", B500000, 500000}, +#endif +#ifdef B576000 + {"576000", B576000, 576000}, +#endif +#ifdef B921600 + {"921600", B921600, 921600}, +#endif +#ifdef B1000000 + {"1000000", B1000000, 1000000}, +#endif +#ifdef B1152000 + {"1152000", B1152000, 1152000}, +#endif +#ifdef B1500000 + {"1500000", B1500000, 1500000}, +#endif +#ifdef B2000000 + {"2000000", B2000000, 2000000}, +#endif +#ifdef B2500000 + {"2500000", B2500000, 2500000}, +#endif +#ifdef B3000000 + {"3000000", B3000000, 3000000}, +#endif +#ifdef B3500000 + {"3500000", B3500000, 3500000}, +#endif +#ifdef B4000000 + {"4000000", B4000000, 4000000}, +#endif + {NULL, 0, 0} +}; + +static speed_t +string_to_baud (const char *arg) +{ + int i; + + for (i = 0; speeds[i].string != NULL; ++i) + if (STREQ (arg, speeds[i].string)) + return speeds[i].speed; + return (speed_t) -1; +} + +static unsigned long int +baud_to_value (speed_t speed) +{ + int i; + + for (i = 0; speeds[i].string != NULL; ++i) + if (speed == speeds[i].speed) + return speeds[i].value; + return 0; +} + +static void +sane_mode (struct termios *mode) +{ + int i; + tcflag_t *bitsp; + + for (i = 0; control_info[i].name; ++i) + { +#if VMIN == VEOF + if (STREQ (control_info[i].name, "min")) + break; +#endif + mode->c_cc[control_info[i].offset] = control_info[i].saneval; + } + + for (i = 0; mode_info[i].name != NULL; ++i) + { + if (mode_info[i].flags & SANE_SET) + { + bitsp = mode_type_flag (mode_info[i].type, mode); + *bitsp = (*bitsp & ~mode_info[i].mask) | mode_info[i].bits; + } + else if (mode_info[i].flags & SANE_UNSET) + { + bitsp = mode_type_flag (mode_info[i].type, mode); + *bitsp = *bitsp & ~mode_info[i].mask & ~mode_info[i].bits; + } + } +} + +/* Return a string that is the printable representation of character CH. */ +/* Adapted from `cat' by Torbjorn Granlund. */ + +static const char * +visible (cc_t ch) +{ + static char buf[10]; + char *bpout = buf; + + if (ch == _POSIX_VDISABLE) + return ""; + + if (ch >= 32) + { + if (ch < 127) + *bpout++ = ch; + else if (ch == 127) + { + *bpout++ = '^'; + *bpout++ = '?'; + } + else + { + *bpout++ = 'M', + *bpout++ = '-'; + if (ch >= 128 + 32) + { + if (ch < 128 + 127) + *bpout++ = ch - 128; + else + { + *bpout++ = '^'; + *bpout++ = '?'; + } + } + else + { + *bpout++ = '^'; + *bpout++ = ch - 128 + 64; + } + } + } + else + { + *bpout++ = '^'; + *bpout++ = ch + 64; + } + *bpout = '\0'; + return (const char *) buf; +} + +/* Parse string S as an integer, using decimal radix by default, + but allowing octal and hex numbers as in C. Reject values + larger than MAXVAL. */ + +static unsigned long int +integer_arg (const char *s, unsigned long int maxval) +{ + unsigned long int value; + if (xstrtoul (s, NULL, 0, &value, "bB") != LONGINT_OK + || maxval < value) + { + error (0, 0, _("invalid integer argument %s"), quote (s)); + usage (EXIT_FAILURE); + } + return value; +} diff --git a/src/su.c b/src/su.c new file mode 100644 index 0000000..70828b8 --- /dev/null +++ b/src/su.c @@ -0,0 +1,526 @@ +/* su for GNU. Run a shell with substitute user and group IDs. + Copyright (C) 1992-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Run a shell with the real and effective UID and GID and groups + of USER, default `root'. + + The shell run is taken from USER's password entry, /bin/sh if + none is specified there. If the account has a password, su + prompts for a password unless run by a user with real UID 0. + + Does not change the current directory. + Sets `HOME' and `SHELL' from the password entry for USER, and if + USER is not root, sets `USER' and `LOGNAME' to USER. + The subshell is not a login shell. + + If one or more ARGs are given, they are passed as additional + arguments to the subshell. + + Does not handle /bin/sh or other shells specially + (setting argv[0] to "-su", passing -c only to certain shells, etc.). + I don't see the point in doing that, and it's ugly. + + This program intentionally does not support a "wheel group" that + restricts who can su to UID 0 accounts. RMS considers that to + be fascist. + + Compile-time options: + -DSYSLOG_SUCCESS Log successful su's (by default, to root) with syslog. + -DSYSLOG_FAILURE Log failed su's (by default, to root) with syslog. + + -DSYSLOG_NON_ROOT Log all su's, not just those to root (UID 0). + Never logs attempted su's to nonexistent accounts. + + Written by David MacKenzie . */ + +#include +#include +#include +#include +#include +#include + +/* Hide any system prototype for getusershell. + This is necessary because some Cray systems have a conflicting + prototype (returning `int') in . */ +#define getusershell _getusershell_sys_proto_ + +#include "system.h" +#include "getpass.h" + +#undef getusershell + +#if HAVE_SYSLOG_H && HAVE_SYSLOG +# include +#else +# undef SYSLOG_SUCCESS +# undef SYSLOG_FAILURE +# undef SYSLOG_NON_ROOT +#endif + +#if HAVE_SYS_PARAM_H +# include +#endif + +#ifndef HAVE_ENDGRENT +# define endgrent() ((void) 0) +#endif + +#ifndef HAVE_ENDPWENT +# define endpwent() ((void) 0) +#endif + +#if HAVE_SHADOW_H +# include +#endif + +#include "error.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "su" + +#define AUTHORS "David MacKenzie" + +#if HAVE_PATHS_H +# include +#endif + +/* The default PATH for simulated logins to non-superuser accounts. */ +#ifdef _PATH_DEFPATH +# define DEFAULT_LOGIN_PATH _PATH_DEFPATH +#else +# define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin" +#endif + +/* The default PATH for simulated logins to superuser accounts. */ +#ifdef _PATH_DEFPATH_ROOT +# define DEFAULT_ROOT_LOGIN_PATH _PATH_DEFPATH_ROOT +#else +# define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc" +#endif + +/* The shell to run if none is given in the user's passwd entry. */ +#define DEFAULT_SHELL "/bin/sh" + +/* The user to become if none is specified. */ +#define DEFAULT_USER "root" + +char *crypt (); +char *getusershell (); +void endusershell (); +void setusershell (); + +extern char **environ; + +static void run_shell (char const *, char const *, char **, size_t) + ATTRIBUTE_NORETURN; + +/* The name this program was run with. */ +char *program_name; + +/* If true, pass the `-f' option to the subshell. */ +static bool fast_startup; + +/* If true, simulate a login instead of just starting a shell. */ +static bool simulate_login; + +/* If true, change some environment vars to indicate the user su'd to. */ +static bool change_environment; + +static struct option const longopts[] = +{ + {"command", required_argument, NULL, 'c'}, + {"fast", no_argument, NULL, 'f'}, + {"login", no_argument, NULL, 'l'}, + {"preserve-environment", no_argument, NULL, 'p'}, + {"shell", required_argument, NULL, 's'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Add NAME=VAL to the environment, checking for out of memory errors. */ + +static void +xsetenv (char const *name, char const *val) +{ + size_t namelen = strlen (name); + size_t vallen = strlen (val); + char *string = xmalloc (namelen + 1 + vallen + 1); + strcpy (string, name); + string[namelen] = '='; + strcpy (string + namelen + 1, val); + if (putenv (string) != 0) + xalloc_die (); +} + +#if defined SYSLOG_SUCCESS || defined SYSLOG_FAILURE +/* Log the fact that someone has run su to the user given by PW; + if SUCCESSFUL is true, they gave the correct password, etc. */ + +static void +log_su (struct passwd const *pw, bool successful) +{ + const char *new_user, *old_user, *tty; + +# ifndef SYSLOG_NON_ROOT + if (pw->pw_uid) + return; +# endif + new_user = pw->pw_name; + /* The utmp entry (via getlogin) is probably the best way to identify + the user, especially if someone su's from a su-shell. */ + old_user = getlogin (); + if (!old_user) + { + /* getlogin can fail -- usually due to lack of utmp entry. + Resort to getpwuid. */ + struct passwd *pwd = getpwuid (getuid ()); + old_user = (pwd ? pwd->pw_name : ""); + } + tty = ttyname (STDERR_FILENO); + if (!tty) + tty = "none"; + /* 4.2BSD openlog doesn't have the third parameter. */ + openlog (last_component (program_name), 0 +# ifdef LOG_AUTH + , LOG_AUTH +# endif + ); + syslog (LOG_NOTICE, +# ifdef SYSLOG_NON_ROOT + "%s(to %s) %s on %s", +# else + "%s%s on %s", +# endif + successful ? "" : "FAILED SU ", +# ifdef SYSLOG_NON_ROOT + new_user, +# endif + old_user, tty); + closelog (); +} +#endif + +/* Ask the user for a password. + Return true if the user gives the correct password for entry PW, + false if not. Return true without asking for a password if run by UID 0 + or if PW has an empty password. */ + +static bool +correct_password (const struct passwd *pw) +{ + char *unencrypted, *encrypted, *correct; +#if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP + /* Shadow passwd stuff for SVR3 and maybe other systems. */ + struct spwd *sp = getspnam (pw->pw_name); + + endspent (); + if (sp) + correct = sp->sp_pwdp; + else +#endif + correct = pw->pw_passwd; + + if (getuid () == 0 || !correct || correct[0] == '\0') + return true; + + unencrypted = getpass (_("Password:")); + if (!unencrypted) + { + error (0, 0, _("getpass: cannot open /dev/tty")); + return false; + } + encrypted = crypt (unencrypted, correct); + memset (unencrypted, 0, strlen (unencrypted)); + return STREQ (encrypted, correct); +} + +/* Update `environ' for the new shell based on PW, with SHELL being + the value for the SHELL environment variable. */ + +static void +modify_environment (const struct passwd *pw, const char *shell) +{ + if (simulate_login) + { + /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH. + Unset all other environment variables. */ + char const *term = getenv ("TERM"); + if (term) + term = xstrdup (term); + environ = xmalloc ((6 + !!term) * sizeof (char *)); + environ[0] = NULL; + if (term) + xsetenv ("TERM", term); + xsetenv ("HOME", pw->pw_dir); + xsetenv ("SHELL", shell); + xsetenv ("USER", pw->pw_name); + xsetenv ("LOGNAME", pw->pw_name); + xsetenv ("PATH", (pw->pw_uid + ? DEFAULT_LOGIN_PATH + : DEFAULT_ROOT_LOGIN_PATH)); + } + else + { + /* Set HOME, SHELL, and if not becoming a super-user, + USER and LOGNAME. */ + if (change_environment) + { + xsetenv ("HOME", pw->pw_dir); + xsetenv ("SHELL", shell); + if (pw->pw_uid) + { + xsetenv ("USER", pw->pw_name); + xsetenv ("LOGNAME", pw->pw_name); + } + } + } +} + +/* Become the user and group(s) specified by PW. */ + +static void +change_identity (const struct passwd *pw) +{ +#ifdef HAVE_INITGROUPS + errno = 0; + if (initgroups (pw->pw_name, pw->pw_gid) == -1) + error (EXIT_FAIL, errno, _("cannot set groups")); + endgrent (); +#endif + if (setgid (pw->pw_gid)) + error (EXIT_FAIL, errno, _("cannot set group id")); + if (setuid (pw->pw_uid)) + error (EXIT_FAIL, errno, _("cannot set user id")); +} + +/* Run SHELL, or DEFAULT_SHELL if SHELL is empty. + If COMMAND is nonzero, pass it to the shell with the -c option. + Pass ADDITIONAL_ARGS to the shell as more arguments; there + are N_ADDITIONAL_ARGS extra arguments. */ + +static void +run_shell (char const *shell, char const *command, char **additional_args, + size_t n_additional_args) +{ + size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1; + char const **args = xnmalloc (n_args, sizeof *args); + size_t argno = 1; + + if (simulate_login) + { + char *arg0; + char *shell_basename; + + shell_basename = last_component (shell); + arg0 = xmalloc (strlen (shell_basename) + 2); + arg0[0] = '-'; + strcpy (arg0 + 1, shell_basename); + args[0] = arg0; + } + else + args[0] = last_component (shell); + if (fast_startup) + args[argno++] = "-f"; + if (command) + { + args[argno++] = "-c"; + args[argno++] = command; + } + memcpy (args + argno, additional_args, n_additional_args * sizeof *args); + args[argno + n_additional_args] = NULL; + execv (shell, (char **) args); + + { + int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE); + error (0, errno, "%s", shell); + exit (exit_status); + } +} + +/* Return true if SHELL is a restricted shell (one not returned by + getusershell), else false, meaning it is a standard shell. */ + +static bool +restricted_shell (const char *shell) +{ + char *line; + + setusershell (); + while ((line = getusershell ()) != NULL) + { + if (*line != '#' && STREQ (line, shell)) + { + endusershell (); + return false; + } + } + endusershell (); + return true; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name); + fputs (_("\ +Change the effective user id and group id to that of USER.\n\ +\n\ + -, -l, --login make the shell a login shell\n\ + -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\ + -f, --fast pass -f to the shell (for csh or tcsh)\n\ + -m, --preserve-environment do not reset environment variables\n\ + -p same as -m\n\ + -s, --shell=SHELL run SHELL if /etc/shells allows it\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +A mere - implies -l. If USER not given, assume root.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int optc; + const char *new_user = DEFAULT_USER; + char *command = NULL; + char *shell = NULL; + struct passwd *pw; + struct passwd pw_copy; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (EXIT_FAIL); + atexit (close_stdout); + + fast_startup = false; + simulate_login = false; + change_environment = true; + + while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, NULL)) != -1) + { + switch (optc) + { + case 'c': + command = optarg; + break; + + case 'f': + fast_startup = true; + break; + + case 'l': + simulate_login = true; + break; + + case 'm': + case 'p': + change_environment = false; + break; + + case 's': + shell = optarg; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAIL); + } + } + + if (optind < argc && STREQ (argv[optind], "-")) + { + simulate_login = true; + ++optind; + } + if (optind < argc) + new_user = argv[optind++]; + + pw = getpwnam (new_user); + if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0] + && pw->pw_passwd)) + error (EXIT_FAIL, 0, _("user %s does not exist"), new_user); + + /* Make a copy of the password information and point pw at the local + copy instead. Otherwise, some systems (e.g. Linux) would clobber + the static data through the getlogin call from log_su. + Also, make sure pw->pw_shell is a nonempty string. + It may be NULL when NEW_USER is a username that is retrieved via NIS (YP), + but that doesn't have a default shell listed. */ + pw_copy = *pw; + pw = &pw_copy; + pw->pw_name = xstrdup (pw->pw_name); + pw->pw_passwd = xstrdup (pw->pw_passwd); + pw->pw_dir = xstrdup (pw->pw_dir); + pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0] + ? pw->pw_shell + : DEFAULT_SHELL); + endpwent (); + + if (!correct_password (pw)) + { +#ifdef SYSLOG_FAILURE + log_su (pw, false); +#endif + error (EXIT_FAIL, 0, _("incorrect password")); + } +#ifdef SYSLOG_SUCCESS + else + { + log_su (pw, true); + } +#endif + + if (!shell && !change_environment) + shell = getenv ("SHELL"); + if (shell && getuid () != 0 && restricted_shell (pw->pw_shell)) + { + /* The user being su'd to has a nonstandard shell, and so is + probably a uucp account or has restricted access. Don't + compromise the account by allowing access with a standard + shell. */ + error (0, 0, _("using restricted shell %s"), pw->pw_shell); + shell = NULL; + } + shell = xstrdup (shell ? shell : pw->pw_shell); + modify_environment (pw, shell); + + change_identity (pw); + if (simulate_login && chdir (pw->pw_dir) != 0) + error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir); + + run_shell (shell, command, argv + optind, MAX (0, argc - optind)); +} diff --git a/src/sum.c b/src/sum.c new file mode 100644 index 0000000..92e4126 --- /dev/null +++ b/src/sum.c @@ -0,0 +1,269 @@ +/* sum -- checksum and count the blocks in a file + Copyright (C) 86, 89, 91, 1995-2002, 2004, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Like BSD sum or SysV sum -r, except like SysV sum if -s option is given. */ + +/* Written by Kayvan Aghaiepour and David MacKenzie. */ + +#include + +#include +#include +#include +#include "system.h" +#include "error.h" +#include "human.h" +#include "safe-read.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "sum" + +#define AUTHORS "Kayvan Aghaiepour", "David MacKenzie" + +/* The name this program was run with. */ +char *program_name; + +/* True if any of the files read were the standard input. */ +static bool have_read_stdin; + +static struct option const longopts[] = +{ + {"sysv", no_argument, NULL, 's'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + fputs (_("\ +Print checksum and block counts for each FILE.\n\ +\n\ + -r defeat -s, use BSD sum algorithm, use 1K blocks\n\ + -s, --sysv use System V sum algorithm, use 512 bytes blocks\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +With no FILE, or when FILE is -, read standard input.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Calculate and print the rotated checksum and the size in 1K blocks + of file FILE, or of the standard input if FILE is "-". + If PRINT_NAME is >1, print FILE next to the checksum and size. + The checksum varies depending on sizeof (int). + Return true if successful. */ + +static bool +bsd_sum_file (const char *file, int print_name) +{ + FILE *fp; + int checksum = 0; /* The checksum mod 2^16. */ + uintmax_t total_bytes = 0; /* The number of bytes. */ + int ch; /* Each character read. */ + char hbuf[LONGEST_HUMAN_READABLE + 1]; + bool is_stdin = STREQ (file, "-"); + + if (is_stdin) + { + fp = stdin; + have_read_stdin = true; + if (O_BINARY && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + } + else + { + fp = fopen (file, (O_BINARY ? "rb" : "r")); + if (fp == NULL) + { + error (0, errno, "%s", file); + return false; + } + } + + while ((ch = getc (fp)) != EOF) + { + total_bytes++; + checksum = (checksum >> 1) + ((checksum & 1) << 15); + checksum += ch; + checksum &= 0xffff; /* Keep it within bounds. */ + } + + if (ferror (fp)) + { + error (0, errno, "%s", file); + if (!is_stdin) + fclose (fp); + return false; + } + + if (!is_stdin && fclose (fp) != 0) + { + error (0, errno, "%s", file); + return false; + } + + printf ("%05d %5s", checksum, + human_readable (total_bytes, hbuf, human_ceiling, 1, 1024)); + if (print_name > 1) + printf (" %s", file); + putchar ('\n'); + + return true; +} + +/* Calculate and print the checksum and the size in 512-byte blocks + of file FILE, or of the standard input if FILE is "-". + If PRINT_NAME is >0, print FILE next to the checksum and size. + Return true if successful. */ + +static bool +sysv_sum_file (const char *file, int print_name) +{ + int fd; + unsigned char buf[8192]; + uintmax_t total_bytes = 0; + char hbuf[LONGEST_HUMAN_READABLE + 1]; + int r; + int checksum; + + /* The sum of all the input bytes, modulo (UINT_MAX + 1). */ + unsigned int s = 0; + + bool is_stdin = STREQ (file, "-"); + + if (is_stdin) + { + fd = STDIN_FILENO; + have_read_stdin = true; + if (O_BINARY && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + } + else + { + fd = open (file, O_RDONLY | O_BINARY); + if (fd == -1) + { + error (0, errno, "%s", file); + return false; + } + } + + while (1) + { + size_t i; + size_t bytes_read = safe_read (fd, buf, sizeof buf); + + if (bytes_read == 0) + break; + + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, "%s", file); + if (!is_stdin) + close (fd); + return false; + } + + for (i = 0; i < bytes_read; i++) + s += buf[i]; + total_bytes += bytes_read; + } + + if (!is_stdin && close (fd) != 0) + { + error (0, errno, "%s", file); + return false; + } + + r = (s & 0xffff) + ((s & 0xffffffff) >> 16); + checksum = (r & 0xffff) + (r >> 16); + + printf ("%d %s", checksum, + human_readable (total_bytes, hbuf, human_ceiling, 1, 512)); + if (print_name) + printf (" %s", file); + putchar ('\n'); + + return true; +} + +int +main (int argc, char **argv) +{ + bool ok; + int optc; + int files_given; + bool (*sum_func) (const char *, int) = bsd_sum_file; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + have_read_stdin = false; + + while ((optc = getopt_long (argc, argv, "rs", longopts, NULL)) != -1) + { + switch (optc) + { + case 'r': /* For SysV compatibility. */ + sum_func = bsd_sum_file; + break; + + case 's': + sum_func = sysv_sum_file; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + files_given = argc - optind; + if (files_given <= 0) + ok = sum_func ("-", files_given); + else + for (ok = true; optind < argc; optind++) + ok &= sum_func (argv[optind], files_given); + + if (have_read_stdin && fclose (stdin) == EOF) + error (EXIT_FAILURE, errno, "-"); + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/sync.c b/src/sync.c new file mode 100644 index 0000000..5e94afb --- /dev/null +++ b/src/sync.c @@ -0,0 +1,78 @@ +/* sync - update the super block + Copyright (C) 1994-2004 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Jim Meyering */ + +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "long-options.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "sync" + +#define AUTHORS "Jim Meyering" + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]\n"), program_name); + fputs (_("\ +Force changed blocks to disk, update the super block.\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (optind < argc) + error (0, 0, _("ignoring all arguments")); + + sync (); + exit (EXIT_SUCCESS); +} diff --git a/src/system.h b/src/system.h new file mode 100644 index 0000000..763909b --- /dev/null +++ b/src/system.h @@ -0,0 +1,583 @@ +/* system-dependent definitions for coreutils + Copyright (C) 1989, 1991-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include + +/* Include sys/types.h before this file. */ + +#if 2 <= __GLIBC__ && 2 <= __GLIBC_MINOR__ +# if ! defined _SYS_TYPES_H +you must include before including this file +# endif +#endif + +#include + +#if !defined HAVE_MKFIFO +# define mkfifo(name, mode) mknod (name, (mode) | S_IFIFO, 0) +#endif + +#if HAVE_SYS_PARAM_H +# include +#endif + +#include + +#ifndef STDIN_FILENO +# define STDIN_FILENO 0 +#endif + +#ifndef STDOUT_FILENO +# define STDOUT_FILENO 1 +#endif + +#ifndef STDERR_FILENO +# define STDERR_FILENO 2 +#endif + + +/* limits.h must come before pathmax.h because limits.h on some systems + undefs PATH_MAX, whereas pathmax.h sets PATH_MAX. */ +#include + +#include "pathmax.h" + +#include "configmake.h" + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +/* Since major is a function on SVR4, we can't use `ifndef major'. */ +#if MAJOR_IN_MKDEV +# include +# define HAVE_MAJOR +#endif +#if MAJOR_IN_SYSMACROS +# include +# define HAVE_MAJOR +#endif +#ifdef major /* Might be defined in sys/types.h. */ +# define HAVE_MAJOR +#endif + +#ifndef HAVE_MAJOR +# define major(dev) (((dev) >> 8) & 0xff) +# define minor(dev) ((dev) & 0xff) +# define makedev(maj, min) (((maj) << 8) | (min)) +#endif +#undef HAVE_MAJOR + +#if ! defined makedev && defined mkdev +# define makedev(maj, min) mkdev (maj, min) +#endif + +/* Don't use bcopy! Use memmove if source and destination may overlap, + memcpy otherwise. */ + +#include + +#include + +/* Some systems don't define the following symbols. */ +#ifndef EDQUOT +# define EDQUOT (-1) +#endif +#ifndef EISDIR +# define EISDIR (-1) +#endif +#ifndef ENOSYS +# define ENOSYS (-1) +#endif + +#include +#include + +/* Exit statuses for programs like 'env' that exec other programs. + EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs. */ +enum +{ + EXIT_FAIL = 1, + EXIT_CANNOT_INVOKE = 126, + EXIT_ENOENT = 127 +}; + +#include "exitfail.h" + +/* Set exit_failure to STATUS if that's not the default already. */ +static inline void +initialize_exit_failure (int status) +{ + if (status != EXIT_FAILURE) + exit_failure = status; +} + +#include + +#ifndef F_OK +# define F_OK 0 +# define X_OK 1 +# define W_OK 2 +# define R_OK 4 +#endif + +#include +#ifndef _D_EXACT_NAMLEN +# define _D_EXACT_NAMLEN(dp) strlen ((dp)->d_name) +#endif + +enum +{ + NOT_AN_INODE_NUMBER = 0 +}; + +#ifdef D_INO_IN_DIRENT +# define D_INO(dp) (dp)->d_ino +#else +/* Some systems don't have inodes, so fake them to avoid lots of ifdefs. */ +# define D_INO(dp) NOT_AN_INODE_NUMBER +#endif + +/* Get or fake the disk device blocksize. + Usually defined by sys/param.h (if at all). */ +#if !defined DEV_BSIZE && defined BSIZE +# define DEV_BSIZE BSIZE +#endif +#if !defined DEV_BSIZE && defined BBSIZE /* SGI */ +# define DEV_BSIZE BBSIZE +#endif +#ifndef DEV_BSIZE +# define DEV_BSIZE 4096 +#endif + +/* Extract or fake data from a `struct stat'. + ST_BLKSIZE: Preferred I/O blocksize for the file, in bytes. + ST_NBLOCKS: Number of blocks in the file, including indirect blocks. + ST_NBLOCKSIZE: Size of blocks used when calculating ST_NBLOCKS. */ +#ifndef HAVE_STRUCT_STAT_ST_BLOCKS +# define ST_BLKSIZE(statbuf) DEV_BSIZE +# if defined _POSIX_SOURCE || !defined BSIZE /* fileblocks.c uses BSIZE. */ +# define ST_NBLOCKS(statbuf) \ + ((statbuf).st_size / ST_NBLOCKSIZE + ((statbuf).st_size % ST_NBLOCKSIZE != 0)) +# else /* !_POSIX_SOURCE && BSIZE */ +# define ST_NBLOCKS(statbuf) \ + (S_ISREG ((statbuf).st_mode) \ + || S_ISDIR ((statbuf).st_mode) \ + ? st_blocks ((statbuf).st_size) : 0) +# endif /* !_POSIX_SOURCE && BSIZE */ +#else /* HAVE_STRUCT_STAT_ST_BLOCKS */ +/* Some systems, like Sequents, return st_blksize of 0 on pipes. + Also, when running `rsh hpux11-system cat any-file', cat would + determine that the output stream had an st_blksize of 2147421096. + Conversely st_blksize can be 2 GiB (or maybe even larger) with XFS + on 64-bit hosts. Somewhat arbitrarily, limit the `optimal' block + size to SIZE_MAX / 8 + 1. (Dividing SIZE_MAX by only 4 wouldn't + suffice, since "cat" sometimes multiplies the result by 4.) If + anyone knows of a system for which this limit is too small, please + report it as a bug in this code. */ +# define ST_BLKSIZE(statbuf) ((0 < (statbuf).st_blksize \ + && (statbuf).st_blksize <= SIZE_MAX / 8 + 1) \ + ? (statbuf).st_blksize : DEV_BSIZE) +# if defined hpux || defined __hpux__ || defined __hpux +/* HP-UX counts st_blocks in 1024-byte units. + This loses when mixing HP-UX and BSD file systems with NFS. */ +# define ST_NBLOCKSIZE 1024 +# else /* !hpux */ +# if defined _AIX && defined _I386 +/* AIX PS/2 counts st_blocks in 4K units. */ +# define ST_NBLOCKSIZE (4 * 1024) +# else /* not AIX PS/2 */ +# if defined _CRAY +# define ST_NBLOCKS(statbuf) \ + (S_ISREG ((statbuf).st_mode) \ + || S_ISDIR ((statbuf).st_mode) \ + ? (statbuf).st_blocks * ST_BLKSIZE(statbuf)/ST_NBLOCKSIZE : 0) +# endif /* _CRAY */ +# endif /* not AIX PS/2 */ +# endif /* !hpux */ +#endif /* HAVE_STRUCT_STAT_ST_BLOCKS */ + +#ifndef ST_NBLOCKS +# define ST_NBLOCKS(statbuf) ((statbuf).st_blocks) +#endif + +#ifndef ST_NBLOCKSIZE +# ifdef S_BLKSIZE +# define ST_NBLOCKSIZE S_BLKSIZE +# else +# define ST_NBLOCKSIZE 512 +# endif +#endif + +/* Redirection and wildcarding when done by the utility itself. + Generally a noop, but used in particular for native VMS. */ +#ifndef initialize_main +# define initialize_main(ac, av) +#endif + +#include "stat-macros.h" + +#include "timespec.h" + +#include + +#include + +#if ! (defined isblank || HAVE_DECL_ISBLANK) +# define isblank(c) ((c) == ' ' || (c) == '\t') +#endif + +/* ISDIGIT differs from isdigit, as follows: + - Its arg may be any int or unsigned int; it need not be an unsigned char + or EOF. + - It's typically faster. + POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to + isdigit unless it's important to use the locale's definition + of `digit' even when the host does not conform to POSIX. */ +#define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9) + +/* Convert a possibly-signed character to an unsigned character. This is + a bit safer than casting to unsigned char, since it catches some type + errors that the cast doesn't. */ +static inline unsigned char to_uchar (char ch) { return ch; } + +#include + +/* Take care of NLS matters. */ + +#include "gettext.h" +#if ! ENABLE_NLS +# undef textdomain +# define textdomain(Domainname) /* empty */ +# undef bindtextdomain +# define bindtextdomain(Domainname, Dirname) /* empty */ +#endif + +#define _(msgid) gettext (msgid) +#define N_(msgid) msgid + +/* Return a value that pluralizes the same way that N does, in all + languages we know of. */ +static inline unsigned long int +select_plural (uintmax_t n) +{ + /* Reduce by a power of ten, but keep it away from zero. The + gettext manual says 1000000 should be safe. */ + enum { PLURAL_REDUCER = 1000000 }; + return (n <= ULONG_MAX ? n : n % PLURAL_REDUCER + PLURAL_REDUCER); +} + +#define STREQ(a, b) (strcmp (a, b) == 0) + +#if !HAVE_DECL_FREE +void free (); +#endif + +#if !HAVE_DECL_MALLOC +char *malloc (); +#endif + +#if !HAVE_DECL_MEMCHR +char *memchr (); +#endif + +#if !HAVE_DECL_REALLOC +char *realloc (); +#endif + +#if !HAVE_DECL_GETENV +char *getenv (); +#endif + +#if !HAVE_DECL_LSEEK +off_t lseek (); +#endif + +#if !HAVE_DECL_GETLOGIN +char *getlogin (); +#endif + +#if !HAVE_DECL_TTYNAME +char *ttyname (); +#endif + +#if !HAVE_DECL_GETEUID +uid_t geteuid (); +#endif + +#if !HAVE_DECL_GETPWUID +struct passwd *getpwuid (); +#endif + +#if !HAVE_DECL_GETGRGID +struct group *getgrgid (); +#endif + +#if !HAVE_DECL_GETUID +uid_t getuid (); +#endif + +#include "xalloc.h" +#include "verify.h" + +/* This is simply a shorthand for the common case in which + the third argument to x2nrealloc would be `sizeof *(P)'. + Ensure that sizeof *(P) is *not* 1. In that case, it'd be + better to use X2REALLOC, although not strictly necessary. */ +#define X2NREALLOC(P, PN) ((void) verify_true (sizeof *(P) != 1), \ + x2nrealloc (P, PN, sizeof *(P))) + +/* Using x2realloc (when appropriate) usually makes your code more + readable than using x2nrealloc, but it also makes it so your + code will malfunction if sizeof *(P) ever becomes 2 or greater. + So use this macro instead of using x2realloc directly. */ +#define X2REALLOC(P, PN) ((void) verify_true (sizeof *(P) == 1), \ + x2realloc (P, PN)) + +#include "unlocked-io.h" +#include "same-inode.h" + +#include "dirname.h" + +static inline bool +dot_or_dotdot (char const *file_name) +{ + if (file_name[0] == '.') + { + char sep = file_name[(file_name[1] == '.') + 1]; + return (! sep || ISSLASH (sep)); + } + else + return false; +} + +/* A wrapper for readdir so that callers don't see entries for `.' or `..'. */ +static inline struct dirent const * +readdir_ignoring_dot_and_dotdot (DIR *dirp) +{ + while (1) + { + struct dirent const *dp = readdir (dirp); + if (dp == NULL || ! dot_or_dotdot (dp->d_name)) + return dp; + } +} + +/* Factor out some of the common --help and --version processing code. */ + +/* These enum values cannot possibly conflict with the option values + ordinarily used by commands, including CHAR_MAX + 1, etc. Avoid + CHAR_MIN - 1, as it may equal -1, the getopt end-of-options value. */ +enum +{ + GETOPT_HELP_CHAR = (CHAR_MIN - 2), + GETOPT_VERSION_CHAR = (CHAR_MIN - 3) +}; + +#define GETOPT_HELP_OPTION_DECL \ + "help", no_argument, NULL, GETOPT_HELP_CHAR +#define GETOPT_VERSION_OPTION_DECL \ + "version", no_argument, NULL, GETOPT_VERSION_CHAR + +#define case_GETOPT_HELP_CHAR \ + case GETOPT_HELP_CHAR: \ + usage (EXIT_SUCCESS); \ + break; + +/* Program_name must be a literal string. + Usually it is just PROGRAM_NAME. */ +#define USAGE_BUILTIN_WARNING \ + _("\n" \ +"NOTE: your shell may have its own version of %s, which usually supersedes\n" \ +"the version described here. Please refer to your shell's documentation\n" \ +"for details about the options it supports.\n") + +#define HELP_OPTION_DESCRIPTION \ + _(" --help display this help and exit\n") +#define VERSION_OPTION_DESCRIPTION \ + _(" --version output version information and exit\n") + +#include "closeout.h" +#include "version-etc.h" + +#define case_GETOPT_VERSION_CHAR(Program_name, Authors) \ + case GETOPT_VERSION_CHAR: \ + version_etc (stdout, Program_name, GNU_PACKAGE, VERSION, Authors, \ + (char *) NULL); \ + exit (EXIT_SUCCESS); \ + break; + +#ifndef MAX +# define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef MIN +# define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#include "intprops.h" + +#ifndef SSIZE_MAX +# define SSIZE_MAX TYPE_MAXIMUM (ssize_t) +#endif + +#ifndef OFF_T_MIN +# define OFF_T_MIN TYPE_MINIMUM (off_t) +#endif + +#ifndef OFF_T_MAX +# define OFF_T_MAX TYPE_MAXIMUM (off_t) +#endif + +#ifndef UID_T_MAX +# define UID_T_MAX TYPE_MAXIMUM (uid_t) +#endif + +#ifndef GID_T_MAX +# define GID_T_MAX TYPE_MAXIMUM (gid_t) +#endif + +#ifndef PID_T_MAX +# define PID_T_MAX TYPE_MAXIMUM (pid_t) +#endif + +/* Use this to suppress gcc's `...may be used before initialized' warnings. */ +#ifdef lint +# define IF_LINT(Code) Code +#else +# define IF_LINT(Code) /* empty */ +#endif + +#ifndef __attribute__ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__ +# define __attribute__(x) /* empty */ +# endif +#endif + +#ifndef ATTRIBUTE_NORETURN +# define ATTRIBUTE_NORETURN __attribute__ ((__noreturn__)) +#endif + +#ifndef ATTRIBUTE_UNUSED +# define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +#endif + +#if defined strdupa +# define ASSIGN_STRDUPA(DEST, S) \ + do { DEST = strdupa (S); } while (0) +#else +# define ASSIGN_STRDUPA(DEST, S) \ + do \ + { \ + const char *s_ = (S); \ + size_t len_ = strlen (s_) + 1; \ + char *tmp_dest_ = alloca (len_); \ + DEST = memcpy (tmp_dest_, s_, len_); \ + } \ + while (0) +#endif + +#ifndef EOVERFLOW +# define EOVERFLOW EINVAL +#endif + +#if ! HAVE_FSEEKO +# if ! defined fseeko +# define fseeko(s, o, w) ((o) == (long int) (o) \ + ? fseek (s, o, w) \ + : (errno = EOVERFLOW, -1)) +# endif +# if ! defined ftello +static inline off_t ftello (FILE *stream) +{ + verify (sizeof (long int) <= sizeof (off_t)); + return ftell (stream); +} +# endif +#endif + +#if ! HAVE_SYNC +# define sync() /* empty */ +#endif + +/* Compute the greatest common divisor of U and V using Euclid's + algorithm. U and V must be nonzero. */ + +static inline size_t +gcd (size_t u, size_t v) +{ + do + { + size_t t = u % v; + u = v; + v = t; + } + while (v); + + return u; +} + +/* Compute the least common multiple of U and V. U and V must be + nonzero. There is no overflow checking, so callers should not + specify outlandish sizes. */ + +static inline size_t +lcm (size_t u, size_t v) +{ + return u * (v / gcd (u, v)); +} + +/* Return PTR, aligned upward to the next multiple of ALIGNMENT. + ALIGNMENT must be nonzero. The caller must arrange for ((char *) + PTR) through ((char *) PTR + ALIGNMENT - 1) to be addressable + locations. */ + +static inline void * +ptr_align (void const *ptr, size_t alignment) +{ + char const *p0 = ptr; + char const *p1 = p0 + alignment - 1; + return (void *) (p1 - (size_t) p1 % alignment); +} + +/* If 10*Accum + Digit_val is larger than the maximum value for Type, + then don't update Accum and return false to indicate it would + overflow. Otherwise, set Accum to that new value and return true. + Verify at compile-time that Type is Accum's type, and that Type is + unsigned. Accum must be an object, so that we can take its + address. Accum and Digit_val may be evaluated multiple times. + + The "Added check" below is not strictly required, but it causes GCC + to return a nonzero exit status instead of merely a warning + diagnostic, and that is more useful. */ + +#define DECIMAL_DIGIT_ACCUMULATE(Accum, Digit_val, Type) \ + ( \ + (void) (&(Accum) == (Type *) NULL), /* The type matches. */ \ + (void) verify_true (! TYPE_SIGNED (Type)), /* The type is unsigned. */ \ + (void) verify_true (sizeof (Accum) == sizeof (Type)), /* Added check. */ \ + (((Type) -1 / 10 < (Accum) \ + || (Type) ((Accum) * 10 + (Digit_val)) < (Accum)) \ + ? false : (((Accum) = (Accum) * 10 + (Digit_val)), true)) \ + ) diff --git a/src/tac-pipe.c b/src/tac-pipe.c new file mode 100644 index 0000000..d2c6f67 --- /dev/null +++ b/src/tac-pipe.c @@ -0,0 +1,263 @@ +/* tac from a pipe. + + Copyright (C) 1997, 1998, 2002, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* FIXME */ +#include + +/* FIXME: this is small for testing */ +#define BUFFER_SIZE (8) + +#define LEN(X, I) ((X)->p[(I)].one_past_end - (X)->p[(I)].start) +#define EMPTY(X) ((X)->n_bufs == 1 && LEN (X, 0) == 0) + +#define ONE_PAST_END(X, I) ((X)->p[(I)].one_past_end) + +struct Line_ptr +{ + size_t i; + char *ptr; +}; +typedef struct Line_ptr Line_ptr; + +struct B_pair +{ + char *start; + char *one_past_end; +}; + +struct Buf +{ + size_t n_bufs; + struct obstack obs; + struct B_pair *p; +}; +typedef struct Buf Buf; + +static bool +buf_init_from_stdin (Buf *x, char eol_byte) +{ + bool last_byte_is_eol_byte = true; + bool ok = true; + +#define OBS (&(x->obs)) + obstack_init (OBS); + + while (1) + { + char *buf = (char *) malloc (BUFFER_SIZE); + size_t bytes_read; + + if (buf == NULL) + { + /* Fall back on the code that relies on a temporary file. + Write all buffers to that file and free them. */ + /* FIXME */ + ok = false; + break; + } + bytes_read = full_read (STDIN_FILENO, buf, BUFFER_SIZE); + if (bytes_read != buffer_size && errno != 0) + error (EXIT_FAILURE, errno, _("read error")); + + { + struct B_pair bp; + bp.start = buf; + bp.one_past_end = buf + bytes_read; + obstack_grow (OBS, &bp, sizeof (bp)); + } + + if (bytes_read != 0) + last_byte_is_eol_byte = (buf[bytes_read - 1] == eol_byte); + + if (bytes_read < BUFFER_SIZE) + break; + } + + if (ok) + { + /* If the file was non-empty and lacked an EOL_BYTE at its end, + then add a buffer containing just that one byte. */ + if (!last_byte_is_eol_byte) + { + char *buf = malloc (1); + if (buf == NULL) + { + /* FIXME: just like above */ + ok = false; + } + else + { + struct B_pair bp; + *buf = eol_byte; + bp.start = buf; + bp.one_past_end = buf + 1; + obstack_grow (OBS, &bp, sizeof (bp)); + } + } + } + + x->n_bufs = obstack_object_size (OBS) / sizeof (x->p[0]); + x->p = (struct B_pair *) obstack_finish (OBS); + + /* If there are two or more buffers and the last buffer is empty, + then free the last one and decrement the buffer count. */ + if (x->n_bufs >= 2 + && x->p[x->n_bufs - 1].start == x->p[x->n_bufs - 1].one_past_end) + free (x->p[--(x->n_bufs)].start); + + return ok; +} + +static void +buf_free (Buf *x) +{ + size_t i; + for (i = 0; i < x->n_bufs; i++) + free (x->p[i].start); + obstack_free (OBS, NULL); +} + +Line_ptr +line_ptr_decrement (const Buf *x, const Line_ptr *lp) +{ + Line_ptr lp_new; + + if (lp->ptr > x->p[lp->i].start) + { + lp_new.i = lp->i; + lp_new.ptr = lp->ptr - 1; + } + else + { + assert (lp->i > 0); + lp_new.i = lp->i - 1; + lp_new.ptr = ONE_PAST_END (x, lp->i - 1) - 1; + } + return lp_new; +} + +Line_ptr +line_ptr_increment (const Buf *x, const Line_ptr *lp) +{ + Line_ptr lp_new; + + assert (lp->ptr <= ONE_PAST_END (x, lp->i) - 1); + if (lp->ptr < ONE_PAST_END (x, lp->i) - 1) + { + lp_new.i = lp->i; + lp_new.ptr = lp->ptr + 1; + } + else + { + assert (lp->i < x->n_bufs - 1); + lp_new.i = lp->i + 1; + lp_new.ptr = x->p[lp->i + 1].start; + } + return lp_new; +} + +static bool +find_bol (const Buf *x, + const Line_ptr *last_bol, Line_ptr *new_bol, char eol_byte) +{ + size_t i; + Line_ptr tmp; + char *last_bol_ptr; + + if (last_bol->ptr == x->p[0].start) + return false; + + tmp = line_ptr_decrement (x, last_bol); + last_bol_ptr = tmp.ptr; + i = tmp.i; + while (1) + { + char *nl = memrchr (x->p[i].start, last_bol_ptr, eol_byte); + if (nl) + { + Line_ptr nl_pos; + nl_pos.i = i; + nl_pos.ptr = nl; + *new_bol = line_ptr_increment (x, &nl_pos); + return true; + } + + if (i == 0) + break; + + --i; + last_bol_ptr = ONE_PAST_END (x, i); + } + + /* If last_bol->ptr didn't point at the first byte of X, then reaching + this point means that we're about to return the line that is at the + beginning of X. */ + if (last_bol->ptr != x->p[0].start) + { + new_bol->i = 0; + new_bol->ptr = x->p[0].start; + return true; + } + + return false; +} + +static void +print_line (FILE *out_stream, const Buf *x, + const Line_ptr *bol, const Line_ptr *bol_next) +{ + size_t i; + for (i = bol->i; i <= bol_next->i; i++) + { + char *a = (i == bol->i ? bol->ptr : x->p[i].start); + char *b = (i == bol_next->i ? bol_next->ptr : ONE_PAST_END (x, i)); + fwrite (a, 1, b - a, out_stream); + } +} + +static bool +tac_mem () +{ + Buf x; + Line_ptr bol; + char eol_byte = '\n'; + + if (! buf_init_from_stdin (&x, eol_byte)) + { + buf_free (&x); + return false; + } + + /* Special case the empty file. */ + if (EMPTY (&x)) + return true; + + /* Initially, point at one past the last byte of the file. */ + bol.i = x.n_bufs - 1; + bol.ptr = ONE_PAST_END (&x, bol.i); + + while (1) + { + Line_ptr new_bol; + if (! find_bol (&x, &bol, &new_bol, eol_byte)) + break; + print_line (stdout, &x, &new_bol, &bol); + bol = new_bol; + } + return true; +} diff --git a/src/tac.c b/src/tac.c new file mode 100644 index 0000000..dc166aa --- /dev/null +++ b/src/tac.c @@ -0,0 +1,666 @@ +/* tac - concatenate and print files in reverse + Copyright (C) 1988-1991, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Jay Lepreau (lepreau@cs.utah.edu). + GNU enhancements by David MacKenzie (djm@gnu.ai.mit.edu). */ + +/* Copy each FILE, or the standard input if none are given or when a + FILE name of "-" is encountered, to the standard output with the + order of the records reversed. The records are separated by + instances of a string, or a newline if none is given. By default, the + separator string is attached to the end of the record that it + follows in the file. + + Options: + -b, --before The separator is attached to the beginning + of the record that it precedes in the file. + -r, --regex The separator is a regular expression. + -s, --separator=separator Use SEPARATOR as the record separator. + + To reverse a file byte by byte, use (in bash, ksh, or sh): +tac -r -s '.\| +' file */ + +#include + +#include +#include +#include +#include "system.h" + +#include + +#include "error.h" +#include "quote.h" +#include "quotearg.h" +#include "safe-read.h" +#include "stdlib--.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "tac" + +#define AUTHORS "Jay Lepreau", "David MacKenzie" + +#if defined __MSDOS__ || defined _WIN32 +/* Define this to non-zero on systems for which the regular mechanism + (of unlinking an open file and expecting to be able to write, seek + back to the beginning, then reread it) doesn't work. E.g., on Windows + and DOS systems. */ +# define DONT_UNLINK_WHILE_OPEN 1 +#endif + + +#ifndef DEFAULT_TMPDIR +# define DEFAULT_TMPDIR "/tmp" +#endif + +/* The number of bytes per atomic read. */ +#define INITIAL_READSIZE 8192 + +/* The number of bytes per atomic write. */ +#define WRITESIZE 8192 + +/* The name this program was run with. */ +char *program_name; + +/* The string that separates the records of the file. */ +static char const *separator; + +/* True if we have ever read standard input. */ +static bool have_read_stdin = false; + +/* If true, print `separator' along with the record preceding it + in the file; otherwise with the record following it. */ +static bool separator_ends_record; + +/* 0 if `separator' is to be matched as a regular expression; + otherwise, the length of `separator', used as a sentinel to + stop the search. */ +static size_t sentinel_length; + +/* The length of a match with `separator'. If `sentinel_length' is 0, + `match_length' is computed every time a match succeeds; + otherwise, it is simply the length of `separator'. */ +static size_t match_length; + +/* The input buffer. */ +static char *G_buffer; + +/* The number of bytes to read at once into `buffer'. */ +static size_t read_size; + +/* The size of `buffer'. This is read_size * 2 + sentinel_length + 2. + The extra 2 bytes allow `past_end' to have a value beyond the + end of `G_buffer' and `match_start' to run off the front of `G_buffer'. */ +static size_t G_buffer_size; + +/* The compiled regular expression representing `separator'. */ +static struct re_pattern_buffer compiled_separator; +static char compiled_separator_fastmap[UCHAR_MAX + 1]; + +static struct option const longopts[] = +{ + {"before", no_argument, NULL, 'b'}, + {"regex", no_argument, NULL, 'r'}, + {"separator", required_argument, NULL, 's'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + fputs (_("\ +Write each FILE to standard output, last line first.\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -b, --before attach the separator before instead of after\n\ + -r, --regex interpret the separator as a regular expression\n\ + -s, --separator=STRING use STRING as the separator instead of newline\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Print the characters from START to PAST_END - 1. + If START is NULL, just flush the buffer. */ + +static void +output (const char *start, const char *past_end) +{ + static char buffer[WRITESIZE]; + static size_t bytes_in_buffer = 0; + size_t bytes_to_add = past_end - start; + size_t bytes_available = WRITESIZE - bytes_in_buffer; + + if (start == 0) + { + fwrite (buffer, 1, bytes_in_buffer, stdout); + bytes_in_buffer = 0; + return; + } + + /* Write out as many full buffers as possible. */ + while (bytes_to_add >= bytes_available) + { + memcpy (buffer + bytes_in_buffer, start, bytes_available); + bytes_to_add -= bytes_available; + start += bytes_available; + fwrite (buffer, 1, WRITESIZE, stdout); + bytes_in_buffer = 0; + bytes_available = WRITESIZE; + } + + memcpy (buffer + bytes_in_buffer, start, bytes_to_add); + bytes_in_buffer += bytes_to_add; +} + +/* Print in reverse the file open on descriptor FD for reading FILE. + Return true if successful. */ + +static bool +tac_seekable (int input_fd, const char *file) +{ + /* Pointer to the location in `G_buffer' where the search for + the next separator will begin. */ + char *match_start; + + /* Pointer to one past the rightmost character in `G_buffer' that + has not been printed yet. */ + char *past_end; + + /* Length of the record growing in `G_buffer'. */ + size_t saved_record_size; + + /* Offset in the file of the next read. */ + off_t file_pos; + + /* True if `output' has not been called yet for any file. + Only used when the separator is attached to the preceding record. */ + bool first_time = true; + char first_char = *separator; /* Speed optimization, non-regexp. */ + char const *separator1 = separator + 1; /* Speed optimization, non-regexp. */ + size_t match_length1 = match_length - 1; /* Speed optimization, non-regexp. */ + struct re_registers regs; + + /* Find the size of the input file. */ + file_pos = lseek (input_fd, (off_t) 0, SEEK_END); + if (file_pos < 1) + return true; /* It's an empty file. */ + + /* Arrange for the first read to lop off enough to leave the rest of the + file a multiple of `read_size'. Since `read_size' can change, this may + not always hold during the program run, but since it usually will, leave + it here for i/o efficiency (page/sector boundaries and all that). + Note: the efficiency gain has not been verified. */ + saved_record_size = file_pos % read_size; + if (saved_record_size == 0) + saved_record_size = read_size; + file_pos -= saved_record_size; + /* `file_pos' now points to the start of the last (probably partial) block + in the input file. */ + + if (lseek (input_fd, file_pos, SEEK_SET) < 0) + error (0, errno, _("%s: seek failed"), quotearg_colon (file)); + + if (safe_read (input_fd, G_buffer, saved_record_size) != saved_record_size) + { + error (0, errno, _("%s: read error"), quotearg_colon (file)); + return false; + } + + match_start = past_end = G_buffer + saved_record_size; + /* For non-regexp search, move past impossible positions for a match. */ + if (sentinel_length) + match_start -= match_length1; + + for (;;) + { + /* Search backward from `match_start' - 1 to `G_buffer' for a match + with `separator'; for speed, use strncmp if `separator' contains no + metacharacters. + If the match succeeds, set `match_start' to point to the start of + the match and `match_length' to the length of the match. + Otherwise, make `match_start' < `G_buffer'. */ + if (sentinel_length == 0) + { + size_t i = match_start - G_buffer; + regoff_t ri = i; + regoff_t range = 1 - ri; + regoff_t ret; + + if (1 < range) + error (EXIT_FAILURE, 0, _("record too large")); + + if (range == 1 + || ((ret = re_search (&compiled_separator, G_buffer, + i, i - 1, range, ®s)) + == -1)) + match_start = G_buffer - 1; + else if (ret == -2) + { + error (EXIT_FAILURE, 0, + _("error in regular expression search")); + } + else + { + match_start = G_buffer + regs.start[0]; + match_length = regs.end[0] - regs.start[0]; + } + } + else + { + /* `match_length' is constant for non-regexp boundaries. */ + while (*--match_start != first_char + || (match_length1 && strncmp (match_start + 1, separator1, + match_length1))) + /* Do nothing. */ ; + } + + /* Check whether we backed off the front of `G_buffer' without finding + a match for `separator'. */ + if (match_start < G_buffer) + { + if (file_pos == 0) + { + /* Hit the beginning of the file; print the remaining record. */ + output (G_buffer, past_end); + return true; + } + + saved_record_size = past_end - G_buffer; + if (saved_record_size > read_size) + { + /* `G_buffer_size' is about twice `read_size', so since + we want to read in another `read_size' bytes before + the data already in `G_buffer', we need to increase + `G_buffer_size'. */ + char *newbuffer; + size_t offset = sentinel_length ? sentinel_length : 1; + ptrdiff_t match_start_offset = match_start - G_buffer; + ptrdiff_t past_end_offset = past_end - G_buffer; + size_t old_G_buffer_size = G_buffer_size; + + read_size *= 2; + G_buffer_size = read_size * 2 + sentinel_length + 2; + if (G_buffer_size < old_G_buffer_size) + xalloc_die (); + newbuffer = xrealloc (G_buffer - offset, G_buffer_size); + newbuffer += offset; + /* Adjust the pointers for the new buffer location. */ + match_start = newbuffer + match_start_offset; + past_end = newbuffer + past_end_offset; + G_buffer = newbuffer; + } + + /* Back up to the start of the next bufferfull of the file. */ + if (file_pos >= read_size) + file_pos -= read_size; + else + { + read_size = file_pos; + file_pos = 0; + } + if (lseek (input_fd, file_pos, SEEK_SET) < 0) + error (0, errno, _("%s: seek failed"), quotearg_colon (file)); + + /* Shift the pending record data right to make room for the new. + The source and destination regions probably overlap. */ + memmove (G_buffer + read_size, G_buffer, saved_record_size); + past_end = G_buffer + read_size + saved_record_size; + /* For non-regexp searches, avoid unneccessary scanning. */ + if (sentinel_length) + match_start = G_buffer + read_size; + else + match_start = past_end; + + if (safe_read (input_fd, G_buffer, read_size) != read_size) + { + error (0, errno, _("%s: read error"), quotearg_colon (file)); + return false; + } + } + else + { + /* Found a match of `separator'. */ + if (separator_ends_record) + { + char *match_end = match_start + match_length; + + /* If this match of `separator' isn't at the end of the + file, print the record. */ + if (!first_time || match_end != past_end) + output (match_end, past_end); + past_end = match_end; + first_time = false; + } + else + { + output (match_start, past_end); + past_end = match_start; + } + + /* For non-regex matching, we can back up. */ + if (sentinel_length > 0) + match_start -= match_length - 1; + } + } +} + +#if DONT_UNLINK_WHILE_OPEN + +/* FIXME-someday: remove all of this DONT_UNLINK_WHILE_OPEN junk. + Using atexit like this is wrong, since it can fail + when called e.g. 32 or more times. + But this isn't a big deal, since the code is used only on WOE/DOS + systems, and few people invoke tac on that many nonseekable files. */ + +static const char *file_to_remove; +static FILE *fp_to_close; + +static void +unlink_tempfile (void) +{ + fclose (fp_to_close); + unlink (file_to_remove); +} + +static void +record_or_unlink_tempfile (char const *fn, FILE *fp) +{ + if (!file_to_remove) + { + file_to_remove = fn; + fp_to_close = fp; + atexit (unlink_tempfile); + } +} + +#else + +static void +record_or_unlink_tempfile (char const *fn, FILE *fp ATTRIBUTE_UNUSED) +{ + unlink (fn); +} + +#endif + +/* Copy from file descriptor INPUT_FD (corresponding to the named FILE) to + a temporary file, and set *G_TMP and *G_TEMPFILE to the resulting stream + and file name. Return true if successful. */ + +static bool +copy_to_temp (FILE **g_tmp, char **g_tempfile, int input_fd, char const *file) +{ + static char *template = NULL; + static char const *tempdir; + char *tempfile; + FILE *tmp; + int fd; + + if (template == NULL) + { + char const * const Template = "%s/tacXXXXXX"; + tempdir = getenv ("TMPDIR"); + if (tempdir == NULL) + tempdir = DEFAULT_TMPDIR; + + /* Subtract 2 for `%s' and add 1 for the trailing NUL byte. */ + template = xmalloc (strlen (tempdir) + strlen (Template) - 2 + 1); + sprintf (template, Template, tempdir); + } + + /* FIXME: there's a small window between a successful mkstemp call + and the unlink that's performed by record_or_unlink_tempfile. + If we're interrupted in that interval, this code fails to remove + the temporary file. On systems that define DONT_UNLINK_WHILE_OPEN, + the window is much larger -- it extends to the atexit-called + unlink_tempfile. + FIXME: clean up upon fatal signal. Don't block them, in case + $TMPFILE is a remote file system. */ + + tempfile = template; + fd = mkstemp (template); + if (fd < 0) + { + error (0, errno, _("cannot create temporary file %s"), quote (tempfile)); + return false; + } + + tmp = fdopen (fd, (O_BINARY ? "w+b" : "w+")); + if (! tmp) + { + error (0, errno, _("cannot open %s for writing"), quote (tempfile)); + close (fd); + unlink (tempfile); + return false; + } + + record_or_unlink_tempfile (tempfile, tmp); + + while (1) + { + size_t bytes_read = safe_read (input_fd, G_buffer, read_size); + if (bytes_read == 0) + break; + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("%s: read error"), quotearg_colon (file)); + goto Fail; + } + + if (fwrite (G_buffer, 1, bytes_read, tmp) != bytes_read) + { + error (0, errno, _("%s: write error"), quotearg_colon (tempfile)); + goto Fail; + } + } + + if (fflush (tmp) != 0) + { + error (0, errno, _("%s: write error"), quotearg_colon (tempfile)); + goto Fail; + } + + *g_tmp = tmp; + *g_tempfile = tempfile; + return true; + + Fail: + fclose (tmp); + return false; +} + +/* Copy INPUT_FD to a temporary, then tac that file. + Return true if successful. */ + +static bool +tac_nonseekable (int input_fd, const char *file) +{ + FILE *tmp_stream; + char *tmp_file; + return (copy_to_temp (&tmp_stream, &tmp_file, input_fd, file) + && tac_seekable (fileno (tmp_stream), tmp_file)); +} + +/* Print FILE in reverse, copying it to a temporary + file first if it is not seekable. + Return true if successful. */ + +static bool +tac_file (const char *filename) +{ + bool ok; + off_t file_size; + int fd; + bool is_stdin = STREQ (filename, "-"); + + if (is_stdin) + { + have_read_stdin = true; + fd = STDIN_FILENO; + filename = _("standard input"); + if (O_BINARY && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + } + else + { + fd = open (filename, O_RDONLY | O_BINARY); + if (fd < 0) + { + error (0, errno, _("cannot open %s for reading"), quote (filename)); + return false; + } + } + + file_size = lseek (fd, (off_t) 0, SEEK_END); + + ok = (file_size < 0 || isatty (fd) + ? tac_nonseekable (fd, filename) + : tac_seekable (fd, filename)); + + if (!is_stdin && close (fd) != 0) + { + error (0, errno, _("%s: read error"), quotearg_colon (filename)); + ok = false; + } + return ok; +} + +int +main (int argc, char **argv) +{ + const char *error_message; /* Return value from re_compile_pattern. */ + int optc; + bool ok; + size_t half_buffer_size; + + /* Initializer for file_list if no file-arguments + were specified on the command line. */ + static char const *const default_file_list[] = {"-", NULL}; + char const *const *file; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + separator = "\n"; + sentinel_length = 1; + separator_ends_record = true; + + while ((optc = getopt_long (argc, argv, "brs:", longopts, NULL)) != -1) + { + switch (optc) + { + case 'b': + separator_ends_record = false; + break; + case 'r': + sentinel_length = 0; + break; + case 's': + separator = optarg; + if (*separator == 0) + error (EXIT_FAILURE, 0, _("separator cannot be empty")); + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + if (sentinel_length == 0) + { + compiled_separator.buffer = NULL; + compiled_separator.allocated = 0; + compiled_separator.fastmap = compiled_separator_fastmap; + compiled_separator.translate = NULL; + error_message = re_compile_pattern (separator, strlen (separator), + &compiled_separator); + if (error_message) + error (EXIT_FAILURE, 0, "%s", error_message); + } + else + match_length = sentinel_length = strlen (separator); + + read_size = INITIAL_READSIZE; + while (sentinel_length >= read_size / 2) + { + if (SIZE_MAX / 2 < read_size) + xalloc_die (); + read_size *= 2; + } + half_buffer_size = read_size + sentinel_length + 1; + G_buffer_size = 2 * half_buffer_size; + if (! (read_size < half_buffer_size && half_buffer_size < G_buffer_size)) + xalloc_die (); + G_buffer = xmalloc (G_buffer_size); + if (sentinel_length) + { + strcpy (G_buffer, separator); + G_buffer += sentinel_length; + } + else + { + ++G_buffer; + } + + file = (optind < argc + ? (char const *const *) &argv[optind] + : default_file_list); + + if (O_BINARY && ! isatty (STDOUT_FILENO)) + freopen (NULL, "wb", stdout); + + { + size_t i; + ok = true; + for (i = 0; file[i]; ++i) + ok &= tac_file (file[i]); + } + + /* Flush the output buffer. */ + output ((char *) NULL, (char *) NULL); + + if (have_read_stdin && close (STDIN_FILENO) < 0) + error (EXIT_FAILURE, errno, "-"); + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/tail.c b/src/tail.c new file mode 100644 index 0000000..2582b9d --- /dev/null +++ b/src/tail.c @@ -0,0 +1,1697 @@ +/* tail -- output the last part of file(s) + Copyright (C) 1989, 90, 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Can display any amount of data, unlike the Unix version, which uses + a fixed size buffer and therefore can only deliver a limited number + of lines. + + Original version by Paul Rubin . + Extensions by David MacKenzie . + tail -f for multiple files by Ian Lance Taylor . */ + +#include + +#include +#include +#include +#include +#include + +#include "system.h" +#include "argmatch.h" +#include "c-strtod.h" +#include "error.h" +#include "fcntl--.h" +#include "inttostr.h" +#include "isapipe.h" +#include "posixver.h" +#include "quote.h" +#include "safe-read.h" +#include "stat-time.h" +#include "xnanosleep.h" +#include "xstrtol.h" +#include "xstrtod.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "tail" + +#define AUTHORS \ + "Paul Rubin", "David MacKenzie, Ian Lance Taylor", "Jim Meyering" + +/* Number of items to tail. */ +#define DEFAULT_N_LINES 10 + +/* Special values for dump_remainder's N_BYTES parameter. */ +#define COPY_TO_EOF UINTMAX_MAX +#define COPY_A_BUFFER (UINTMAX_MAX - 1) + +/* FIXME: make Follow_name the default? */ +#define DEFAULT_FOLLOW_MODE Follow_descriptor + +enum Follow_mode +{ + /* Follow the name of each file: if the file is renamed, try to reopen + that name and track the end of the new file if/when it's recreated. + This is useful for tracking logs that are occasionally rotated. */ + Follow_name = 1, + + /* Follow each descriptor obtained upon opening a file. + That means we'll continue to follow the end of a file even after + it has been renamed or unlinked. */ + Follow_descriptor = 2 +}; + +/* The types of files for which tail works. */ +#define IS_TAILABLE_FILE_TYPE(Mode) \ + (S_ISREG (Mode) || S_ISFIFO (Mode) || S_ISSOCK (Mode) || S_ISCHR (Mode)) + +static char const *const follow_mode_string[] = +{ + "descriptor", "name", NULL +}; + +static enum Follow_mode const follow_mode_map[] = +{ + Follow_descriptor, Follow_name, +}; + +struct File_spec +{ + /* The actual file name, or "-" for stdin. */ + char *name; + + /* File descriptor on which the file is open; -1 if it's not open. */ + int fd; + + /* Attributes of the file the last time we checked. */ + off_t size; + struct timespec mtime; + dev_t dev; + ino_t ino; + mode_t mode; + + /* 1 if O_NONBLOCK is clear, 0 if set, -1 if not known. */ + int blocking; + + /* The specified name initially referred to a directory or some other + type for which tail isn't meaningful. Unlike for a permission problem + (tailable, below) once this is set, the name is not checked ever again. */ + bool ignore; + + /* See description of DEFAULT_MAX_N_... below. */ + uintmax_t n_unchanged_stats; + + /* A file is tailable if it exists, is readable, and is of type + IS_TAILABLE_FILE_TYPE. */ + bool tailable; + + /* The value of errno seen last time we checked this file. */ + int errnum; + +}; + +/* Keep trying to open a file even if it is inaccessible when tail starts + or if it becomes inaccessible later -- useful only with -f. */ +static bool reopen_inaccessible_files; + +/* If true, interpret the numeric argument as the number of lines. + Otherwise, interpret it as the number of bytes. */ +static bool count_lines; + +/* Whether we follow the name of each file or the file descriptor + that is initially associated with each name. */ +static enum Follow_mode follow_mode = Follow_descriptor; + +/* If true, read from the ends of all specified files until killed. */ +static bool forever; + +/* If true, count from start of file instead of end. */ +static bool from_start; + +/* If true, print filename headers. */ +static bool print_headers; + +/* When to print the filename banners. */ +enum header_mode +{ + multiple_files, always, never +}; + +/* When tailing a file by name, if there have been this many consecutive + iterations for which the file has not changed, then open/fstat + the file to determine if that file name is still associated with the + same device/inode-number pair as before. This option is meaningful only + when following by name. --max-unchanged-stats=N */ +#define DEFAULT_MAX_N_UNCHANGED_STATS_BETWEEN_OPENS 5 +static uintmax_t max_n_unchanged_stats_between_opens = + DEFAULT_MAX_N_UNCHANGED_STATS_BETWEEN_OPENS; + +/* The name this program was run with. */ +char *program_name; + +/* The process ID of the process (presumably on the current host) + that is writing to all followed files. */ +static pid_t pid; + +/* True if we have ever read standard input. */ +static bool have_read_stdin; + +/* If nonzero, skip the is-regular-file test used to determine whether + to use the lseek optimization. Instead, use the more general (and + more expensive) code unconditionally. Intended solely for testing. */ +static bool presume_input_pipe; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + RETRY_OPTION = CHAR_MAX + 1, + MAX_UNCHANGED_STATS_OPTION, + PID_OPTION, + PRESUME_INPUT_PIPE_OPTION, + LONG_FOLLOW_OPTION +}; + +static struct option const long_options[] = +{ + {"bytes", required_argument, NULL, 'c'}, + {"follow", optional_argument, NULL, LONG_FOLLOW_OPTION}, + {"lines", required_argument, NULL, 'n'}, + {"max-unchanged-stats", required_argument, NULL, MAX_UNCHANGED_STATS_OPTION}, + {"pid", required_argument, NULL, PID_OPTION}, + {"-presume-input-pipe", no_argument, NULL, + PRESUME_INPUT_PIPE_OPTION}, /* do not document */ + {"quiet", no_argument, NULL, 'q'}, + {"retry", no_argument, NULL, RETRY_OPTION}, + {"silent", no_argument, NULL, 'q'}, + {"sleep-interval", required_argument, NULL, 's'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + printf (_("\ +Print the last %d lines of each FILE to standard output.\n\ +With more than one FILE, precede each with a header giving the file name.\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +"), DEFAULT_N_LINES); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + --retry keep trying to open a file even if it is\n\ + inaccessible when tail starts or if it becomes\n\ + inaccessible later; useful when following by name,\n\ + i.e., with --follow=name\n\ + -c, --bytes=N output the last N bytes; alternatively, use +N to\n\ + output bytes starting with the Nth of each file\n\ +"), stdout); + fputs (_("\ + -f, --follow[={name|descriptor}]\n\ + output appended data as the file grows;\n\ + -f, --follow, and --follow=descriptor are\n\ + equivalent\n\ + -F same as --follow=name --retry\n\ +"), stdout); + printf (_("\ + -n, --lines=N output the last N lines, instead of the last %d;\n\ + or use +N to output lines starting with the Nth\n\ + --max-unchanged-stats=N\n\ + with --follow=name, reopen a FILE which has not\n\ + changed size after N (default %d) iterations\n\ + to see if it has been unlinked or renamed\n\ + (this is the usual case of rotated log files)\n\ +"), + DEFAULT_N_LINES, + DEFAULT_MAX_N_UNCHANGED_STATS_BETWEEN_OPENS + ); + fputs (_("\ + --pid=PID with -f, terminate after process ID, PID dies\n\ + -q, --quiet, --silent never output headers giving file names\n\ + -s, --sleep-interval=S with -f, sleep for approximately S seconds\n\ + (default 1.0) between iterations.\n\ + -v, --verbose always output headers giving file names\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +If the first character of N (the number of bytes or lines) is a `+',\n\ +print beginning with the Nth item from the start of each file, otherwise,\n\ +print the last N items in the file. N may have a multiplier suffix:\n\ +b 512, k 1024, m 1024*1024.\n\ +\n\ +"), stdout); + fputs (_("\ +With --follow (-f), tail defaults to following the file descriptor, which\n\ +means that even if a tail'ed file is renamed, tail will continue to track\n\ +its end. \ +"), stdout); + fputs (_("\ +This default behavior is not desirable when you really want to\n\ +track the actual name of the file, not the file descriptor (e.g., log\n\ +rotation). Use --follow=name in that case. That causes tail to track the\n\ +named file by reopening it periodically to see if it has been removed and\n\ +recreated by some other program.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +static bool +valid_file_spec (struct File_spec const *f) +{ + /* Exactly one of the following subexpressions must be true. */ + return ((f->fd == -1) ^ (f->errnum == 0)); +} + +static char const * +pretty_name (struct File_spec const *f) +{ + return (STREQ (f->name, "-") ? "standard input" : f->name); +} + +static void +xwrite_stdout (char const *buffer, size_t n_bytes) +{ + if (n_bytes > 0 && fwrite (buffer, 1, n_bytes, stdout) == 0) + error (EXIT_FAILURE, errno, _("write error")); +} + +/* Record a file F with descriptor FD, size SIZE, status ST, and + blocking status BLOCKING. */ + +static void +record_open_fd (struct File_spec *f, int fd, + off_t size, struct stat const *st, + int blocking) +{ + f->fd = fd; + f->size = size; + f->mtime = get_stat_mtime (st); + f->dev = st->st_dev; + f->ino = st->st_ino; + f->mode = st->st_mode; + f->blocking = blocking; + f->n_unchanged_stats = 0; + f->ignore = 0; +} + +/* Close the file with descriptor FD and name FILENAME. */ + +static void +close_fd (int fd, const char *filename) +{ + if (fd != -1 && fd != STDIN_FILENO && close (fd)) + { + error (0, errno, _("closing %s (fd=%d)"), filename, fd); + } +} + +static void +write_header (const char *pretty_filename) +{ + static bool first_file = true; + + printf ("%s==> %s <==\n", (first_file ? "" : "\n"), pretty_filename); + first_file = false; +} + +/* Read and output N_BYTES of file PRETTY_FILENAME starting at the current + position in FD. If N_BYTES is COPY_TO_EOF, then copy until end of file. + If N_BYTES is COPY_A_BUFFER, then copy at most one buffer's worth. + Return the number of bytes read from the file. */ + +static uintmax_t +dump_remainder (const char *pretty_filename, int fd, uintmax_t n_bytes) +{ + uintmax_t n_written; + uintmax_t n_remaining = n_bytes; + + n_written = 0; + while (1) + { + char buffer[BUFSIZ]; + size_t n = MIN (n_remaining, BUFSIZ); + size_t bytes_read = safe_read (fd, buffer, n); + if (bytes_read == SAFE_READ_ERROR) + { + if (errno != EAGAIN) + error (EXIT_FAILURE, errno, _("error reading %s"), + quote (pretty_filename)); + break; + } + if (bytes_read == 0) + break; + xwrite_stdout (buffer, bytes_read); + n_written += bytes_read; + if (n_bytes != COPY_TO_EOF) + { + n_remaining -= bytes_read; + if (n_remaining == 0 || n_bytes == COPY_A_BUFFER) + break; + } + } + + return n_written; +} + +/* Call lseek with the specified arguments, where file descriptor FD + corresponds to the file, FILENAME. + Give a diagnostic and exit nonzero if lseek fails. + Otherwise, return the resulting offset. */ + +static off_t +xlseek (int fd, off_t offset, int whence, char const *filename) +{ + off_t new_offset = lseek (fd, offset, whence); + char buf[INT_BUFSIZE_BOUND (off_t)]; + char *s; + + if (0 <= new_offset) + return new_offset; + + s = offtostr (offset, buf); + switch (whence) + { + case SEEK_SET: + error (0, errno, _("%s: cannot seek to offset %s"), + filename, s); + break; + case SEEK_CUR: + error (0, errno, _("%s: cannot seek to relative offset %s"), + filename, s); + break; + case SEEK_END: + error (0, errno, _("%s: cannot seek to end-relative offset %s"), + filename, s); + break; + default: + abort (); + } + + exit (EXIT_FAILURE); +} + +/* Print the last N_LINES lines from the end of file FD. + Go backward through the file, reading `BUFSIZ' bytes at a time (except + probably the first), until we hit the start of the file or have + read NUMBER newlines. + START_POS is the starting position of the read pointer for the file + associated with FD (may be nonzero). + END_POS is the file offset of EOF (one larger than offset of last byte). + Return true if successful. */ + +static bool +file_lines (const char *pretty_filename, int fd, uintmax_t n_lines, + off_t start_pos, off_t end_pos, uintmax_t *read_pos) +{ + char buffer[BUFSIZ]; + size_t bytes_read; + off_t pos = end_pos; + + if (n_lines == 0) + return true; + + /* Set `bytes_read' to the size of the last, probably partial, buffer; + 0 < `bytes_read' <= `BUFSIZ'. */ + bytes_read = (pos - start_pos) % BUFSIZ; + if (bytes_read == 0) + bytes_read = BUFSIZ; + /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that all + reads will be on block boundaries, which might increase efficiency. */ + pos -= bytes_read; + xlseek (fd, pos, SEEK_SET, pretty_filename); + bytes_read = safe_read (fd, buffer, bytes_read); + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (pretty_filename)); + return false; + } + *read_pos = pos + bytes_read; + + /* Count the incomplete line on files that don't end with a newline. */ + if (bytes_read && buffer[bytes_read - 1] != '\n') + --n_lines; + + do + { + /* Scan backward, counting the newlines in this bufferfull. */ + + size_t n = bytes_read; + while (n) + { + char const *nl; + nl = memrchr (buffer, '\n', n); + if (nl == NULL) + break; + n = nl - buffer; + if (n_lines-- == 0) + { + /* If this newline isn't the last character in the buffer, + output the part that is after it. */ + if (n != bytes_read - 1) + xwrite_stdout (nl + 1, bytes_read - (n + 1)); + *read_pos += dump_remainder (pretty_filename, fd, + end_pos - (pos + bytes_read)); + return true; + } + } + + /* Not enough newlines in that bufferfull. */ + if (pos == start_pos) + { + /* Not enough lines in the file; print everything from + start_pos to the end. */ + xlseek (fd, start_pos, SEEK_SET, pretty_filename); + *read_pos = start_pos + dump_remainder (pretty_filename, fd, + end_pos); + return true; + } + pos -= BUFSIZ; + xlseek (fd, pos, SEEK_SET, pretty_filename); + + bytes_read = safe_read (fd, buffer, BUFSIZ); + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (pretty_filename)); + return false; + } + + *read_pos = pos + bytes_read; + } + while (bytes_read > 0); + + return true; +} + +/* Print the last N_LINES lines from the end of the standard input, + open for reading as pipe FD. + Buffer the text as a linked list of LBUFFERs, adding them as needed. + Return true if successful. */ + +static bool +pipe_lines (const char *pretty_filename, int fd, uintmax_t n_lines, + uintmax_t *read_pos) +{ + struct linebuffer + { + char buffer[BUFSIZ]; + size_t nbytes; + size_t nlines; + struct linebuffer *next; + }; + typedef struct linebuffer LBUFFER; + LBUFFER *first, *last, *tmp; + size_t total_lines = 0; /* Total number of newlines in all buffers. */ + bool ok = true; + size_t n_read; /* Size in bytes of most recent read */ + + first = last = xmalloc (sizeof (LBUFFER)); + first->nbytes = first->nlines = 0; + first->next = NULL; + tmp = xmalloc (sizeof (LBUFFER)); + + /* Input is always read into a fresh buffer. */ + while (1) + { + n_read = safe_read (fd, tmp->buffer, BUFSIZ); + if (n_read == 0 || n_read == SAFE_READ_ERROR) + break; + tmp->nbytes = n_read; + *read_pos += n_read; + tmp->nlines = 0; + tmp->next = NULL; + + /* Count the number of newlines just read. */ + { + char const *buffer_end = tmp->buffer + n_read; + char const *p = tmp->buffer; + while ((p = memchr (p, '\n', buffer_end - p))) + { + ++p; + ++tmp->nlines; + } + } + total_lines += tmp->nlines; + + /* If there is enough room in the last buffer read, just append the new + one to it. This is because when reading from a pipe, `n_read' can + often be very small. */ + if (tmp->nbytes + last->nbytes < BUFSIZ) + { + memcpy (&last->buffer[last->nbytes], tmp->buffer, tmp->nbytes); + last->nbytes += tmp->nbytes; + last->nlines += tmp->nlines; + } + else + { + /* If there's not enough room, link the new buffer onto the end of + the list, then either free up the oldest buffer for the next + read if that would leave enough lines, or else malloc a new one. + Some compaction mechanism is possible but probably not + worthwhile. */ + last = last->next = tmp; + if (total_lines - first->nlines > n_lines) + { + tmp = first; + total_lines -= first->nlines; + first = first->next; + } + else + tmp = xmalloc (sizeof (LBUFFER)); + } + } + + free (tmp); + + if (n_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (pretty_filename)); + ok = false; + goto free_lbuffers; + } + + /* If the file is empty, then bail out. */ + if (last->nbytes == 0) + goto free_lbuffers; + + /* This prevents a core dump when the pipe contains no newlines. */ + if (n_lines == 0) + goto free_lbuffers; + + /* Count the incomplete line on files that don't end with a newline. */ + if (last->buffer[last->nbytes - 1] != '\n') + { + ++last->nlines; + ++total_lines; + } + + /* Run through the list, printing lines. First, skip over unneeded + buffers. */ + for (tmp = first; total_lines - tmp->nlines > n_lines; tmp = tmp->next) + total_lines -= tmp->nlines; + + /* Find the correct beginning, then print the rest of the file. */ + { + char const *beg = tmp->buffer; + char const *buffer_end = tmp->buffer + tmp->nbytes; + if (total_lines > n_lines) + { + /* Skip `total_lines' - `n_lines' newlines. We made sure that + `total_lines' - `n_lines' <= `tmp->nlines'. */ + size_t j; + for (j = total_lines - n_lines; j; --j) + { + beg = memchr (beg, '\n', buffer_end - beg); + assert (beg); + ++beg; + } + } + + xwrite_stdout (beg, buffer_end - beg); + } + + for (tmp = tmp->next; tmp; tmp = tmp->next) + xwrite_stdout (tmp->buffer, tmp->nbytes); + +free_lbuffers: + while (first) + { + tmp = first->next; + free (first); + first = tmp; + } + return ok; +} + +/* Print the last N_BYTES characters from the end of pipe FD. + This is a stripped down version of pipe_lines. + Return true if successful. */ + +static bool +pipe_bytes (const char *pretty_filename, int fd, uintmax_t n_bytes, + uintmax_t *read_pos) +{ + struct charbuffer + { + char buffer[BUFSIZ]; + size_t nbytes; + struct charbuffer *next; + }; + typedef struct charbuffer CBUFFER; + CBUFFER *first, *last, *tmp; + size_t i; /* Index into buffers. */ + size_t total_bytes = 0; /* Total characters in all buffers. */ + bool ok = true; + size_t n_read; + + first = last = xmalloc (sizeof (CBUFFER)); + first->nbytes = 0; + first->next = NULL; + tmp = xmalloc (sizeof (CBUFFER)); + + /* Input is always read into a fresh buffer. */ + while (1) + { + n_read = safe_read (fd, tmp->buffer, BUFSIZ); + if (n_read == 0 || n_read == SAFE_READ_ERROR) + break; + *read_pos += n_read; + tmp->nbytes = n_read; + tmp->next = NULL; + + total_bytes += tmp->nbytes; + /* If there is enough room in the last buffer read, just append the new + one to it. This is because when reading from a pipe, `nbytes' can + often be very small. */ + if (tmp->nbytes + last->nbytes < BUFSIZ) + { + memcpy (&last->buffer[last->nbytes], tmp->buffer, tmp->nbytes); + last->nbytes += tmp->nbytes; + } + else + { + /* If there's not enough room, link the new buffer onto the end of + the list, then either free up the oldest buffer for the next + read if that would leave enough characters, or else malloc a new + one. Some compaction mechanism is possible but probably not + worthwhile. */ + last = last->next = tmp; + if (total_bytes - first->nbytes > n_bytes) + { + tmp = first; + total_bytes -= first->nbytes; + first = first->next; + } + else + { + tmp = xmalloc (sizeof (CBUFFER)); + } + } + } + + free (tmp); + + if (n_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (pretty_filename)); + ok = false; + goto free_cbuffers; + } + + /* Run through the list, printing characters. First, skip over unneeded + buffers. */ + for (tmp = first; total_bytes - tmp->nbytes > n_bytes; tmp = tmp->next) + total_bytes -= tmp->nbytes; + + /* Find the correct beginning, then print the rest of the file. + We made sure that `total_bytes' - `n_bytes' <= `tmp->nbytes'. */ + if (total_bytes > n_bytes) + i = total_bytes - n_bytes; + else + i = 0; + xwrite_stdout (&tmp->buffer[i], tmp->nbytes - i); + + for (tmp = tmp->next; tmp; tmp = tmp->next) + xwrite_stdout (tmp->buffer, tmp->nbytes); + +free_cbuffers: + while (first) + { + tmp = first->next; + free (first); + first = tmp; + } + return ok; +} + +/* Skip N_BYTES characters from the start of pipe FD, and print + any extra characters that were read beyond that. + Return 1 on error, 0 if ok, -1 if EOF. */ + +static int +start_bytes (const char *pretty_filename, int fd, uintmax_t n_bytes, + uintmax_t *read_pos) +{ + char buffer[BUFSIZ]; + + while (0 < n_bytes) + { + size_t bytes_read = safe_read (fd, buffer, BUFSIZ); + if (bytes_read == 0) + return -1; + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, _("error reading %s"), quote (pretty_filename)); + return 1; + } + read_pos += bytes_read; + if (bytes_read <= n_bytes) + n_bytes -= bytes_read; + else + { + size_t n_remaining = bytes_read - n_bytes; + if (n_remaining) + xwrite_stdout (&buffer[n_bytes], n_remaining); + break; + } + } + + return 0; +} + +/* Skip N_LINES lines at the start of file or pipe FD, and print + any extra characters that were read beyond that. + Return 1 on error, 0 if ok, -1 if EOF. */ + +static int +start_lines (const char *pretty_filename, int fd, uintmax_t n_lines, + uintmax_t *read_pos) +{ + if (n_lines == 0) + return 0; + + while (1) + { + char buffer[BUFSIZ]; + char *p = buffer; + size_t bytes_read = safe_read (fd, buffer, BUFSIZ); + char *buffer_end = buffer + bytes_read; + if (bytes_read == 0) /* EOF */ + return -1; + if (bytes_read == SAFE_READ_ERROR) /* error */ + { + error (0, errno, _("error reading %s"), quote (pretty_filename)); + return 1; + } + + *read_pos += bytes_read; + + while ((p = memchr (p, '\n', buffer_end - p))) + { + ++p; + if (--n_lines == 0) + { + if (p < buffer_end) + xwrite_stdout (p, buffer_end - p); + return 0; + } + } + } +} + +/* FIXME: describe */ + +static void +recheck (struct File_spec *f, bool blocking) +{ + /* open/fstat the file and announce if dev/ino have changed */ + struct stat new_stats; + bool ok = true; + bool is_stdin = (STREQ (f->name, "-")); + bool was_tailable = f->tailable; + int prev_errnum = f->errnum; + bool new_file; + int fd = (is_stdin + ? STDIN_FILENO + : open (f->name, O_RDONLY | (blocking ? 0 : O_NONBLOCK))); + + assert (valid_file_spec (f)); + + /* If the open fails because the file doesn't exist, + then mark the file as not tailable. */ + f->tailable = !(reopen_inaccessible_files && fd == -1); + + if (fd == -1 || fstat (fd, &new_stats) < 0) + { + ok = false; + f->errnum = errno; + if (!f->tailable) + { + if (was_tailable) + { + /* FIXME-maybe: detect the case in which the file first becomes + unreadable (perms), and later becomes readable again and can + be seen to be the same file (dev/ino). Otherwise, tail prints + the entire contents of the file when it becomes readable. */ + error (0, f->errnum, _("%s has become inaccessible"), + quote (pretty_name (f))); + } + else + { + /* say nothing... it's still not tailable */ + } + } + else if (prev_errnum != errno) + { + error (0, errno, "%s", pretty_name (f)); + } + } + else if (!IS_TAILABLE_FILE_TYPE (new_stats.st_mode)) + { + ok = false; + f->errnum = -1; + error (0, 0, _("%s has been replaced with an untailable file;\ + giving up on this name"), + quote (pretty_name (f))); + f->ignore = true; + } + else + { + f->errnum = 0; + } + + new_file = false; + if (!ok) + { + close_fd (fd, pretty_name (f)); + close_fd (f->fd, pretty_name (f)); + f->fd = -1; + } + else if (prev_errnum && prev_errnum != ENOENT) + { + new_file = true; + assert (f->fd == -1); + error (0, 0, _("%s has become accessible"), quote (pretty_name (f))); + } + else if (f->ino != new_stats.st_ino || f->dev != new_stats.st_dev) + { + new_file = true; + if (f->fd == -1) + { + error (0, 0, + _("%s has appeared; following end of new file"), + quote (pretty_name (f))); + } + else + { + /* Close the old one. */ + close_fd (f->fd, pretty_name (f)); + + /* File has been replaced (e.g., via log rotation) -- + tail the new one. */ + error (0, 0, + _("%s has been replaced; following end of new file"), + quote (pretty_name (f))); + } + } + else + { + if (f->fd == -1) + { + /* This happens when one iteration finds the file missing, + then the preceding pair is reused as the + file is recreated. */ + new_file = true; + } + else + { + close_fd (fd, pretty_name (f)); + } + } + + if (new_file) + { + /* Start at the beginning of the file. */ + record_open_fd (f, fd, 0, &new_stats, (is_stdin ? -1 : blocking)); + xlseek (fd, 0, SEEK_SET, pretty_name (f)); + } +} + +/* Return true if any of the N_FILES files in F are live, i.e., have + open file descriptors. */ + +static bool +any_live_files (const struct File_spec *f, int n_files) +{ + int i; + + for (i = 0; i < n_files; i++) + if (0 <= f[i].fd) + return true; + return false; +} + +/* Tail NFILES files forever, or until killed. + The pertinent information for each file is stored in an entry of F. + Loop over each of them, doing an fstat to see if they have changed size, + and an occasional open/fstat to see if any dev/ino pair has changed. + If none of them have changed size in one iteration, sleep for a + while and try again. Continue until the user interrupts us. */ + +static void +tail_forever (struct File_spec *f, int nfiles, double sleep_interval) +{ + /* Use blocking I/O as an optimization, when it's easy. */ + bool blocking = (pid == 0 && follow_mode == Follow_descriptor + && nfiles == 1 && ! S_ISREG (f[0].mode)); + int last; + bool writer_is_dead = false; + + last = nfiles - 1; + + while (1) + { + int i; + bool any_input = false; + + for (i = 0; i < nfiles; i++) + { + int fd; + char const *name; + mode_t mode; + struct stat stats; + uintmax_t bytes_read; + + if (f[i].ignore) + continue; + + if (f[i].fd < 0) + { + recheck (&f[i], blocking); + continue; + } + + fd = f[i].fd; + name = pretty_name (&f[i]); + mode = f[i].mode; + + if (f[i].blocking != blocking) + { + int old_flags = fcntl (fd, F_GETFL); + int new_flags = old_flags | (blocking ? 0 : O_NONBLOCK); + if (old_flags < 0 + || (new_flags != old_flags + && fcntl (fd, F_SETFL, new_flags) == -1)) + { + /* Don't update f[i].blocking if fcntl fails. */ + if (S_ISREG (f[i].mode) && errno == EPERM) + { + /* This happens when using tail -f on a file with + the append-only attribute. */ + } + else + error (EXIT_FAILURE, errno, + _("%s: cannot change nonblocking mode"), name); + } + else + f[i].blocking = blocking; + } + + if (!f[i].blocking) + { + if (fstat (fd, &stats) != 0) + { + f[i].fd = -1; + f[i].errnum = errno; + error (0, errno, "%s", name); + continue; + } + + if (f[i].mode == stats.st_mode + && (! S_ISREG (stats.st_mode) || f[i].size == stats.st_size) + && timespec_cmp (f[i].mtime, get_stat_mtime (&stats)) == 0) + { + if ((max_n_unchanged_stats_between_opens + <= f[i].n_unchanged_stats++) + && follow_mode == Follow_name) + { + recheck (&f[i], f[i].blocking); + f[i].n_unchanged_stats = 0; + } + continue; + } + + /* This file has changed. Print out what we can, and + then keep looping. */ + + f[i].mtime = get_stat_mtime (&stats); + f[i].mode = stats.st_mode; + + /* reset counter */ + f[i].n_unchanged_stats = 0; + + if (S_ISREG (mode) && stats.st_size < f[i].size) + { + error (0, 0, _("%s: file truncated"), name); + last = i; + xlseek (fd, stats.st_size, SEEK_SET, name); + f[i].size = stats.st_size; + continue; + } + + if (i != last) + { + if (print_headers) + write_header (name); + last = i; + } + } + + bytes_read = dump_remainder (name, fd, + (f[i].blocking + ? COPY_A_BUFFER : COPY_TO_EOF)); + any_input |= (bytes_read != 0); + f[i].size += bytes_read; + } + + if (! any_live_files (f, nfiles) && ! reopen_inaccessible_files) + { + error (0, 0, _("no files remaining")); + break; + } + + if ((!any_input | blocking) && fflush (stdout) != 0) + error (EXIT_FAILURE, errno, _("write error")); + + /* If nothing was read, sleep and/or check for dead writers. */ + if (!any_input) + { + if (writer_is_dead) + break; + + if (xnanosleep (sleep_interval)) + error (EXIT_FAILURE, errno, _("cannot read realtime clock")); + + /* Once the writer is dead, read the files once more to + avoid a race condition. */ + writer_is_dead = (pid != 0 + && kill (pid, 0) != 0 + /* Handle the case in which you cannot send a + signal to the writer, so kill fails and sets + errno to EPERM. */ + && errno != EPERM); + } + } +} + +/* Output the last N_BYTES bytes of file FILENAME open for reading in FD. + Return true if successful. */ + +static bool +tail_bytes (const char *pretty_filename, int fd, uintmax_t n_bytes, + uintmax_t *read_pos) +{ + struct stat stats; + + if (fstat (fd, &stats)) + { + error (0, errno, _("cannot fstat %s"), quote (pretty_filename)); + return false; + } + + if (from_start) + { + if ( ! presume_input_pipe + && S_ISREG (stats.st_mode) && n_bytes <= OFF_T_MAX) + { + xlseek (fd, n_bytes, SEEK_CUR, pretty_filename); + *read_pos += n_bytes; + } + else + { + int t = start_bytes (pretty_filename, fd, n_bytes, read_pos); + if (t) + return t < 0; + } + *read_pos += dump_remainder (pretty_filename, fd, COPY_TO_EOF); + } + else + { + if ( ! presume_input_pipe + && S_ISREG (stats.st_mode) && n_bytes <= OFF_T_MAX) + { + off_t current_pos = xlseek (fd, 0, SEEK_CUR, pretty_filename); + off_t end_pos = xlseek (fd, 0, SEEK_END, pretty_filename); + off_t diff = end_pos - current_pos; + /* Be careful here. The current position may actually be + beyond the end of the file. */ + off_t bytes_remaining = (diff = end_pos - current_pos) < 0 ? 0 : diff; + off_t nb = n_bytes; + + if (bytes_remaining <= nb) + { + /* From the current position to end of file, there are no + more bytes than have been requested. So reposition the + file pointer to the incoming current position and print + everything after that. */ + *read_pos = xlseek (fd, current_pos, SEEK_SET, pretty_filename); + } + else + { + /* There are more bytes remaining than were requested. + Back up. */ + *read_pos = xlseek (fd, -nb, SEEK_END, pretty_filename); + } + *read_pos += dump_remainder (pretty_filename, fd, n_bytes); + } + else + return pipe_bytes (pretty_filename, fd, n_bytes, read_pos); + } + return true; +} + +/* Output the last N_LINES lines of file FILENAME open for reading in FD. + Return true if successful. */ + +static bool +tail_lines (const char *pretty_filename, int fd, uintmax_t n_lines, + uintmax_t *read_pos) +{ + struct stat stats; + + if (fstat (fd, &stats)) + { + error (0, errno, _("cannot fstat %s"), quote (pretty_filename)); + return false; + } + + if (from_start) + { + int t = start_lines (pretty_filename, fd, n_lines, read_pos); + if (t) + return t < 0; + *read_pos += dump_remainder (pretty_filename, fd, COPY_TO_EOF); + } + else + { + off_t start_pos = -1; + off_t end_pos; + + /* Use file_lines only if FD refers to a regular file for + which lseek (... SEEK_END) works. */ + if ( ! presume_input_pipe + && S_ISREG (stats.st_mode) + && (start_pos = lseek (fd, 0, SEEK_CUR)) != -1 + && start_pos < (end_pos = lseek (fd, 0, SEEK_END))) + { + *read_pos = end_pos; + if (end_pos != 0 + && ! file_lines (pretty_filename, fd, n_lines, + start_pos, end_pos, read_pos)) + return false; + } + else + { + /* Under very unlikely circumstances, it is possible to reach + this point after positioning the file pointer to end of file + via the `lseek (...SEEK_END)' above. In that case, reposition + the file pointer back to start_pos before calling pipe_lines. */ + if (start_pos != -1) + xlseek (fd, start_pos, SEEK_SET, pretty_filename); + + return pipe_lines (pretty_filename, fd, n_lines, read_pos); + } + } + return true; +} + +/* Display the last N_UNITS units of file FILENAME, open for reading + via FD. Set *READ_POS to the position of the input stream pointer. + *READ_POS is usually the number of bytes read and corresponds to an + offset from the beginning of a file. However, it may be larger than + OFF_T_MAX (as for an input pipe), and may also be larger than the + number of bytes read (when an input pointer is initially not at + beginning of file), and may be far greater than the number of bytes + actually read for an input file that is seekable. + Return true if successful. */ + +static bool +tail (const char *filename, int fd, uintmax_t n_units, + uintmax_t *read_pos) +{ + *read_pos = 0; + if (count_lines) + return tail_lines (filename, fd, n_units, read_pos); + else + return tail_bytes (filename, fd, n_units, read_pos); +} + +/* Display the last N_UNITS units of the file described by F. + Return true if successful. */ + +static bool +tail_file (struct File_spec *f, uintmax_t n_units) +{ + int fd; + bool ok; + + bool is_stdin = (STREQ (f->name, "-")); + + if (is_stdin) + { + have_read_stdin = true; + fd = STDIN_FILENO; + if (O_BINARY && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + } + else + fd = open (f->name, O_RDONLY | O_BINARY); + + f->tailable = !(reopen_inaccessible_files && fd == -1); + + if (fd == -1) + { + if (forever) + { + f->fd = -1; + f->errnum = errno; + f->ignore = false; + f->ino = 0; + f->dev = 0; + } + error (0, errno, _("cannot open %s for reading"), + quote (pretty_name (f))); + ok = false; + } + else + { + uintmax_t read_pos; + + if (print_headers) + write_header (pretty_name (f)); + ok = tail (pretty_name (f), fd, n_units, &read_pos); + if (forever) + { + struct stat stats; + +#if TEST_RACE_BETWEEN_FINAL_READ_AND_INITIAL_FSTAT + /* Before the tail function provided `read_pos', there was + a race condition described in the URL below. This sleep + call made the window big enough to exercise the problem. */ + sleep (1); +#endif + f->errnum = ok - 1; + if (fstat (fd, &stats) < 0) + { + ok = false; + f->errnum = errno; + error (0, errno, _("error reading %s"), quote (pretty_name (f))); + } + else if (!IS_TAILABLE_FILE_TYPE (stats.st_mode)) + { + error (0, 0, _("%s: cannot follow end of this type of file;\ + giving up on this name"), + pretty_name (f)); + ok = false; + f->errnum = -1; + f->ignore = true; + } + + if (!ok) + { + close_fd (fd, pretty_name (f)); + f->fd = -1; + } + else + { + /* Note: we must use read_pos here, not stats.st_size, + to avoid a race condition described by Ken Raeburn: + http://mail.gnu.org/archive/html/bug-textutils/2003-05/msg00007.html */ + record_open_fd (f, fd, read_pos, &stats, (is_stdin ? -1 : 1)); + } + } + else + { + if (!is_stdin && close (fd)) + { + error (0, errno, _("error reading %s"), quote (pretty_name (f))); + ok = false; + } + } + } + + return ok; +} + +/* If obsolete usage is allowed, and the command line arguments are of + the obsolete form and the option string is well-formed, set + *N_UNITS, the globals COUNT_LINES, FOREVER, and FROM_START, and + return true. If the command line arguments are obviously incorrect + (e.g., because obsolete usage is not allowed and the arguments are + incorrect for non-obsolete usage), report an error and exit. + Otherwise, return false and don't modify any parameter or global + variable. */ + +static bool +parse_obsolete_option (int argc, char * const *argv, uintmax_t *n_units) +{ + const char *p; + const char *n_string; + const char *n_string_end; + bool obsolete_usage; + int default_count = DEFAULT_N_LINES; + bool t_from_start; + bool t_count_lines = true; + bool t_forever = false; + + /* With the obsolete form, there is one option string and at most + one file argument. Watch out for "-" and "--", though. */ + if (! (argc == 2 + || (argc == 3 && ! (argv[2][0] == '-' && argv[2][1])) + || (3 <= argc && argc <= 4 && STREQ (argv[2], "--")))) + return false; + + obsolete_usage = (posix2_version () < 200112); + p = argv[1]; + + switch (*p++) + { + default: + return false; + + case '+': + /* Leading "+" is a file name in the non-obsolete form. */ + if (!obsolete_usage) + return false; + + t_from_start = true; + break; + + case '-': + /* In the non-obsolete form, "-" is standard input and "-c" + requires an option-argument. The obsolete multidigit options + are supported as a GNU extension even when conforming to + POSIX 1003.1-2001, so don't complain about them. */ + if (!obsolete_usage && !p[p[0] == 'c']) + return false; + + t_from_start = false; + break; + } + + n_string = p; + while (ISDIGIT (*p)) + p++; + n_string_end = p; + + switch (*p) + { + case 'b': default_count *= 512; /* Fall through. */ + case 'c': t_count_lines = false; /* Fall through. */ + case 'l': p++; break; + } + + if (*p == 'f') + { + t_forever = true; + ++p; + } + + if (*p) + return false; + + if (n_string == n_string_end) + *n_units = default_count; + else if ((xstrtoumax (n_string, NULL, 10, n_units, "b") + & ~LONGINT_INVALID_SUFFIX_CHAR) + != LONGINT_OK) + error (EXIT_FAILURE, 0, _("number in %s is too large"), quote (argv[1])); + + /* Set globals. */ + from_start = t_from_start; + count_lines = t_count_lines; + forever = t_forever; + + return true; +} + +static void +parse_options (int argc, char **argv, + uintmax_t *n_units, enum header_mode *header_mode, + double *sleep_interval) +{ + int c; + + while ((c = getopt_long (argc, argv, "c:n:fFqs:v0123456789", + long_options, NULL)) + != -1) + { + switch (c) + { + case 'F': + forever = true; + follow_mode = Follow_name; + reopen_inaccessible_files = true; + break; + + case 'c': + case 'n': + count_lines = (c == 'n'); + if (*optarg == '+') + from_start = true; + else if (*optarg == '-') + ++optarg; + + { + strtol_error s_err; + s_err = xstrtoumax (optarg, NULL, 10, n_units, "bkm"); + if (s_err != LONGINT_OK) + { + error (EXIT_FAILURE, 0, "%s: %s", optarg, + (c == 'n' + ? _("invalid number of lines") + : _("invalid number of bytes"))); + } + } + break; + + case 'f': + case LONG_FOLLOW_OPTION: + forever = true; + if (optarg == NULL) + follow_mode = DEFAULT_FOLLOW_MODE; + else + follow_mode = XARGMATCH ("--follow", optarg, + follow_mode_string, follow_mode_map); + break; + + case RETRY_OPTION: + reopen_inaccessible_files = true; + break; + + case MAX_UNCHANGED_STATS_OPTION: + /* --max-unchanged-stats=N */ + if (xstrtoumax (optarg, NULL, 10, + &max_n_unchanged_stats_between_opens, + "") + != LONGINT_OK) + { + error (EXIT_FAILURE, 0, + _("%s: invalid maximum number of unchanged stats between opens"), + optarg); + } + break; + + case PID_OPTION: + { + strtol_error s_err; + unsigned long int tmp_ulong; + s_err = xstrtoul (optarg, NULL, 10, &tmp_ulong, ""); + if (s_err != LONGINT_OK || tmp_ulong > PID_T_MAX) + { + error (EXIT_FAILURE, 0, _("%s: invalid PID"), optarg); + } + pid = tmp_ulong; + } + break; + + case PRESUME_INPUT_PIPE_OPTION: + presume_input_pipe = true; + break; + + case 'q': + *header_mode = never; + break; + + case 's': + { + double s; + if (! (xstrtod (optarg, NULL, &s, c_strtod) && 0 <= s)) + error (EXIT_FAILURE, 0, + _("%s: invalid number of seconds"), optarg); + *sleep_interval = s; + } + break; + + case 'v': + *header_mode = always; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + error (EXIT_FAILURE, 0, + _("option used in invalid context -- %c"), c); + + default: + usage (EXIT_FAILURE); + } + } + + if (reopen_inaccessible_files && follow_mode != Follow_name) + error (0, 0, _("warning: --retry is useful mainly when following by name")); + + if (pid && !forever) + error (0, 0, + _("warning: PID ignored; --pid=PID is useful only when following")); + else if (pid && kill (pid, 0) != 0 && errno == ENOSYS) + { + error (0, 0, _("warning: --pid=PID is not supported on this system")); + pid = 0; + } +} + +int +main (int argc, char **argv) +{ + enum header_mode header_mode = multiple_files; + bool ok = true; + /* If from_start, the number of items to skip before printing; otherwise, + the number of items at the end of the file to print. Although the type + is signed, the value is never negative. */ + uintmax_t n_units = DEFAULT_N_LINES; + int n_files; + char **file; + struct File_spec *F; + int i; + bool obsolete_option; + + /* The number of seconds to sleep between iterations. + During one iteration, every file name or descriptor is checked to + see if it has changed. */ + double sleep_interval = 1.0; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + have_read_stdin = false; + + count_lines = true; + forever = from_start = print_headers = false; + obsolete_option = parse_obsolete_option (argc, argv, &n_units); + argc -= obsolete_option; + argv += obsolete_option; + parse_options (argc, argv, &n_units, &header_mode, &sleep_interval); + + /* To start printing with item N_UNITS from the start of the file, skip + N_UNITS - 1 items. `tail -n +0' is actually meaningless, but for Unix + compatibility it's treated the same as `tail -n +1'. */ + if (from_start) + { + if (n_units) + --n_units; + } + + if (optind < argc) + { + n_files = argc - optind; + file = argv + optind; + } + else + { + static char *dummy_stdin = "-"; + n_files = 1; + file = &dummy_stdin; + + /* POSIX says that -f is ignored if no file operand is specified + and standard input is a pipe. However, the GNU coding + standards say that program behavior should not depend on + device type, because device independence is an important + principle of the system's design. + + Follow the POSIX requirement only if POSIXLY_CORRECT is set. */ + + if (forever && getenv ("POSIXLY_CORRECT")) + { + struct stat st; + int is_a_fifo_or_pipe = + (fstat (STDIN_FILENO, &st) != 0 ? -1 + : S_ISFIFO (st.st_mode) ? 1 + : HAVE_FIFO_PIPES == 1 ? 0 + : isapipe (STDIN_FILENO)); + if (is_a_fifo_or_pipe < 0) + error (EXIT_FAILURE, errno, _("standard input")); + if (is_a_fifo_or_pipe) + forever = false; + } + } + + { + bool found_hyphen = false; + + for (i = 0; i < n_files; i++) + if (STREQ (file[i], "-")) + found_hyphen = true; + + /* When following by name, there must be a name. */ + if (found_hyphen && follow_mode == Follow_name) + error (EXIT_FAILURE, 0, _("cannot follow %s by name"), quote ("-")); + + /* When following forever, warn if any file is `-'. + This is only a warning, since tail's output (before a failing seek, + and that from any non-stdin files) might still be useful. */ + if (forever && found_hyphen && isatty (STDIN_FILENO)) + error (0, 0, _("warning: following standard input" + " indefinitely is ineffective")); + } + + F = xnmalloc (n_files, sizeof *F); + for (i = 0; i < n_files; i++) + F[i].name = file[i]; + + if (header_mode == always + || (header_mode == multiple_files && n_files > 1)) + print_headers = true; + + if (O_BINARY && ! isatty (STDOUT_FILENO)) + freopen (NULL, "wb", stdout); + + for (i = 0; i < n_files; i++) + ok &= tail_file (&F[i], n_units); + + if (forever) + tail_forever (F, n_files, sleep_interval); + + if (have_read_stdin && close (STDIN_FILENO) < 0) + error (EXIT_FAILURE, errno, "-"); + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/tee.c b/src/tee.c new file mode 100644 index 0000000..d21edbc --- /dev/null +++ b/src/tee.c @@ -0,0 +1,220 @@ +/* tee - read from standard input and write to standard output and files. + Copyright (C) 85,1990-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Mike Parker, Richard M. Stallman, and David MacKenzie */ + +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "stdio--.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "tee" + +#define AUTHORS "Mike Parker", "Richard M. Stallman", "David MacKenzie" + +static bool tee_files (int nfiles, const char **files); + +/* If true, append to output files rather than truncating them. */ +static bool append; + +/* If true, ignore interrupts. */ +static bool ignore_interrupts; + +/* The name that this program was run with. */ +char *program_name; + +static struct option const long_options[] = +{ + {"append", no_argument, NULL, 'a'}, + {"ignore-interrupts", no_argument, NULL, 'i'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name); + fputs (_("\ +Copy standard input to each FILE, and also to standard output.\n\ +\n\ + -a, --append append to the given FILEs, do not overwrite\n\ + -i, --ignore-interrupts ignore interrupt signals\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +If a FILE is -, copy again to standard output.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + bool ok; + int optc; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + append = false; + ignore_interrupts = false; + + while ((optc = getopt_long (argc, argv, "ai", long_options, NULL)) != -1) + { + switch (optc) + { + case 'a': + append = true; + break; + + case 'i': + ignore_interrupts = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (ignore_interrupts) + signal (SIGINT, SIG_IGN); + + /* Do *not* warn if tee is given no file arguments. + POSIX requires that it work when given no arguments. */ + + ok = tee_files (argc - optind, (const char **) &argv[optind]); + if (close (STDIN_FILENO) != 0) + error (EXIT_FAILURE, errno, _("standard input")); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} + +/* Copy the standard input into each of the NFILES files in FILES + and into the standard output. + Return true if successful. */ + +static bool +tee_files (int nfiles, const char **files) +{ + FILE **descriptors; + char buffer[BUFSIZ]; + ssize_t bytes_read; + int i; + bool ok = true; + char const *mode_string = + (O_BINARY + ? (append ? "ab" : "wb") + : (append ? "a" : "w")); + + descriptors = xnmalloc (nfiles + 1, sizeof *descriptors); + + /* Move all the names `up' one in the argv array to make room for + the entry for standard output. This writes into argv[argc]. */ + for (i = nfiles; i >= 1; i--) + files[i] = files[i - 1]; + + if (O_BINARY && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + if (O_BINARY && ! isatty (STDOUT_FILENO)) + freopen (NULL, "wb", stdout); + + /* In the array of NFILES + 1 descriptors, make + the first one correspond to standard output. */ + descriptors[0] = stdout; + files[0] = _("standard output"); + setvbuf (stdout, NULL, _IONBF, 0); + + for (i = 1; i <= nfiles; i++) + { + descriptors[i] = (STREQ (files[i], "-") + ? stdout + : fopen (files[i], mode_string)); + if (descriptors[i] == NULL) + { + error (0, errno, "%s", files[i]); + ok = false; + } + else + setvbuf (descriptors[i], NULL, _IONBF, 0); + } + + while (1) + { + bytes_read = read (0, buffer, sizeof buffer); +#ifdef EINTR + if (bytes_read < 0 && errno == EINTR) + continue; +#endif + if (bytes_read <= 0) + break; + + /* Write to all NFILES + 1 descriptors. + Standard output is the first one. */ + for (i = 0; i <= nfiles; i++) + if (descriptors[i] + && fwrite (buffer, 1, bytes_read, descriptors[i]) != bytes_read) + { + error (0, errno, "%s", files[i]); + descriptors[i] = NULL; + ok = false; + } + } + + if (bytes_read == -1) + { + error (0, errno, _("read error")); + ok = false; + } + + /* Close the files, but not standard output. */ + for (i = 1; i <= nfiles; i++) + if (!STREQ (files[i], "-") + && descriptors[i] && fclose (descriptors[i]) != 0) + { + error (0, errno, "%s", files[i]); + ok = false; + } + + free (descriptors); + + return ok; +} diff --git a/src/test.c b/src/test.c new file mode 100644 index 0000000..b25436b --- /dev/null +++ b/src/test.c @@ -0,0 +1,849 @@ +/* GNU test program (ksb and mjb) */ + +/* Modified to run with the GNU shell by bfox. */ + +/* Copyright (C) 1987-2005 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Define TEST_STANDALONE to get the /bin/test version. Otherwise, you get + the shell builtin version. */ + +#include +#include +#include + +#define TEST_STANDALONE 1 + +#ifndef LBRACKET +# define LBRACKET 0 +#endif + +/* The official name of this program (e.g., no `g' prefix). */ +#if LBRACKET +# define PROGRAM_NAME "[" +#else +# define PROGRAM_NAME "test" +#endif + +#include "system.h" +#include "error.h" +#include "euidaccess.h" +#include "inttostr.h" +#include "quote.h" +#include "stat-time.h" +#include "strnumcmp.h" + +#if HAVE_SYS_PARAM_H +# include +#endif + +char *program_name; + +/* Exit status for syntax errors, etc. */ +enum { TEST_TRUE, TEST_FALSE, TEST_FAILURE }; + +#if defined TEST_STANDALONE +# define test_exit(val) exit (val) +#else + static jmp_buf test_exit_buf; + static int test_error_return = 0; +# define test_exit(val) test_error_return = val, longjmp (test_exit_buf, 1) +#endif /* !TEST_STANDALONE */ + +static int pos; /* The offset of the current argument in ARGV. */ +static int argc; /* The number of arguments present in ARGV. */ +static char **argv; /* The argument list. */ + +static bool test_unop (char const *s); +static bool unary_operator (void); +static bool binary_operator (bool); +static bool two_arguments (void); +static bool three_arguments (void); +static bool posixtest (int); + +static bool expr (void); +static bool term (void); +static bool and (void); +static bool or (void); + +static void test_syntax_error (char const *format, char const *arg) + ATTRIBUTE_NORETURN; +static void beyond (void) ATTRIBUTE_NORETURN; + +static void +test_syntax_error (char const *format, char const *arg) +{ + fprintf (stderr, "%s: ", argv[0]); + fprintf (stderr, format, arg); + fputc ('\n', stderr); + fflush (stderr); + test_exit (TEST_FAILURE); +} + +/* Increment our position in the argument list. Check that we're not + past the end of the argument list. This check is supressed if the + argument is false. */ + +static inline void +advance (bool f) +{ + ++pos; + + if (f && pos >= argc) + beyond (); +} + +static inline void +unary_advance (void) +{ + advance (true); + ++pos; +} + +/* + * beyond - call when we're beyond the end of the argument list (an + * error condition) + */ +static void +beyond (void) +{ + test_syntax_error (_("missing argument after %s"), quote (argv[argc - 1])); +} + +/* If the characters pointed to by STRING constitute a valid number, + return a pointer to the start of the number, skipping any blanks or + leading '+'. Otherwise, report an error and exit. */ +static char const * +find_int (char const *string) +{ + char const *p; + char const *number_start; + + for (p = string; isblank (to_uchar (*p)); p++) + continue; + + if (*p == '+') + { + p++; + number_start = p; + } + else + { + number_start = p; + p += (*p == '-'); + } + + if (ISDIGIT (*p++)) + { + while (ISDIGIT (*p)) + p++; + while (isblank (to_uchar (*p))) + p++; + if (!*p) + return number_start; + } + + test_syntax_error (_("invalid integer %s"), quote (string)); +} + +/* Find the modification time of FILE, and stuff it into *MTIME. + Return true if successful. */ +static bool +get_mtime (char const *filename, struct timespec *mtime) +{ + struct stat finfo; + bool ok = (stat (filename, &finfo) == 0); +#ifdef lint + static struct timespec const zero; + *mtime = zero; +#endif + if (ok) + *mtime = get_stat_mtime (&finfo); + return ok; +} + +/* Return true if S is one of the test command's binary operators. */ +static bool +binop (char const *s) +{ + return ((STREQ (s, "=")) || (STREQ (s, "!=")) || (STREQ (s, "-nt")) || + (STREQ (s, "-ot")) || (STREQ (s, "-ef")) || (STREQ (s, "-eq")) || + (STREQ (s, "-ne")) || (STREQ (s, "-lt")) || (STREQ (s, "-le")) || + (STREQ (s, "-gt")) || (STREQ (s, "-ge"))); +} + +/* + * term - parse a term and return 1 or 0 depending on whether the term + * evaluates to true or false, respectively. + * + * term ::= + * '-'('h'|'d'|'f'|'r'|'s'|'w'|'c'|'b'|'p'|'u'|'g'|'k') filename + * '-'('L'|'x') filename + * '-t' int + * '-'('z'|'n') string + * string + * string ('!='|'=') string + * '-'(eq|ne|le|lt|ge|gt) + * file '-'(nt|ot|ef) file + * '(' ')' + * int ::= + * '-l' string + * positive and negative integers + */ +static bool +term (void) +{ + bool value; + bool negated = false; + + /* Deal with leading `not's. */ + while (pos < argc && argv[pos][0] == '!' && argv[pos][1] == '\0') + { + advance (true); + negated = !negated; + } + + if (pos >= argc) + beyond (); + + /* A paren-bracketed argument. */ + if (argv[pos][0] == '(' && argv[pos][1] == '\0') + { + int nargs; + + advance (true); + + for (nargs = 1; + pos + nargs < argc && ! STREQ (argv[pos + nargs], ")"); + nargs++) + if (nargs == 4) + { + nargs = argc - pos; + break; + } + + value = posixtest (nargs); + if (argv[pos] == 0) + test_syntax_error (_("')' expected"), NULL); + else + if (argv[pos][0] != ')' || argv[pos][1]) + test_syntax_error (_("')' expected, found %s"), argv[pos]); + advance (false); + } + + /* Are there enough arguments left that this could be dyadic? */ + else if (4 <= argc - pos && STREQ (argv[pos], "-l") && binop (argv[pos + 2])) + value = binary_operator (true); + else if (3 <= argc - pos && binop (argv[pos + 1])) + value = binary_operator (false); + + /* It might be a switch type argument. */ + else if (argv[pos][0] == '-' && argv[pos][1] && argv[pos][2] == '\0') + { + if (test_unop (argv[pos])) + value = unary_operator (); + else + test_syntax_error (_("%s: unary operator expected"), argv[pos]); + } + else + { + value = (argv[pos][0] != '\0'); + advance (false); + } + + return negated ^ value; +} + +static bool +binary_operator (bool l_is_l) +{ + int op; + struct stat stat_buf, stat_spare; + /* Is the right integer expression of the form '-l string'? */ + bool r_is_l; + + if (l_is_l) + advance (false); + op = pos + 1; + + if ((op < argc - 2) && STREQ (argv[op + 1], "-l")) + { + r_is_l = true; + advance (false); + } + else + r_is_l = false; + + if (argv[op][0] == '-') + { + /* check for eq, nt, and stuff */ + if ((((argv[op][1] == 'l' || argv[op][1] == 'g') + && (argv[op][2] == 'e' || argv[op][2] == 't')) + || (argv[op][1] == 'e' && argv[op][2] == 'q') + || (argv[op][1] == 'n' && argv[op][2] == 'e')) + && !argv[op][3]) + { + char lbuf[INT_BUFSIZE_BOUND (uintmax_t)]; + char rbuf[INT_BUFSIZE_BOUND (uintmax_t)]; + char const *l = (l_is_l + ? umaxtostr (strlen (argv[op - 1]), lbuf) + : find_int (argv[op - 1])); + char const *r = (r_is_l + ? umaxtostr (strlen (argv[op + 2]), rbuf) + : find_int (argv[op + 1])); + int cmp = strintcmp (l, r); + bool xe_operator = (argv[op][2] == 'e'); + pos += 3; + return (argv[op][1] == 'l' ? cmp < xe_operator + : argv[op][1] == 'g' ? cmp > - xe_operator + : (cmp != 0) == xe_operator); + } + + switch (argv[op][1]) + { + default: + break; + + case 'n': + if (argv[op][2] == 't' && !argv[op][3]) + { + /* nt - newer than */ + struct timespec lt, rt; + bool le, re; + pos += 3; + if (l_is_l | r_is_l) + test_syntax_error (_("-nt does not accept -l"), NULL); + le = get_mtime (argv[op - 1], <); + re = get_mtime (argv[op + 1], &rt); + return le && (!re || timespec_cmp (lt, rt) > 0); + } + break; + + case 'e': + if (argv[op][2] == 'f' && !argv[op][3]) + { + /* ef - hard link? */ + pos += 3; + if (l_is_l | r_is_l) + test_syntax_error (_("-ef does not accept -l"), NULL); + return (stat (argv[op - 1], &stat_buf) == 0 + && stat (argv[op + 1], &stat_spare) == 0 + && stat_buf.st_dev == stat_spare.st_dev + && stat_buf.st_ino == stat_spare.st_ino); + } + break; + + case 'o': + if ('t' == argv[op][2] && '\000' == argv[op][3]) + { + /* ot - older than */ + struct timespec lt, rt; + bool le, re; + pos += 3; + if (l_is_l | r_is_l) + test_syntax_error (_("-ot does not accept -l"), NULL); + le = get_mtime (argv[op - 1], <); + re = get_mtime (argv[op + 1], &rt); + return re && (!le || timespec_cmp (lt, rt) < 0); + } + break; + } + + /* FIXME: is this dead code? */ + test_syntax_error (_("unknown binary operator"), argv[op]); + } + + if (argv[op][0] == '=' && !argv[op][1]) + { + bool value = STREQ (argv[pos], argv[pos + 2]); + pos += 3; + return value; + } + + if (STREQ (argv[op], "!=")) + { + bool value = !STREQ (argv[pos], argv[pos + 2]); + pos += 3; + return value; + } + + /* Not reached. */ + abort (); +} + +static bool +unary_operator (void) +{ + struct stat stat_buf; + + switch (argv[pos][1]) + { + default: + return false; + + /* All of the following unary operators use unary_advance (), which + checks to make sure that there is an argument, and then advances + pos right past it. This means that pos - 1 is the location of the + argument. */ + + case 'a': /* file exists in the file system? */ + case 'e': + unary_advance (); + return stat (argv[pos - 1], &stat_buf) == 0; + + case 'r': /* file is readable? */ + unary_advance (); + return euidaccess (argv[pos - 1], R_OK) == 0; + + case 'w': /* File is writable? */ + unary_advance (); + return euidaccess (argv[pos - 1], W_OK) == 0; + + case 'x': /* File is executable? */ + unary_advance (); + return euidaccess (argv[pos - 1], X_OK) == 0; + + case 'O': /* File is owned by you? */ + unary_advance (); + return (stat (argv[pos - 1], &stat_buf) == 0 + && (geteuid () == stat_buf.st_uid)); + + case 'G': /* File is owned by your group? */ + unary_advance (); + return (stat (argv[pos - 1], &stat_buf) == 0 + && (getegid () == stat_buf.st_gid)); + + case 'f': /* File is a file? */ + unary_advance (); + /* Under POSIX, -f is true if the given file exists + and is a regular file. */ + return (stat (argv[pos - 1], &stat_buf) == 0 + && S_ISREG (stat_buf.st_mode)); + + case 'd': /* File is a directory? */ + unary_advance (); + return (stat (argv[pos - 1], &stat_buf) == 0 + && S_ISDIR (stat_buf.st_mode)); + + case 's': /* File has something in it? */ + unary_advance (); + return (stat (argv[pos - 1], &stat_buf) == 0 + && 0 < stat_buf.st_size); + + case 'S': /* File is a socket? */ + unary_advance (); + return (stat (argv[pos - 1], &stat_buf) == 0 + && S_ISSOCK (stat_buf.st_mode)); + + case 'c': /* File is character special? */ + unary_advance (); + return (stat (argv[pos - 1], &stat_buf) == 0 + && S_ISCHR (stat_buf.st_mode)); + + case 'b': /* File is block special? */ + unary_advance (); + return (stat (argv[pos - 1], &stat_buf) == 0 + && S_ISBLK (stat_buf.st_mode)); + + case 'p': /* File is a named pipe? */ + unary_advance (); + return (stat (argv[pos - 1], &stat_buf) == 0 + && S_ISFIFO (stat_buf.st_mode)); + + case 'L': /* Same as -h */ + /*FALLTHROUGH*/ + + case 'h': /* File is a symbolic link? */ + unary_advance (); + return (lstat (argv[pos - 1], &stat_buf) == 0 + && S_ISLNK (stat_buf.st_mode)); + + case 'u': /* File is setuid? */ + unary_advance (); + return (stat (argv[pos - 1], &stat_buf) == 0 + && (stat_buf.st_mode & S_ISUID)); + + case 'g': /* File is setgid? */ + unary_advance (); + return (stat (argv[pos - 1], &stat_buf) == 0 + && (stat_buf.st_mode & S_ISGID)); + + case 'k': /* File has sticky bit set? */ + unary_advance (); + return (stat (argv[pos - 1], &stat_buf) == 0 + && (stat_buf.st_mode & S_ISVTX)); + + case 't': /* File (fd) is a terminal? */ + { + long int fd; + char const *arg; + unary_advance (); + arg = find_int (argv[pos - 1]); + errno = 0; + fd = strtol (arg, NULL, 10); + return (errno != ERANGE && 0 <= fd && fd <= INT_MAX && isatty (fd)); + } + + case 'n': /* True if arg has some length. */ + unary_advance (); + return argv[pos - 1][0] != 0; + + case 'z': /* True if arg has no length. */ + unary_advance (); + return argv[pos - 1][0] == '\0'; + } +} + +/* + * and: + * term + * term '-a' and + */ +static bool +and (void) +{ + bool value = true; + + for (;;) + { + value &= term (); + if (! (pos < argc && STREQ (argv[pos], "-a"))) + return value; + advance (false); + } +} + +/* + * or: + * and + * and '-o' or + */ +static bool +or (void) +{ + bool value = false; + + for (;;) + { + value |= and (); + if (! (pos < argc && STREQ (argv[pos], "-o"))) + return value; + advance (false); + } +} + +/* + * expr: + * or + */ +static bool +expr (void) +{ + if (pos >= argc) + beyond (); + + return or (); /* Same with this. */ +} + +/* Return true if OP is one of the test command's unary operators. */ +static bool +test_unop (char const *op) +{ + if (op[0] != '-') + return false; + + switch (op[1]) + { + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'k': case 'n': + case 'o': case 'p': case 'r': case 's': case 't': + case 'u': case 'w': case 'x': case 'z': + case 'G': case 'L': case 'O': case 'S': case 'N': + return true; + } + + return false; +} + +static bool +one_argument (void) +{ + return argv[pos++][0] != '\0'; +} + +static bool +two_arguments (void) +{ + bool value; + + if (STREQ (argv[pos], "!")) + { + advance (false); + value = ! one_argument (); + } + else if (argv[pos][0] == '-' + && argv[pos][1] != '\0' + && argv[pos][2] == '\0') + { + if (test_unop (argv[pos])) + value = unary_operator (); + else + test_syntax_error (_("%s: unary operator expected"), argv[pos]); + } + else + beyond (); + return (value); +} + +static bool +three_arguments (void) +{ + bool value; + + if (binop (argv[pos + 1])) + value = binary_operator (false); + else if (STREQ (argv[pos], "!")) + { + advance (true); + value = !two_arguments (); + } + else if (STREQ (argv[pos], "(") && STREQ (argv[pos + 2], ")")) + { + advance (false); + value = one_argument (); + advance (false); + } + else if (STREQ (argv[pos + 1], "-a") || STREQ (argv[pos + 1], "-o")) + value = expr (); + else + test_syntax_error (_("%s: binary operator expected"), argv[pos+1]); + return (value); +} + +/* This is an implementation of a Posix.2 proposal by David Korn. */ +static bool +posixtest (int nargs) +{ + bool value; + + switch (nargs) + { + case 1: + value = one_argument (); + break; + + case 2: + value = two_arguments (); + break; + + case 3: + value = three_arguments (); + break; + + case 4: + if (STREQ (argv[pos], "!")) + { + advance (true); + value = !three_arguments (); + break; + } + if (STREQ (argv[pos], "(") && STREQ (argv[pos + 3], ")")) + { + advance (false); + value = two_arguments (); + advance (false); + break; + } + /* FALLTHROUGH */ + case 5: + default: + if (nargs <= 0) + abort (); + value = expr (); + } + + return (value); +} + +#if defined TEST_STANDALONE +# include "long-options.h" + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + fputs (_("\ +Usage: test EXPRESSION\n\ + or: test\n\ + or: [ EXPRESSION ]\n\ + or: [ ]\n\ + or: [ OPTION\n\ +"), stdout); + fputs (_("\ +Exit with the status determined by EXPRESSION.\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +An omitted EXPRESSION defaults to false. Otherwise,\n\ +EXPRESSION is true or false and sets exit status. It is one of:\n\ +"), stdout); + fputs (_("\ +\n\ + ( EXPRESSION ) EXPRESSION is true\n\ + ! EXPRESSION EXPRESSION is false\n\ + EXPRESSION1 -a EXPRESSION2 both EXPRESSION1 and EXPRESSION2 are true\n\ + EXPRESSION1 -o EXPRESSION2 either EXPRESSION1 or EXPRESSION2 is true\n\ +"), stdout); + fputs (_("\ +\n\ + -n STRING the length of STRING is nonzero\n\ + STRING equivalent to -n STRING\n\ + -z STRING the length of STRING is zero\n\ + STRING1 = STRING2 the strings are equal\n\ + STRING1 != STRING2 the strings are not equal\n\ +"), stdout); + fputs (_("\ +\n\ + INTEGER1 -eq INTEGER2 INTEGER1 is equal to INTEGER2\n\ + INTEGER1 -ge INTEGER2 INTEGER1 is greater than or equal to INTEGER2\n\ + INTEGER1 -gt INTEGER2 INTEGER1 is greater than INTEGER2\n\ + INTEGER1 -le INTEGER2 INTEGER1 is less than or equal to INTEGER2\n\ + INTEGER1 -lt INTEGER2 INTEGER1 is less than INTEGER2\n\ + INTEGER1 -ne INTEGER2 INTEGER1 is not equal to INTEGER2\n\ +"), stdout); + fputs (_("\ +\n\ + FILE1 -ef FILE2 FILE1 and FILE2 have the same device and inode numbers\n\ + FILE1 -nt FILE2 FILE1 is newer (modification date) than FILE2\n\ + FILE1 -ot FILE2 FILE1 is older than FILE2\n\ +"), stdout); + fputs (_("\ +\n\ + -b FILE FILE exists and is block special\n\ + -c FILE FILE exists and is character special\n\ + -d FILE FILE exists and is a directory\n\ + -e FILE FILE exists\n\ +"), stdout); + fputs (_("\ + -f FILE FILE exists and is a regular file\n\ + -g FILE FILE exists and is set-group-ID\n\ + -G FILE FILE exists and is owned by the effective group ID\n\ + -h FILE FILE exists and is a symbolic link (same as -L)\n\ + -k FILE FILE exists and has its sticky bit set\n\ +"), stdout); + fputs (_("\ + -L FILE FILE exists and is a symbolic link (same as -h)\n\ + -O FILE FILE exists and is owned by the effective user ID\n\ + -p FILE FILE exists and is a named pipe\n\ + -r FILE FILE exists and read permission is granted\n\ + -s FILE FILE exists and has a size greater than zero\n\ +"), stdout); + fputs (_("\ + -S FILE FILE exists and is a socket\n\ + -t FD file descriptor FD is opened on a terminal\n\ + -u FILE FILE exists and its set-user-ID bit is set\n\ + -w FILE FILE exists and write permission is granted\n\ + -x FILE FILE exists and execute (or search) permission is granted\n\ +"), stdout); + fputs (_("\ +\n\ +Except for -h and -L, all FILE-related tests dereference symbolic links.\n\ +Beware that parentheses need to be escaped (e.g., by backslashes) for shells.\n\ +INTEGER may also be -l STRING, which evaluates to the length of STRING.\n\ +"), stdout); + printf (USAGE_BUILTIN_WARNING, _("test and/or [")); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} +#endif /* TEST_STANDALONE */ + +#if !defined TEST_STANDALONE +# define main test_command +#endif + +#define AUTHORS "Kevin Braunsdorf", "Matthew Bradburn" + +/* + * [: + * '[' expr ']' + * test: + * test expr + */ +int +main (int margc, char **margv) +{ + bool value; + +#if !defined TEST_STANDALONE + int code; + + code = setjmp (test_exit_buf); + + if (code) + return (test_error_return); +#else /* TEST_STANDALONE */ + initialize_main (&margc, &margv); + program_name = margv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (TEST_FAILURE); + atexit (close_stdout); +#endif /* TEST_STANDALONE */ + + argv = margv; + + if (LBRACKET) + { + /* Recognize --help or --version, but only when invoked in the + "[" form, and when the last argument is not "]". POSIX + allows "[ --help" and "[ --version" to have the usual GNU + behavior, but it requires "test --help" and "test --version" + to exit silently with status 0. */ + if (margc < 2 || !STREQ (margv[margc - 1], "]")) + { + parse_long_options (margc, margv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + test_syntax_error (_("missing `]'"), NULL); + } + + --margc; + } + + argc = margc; + pos = 1; + + if (pos >= argc) + test_exit (TEST_FALSE); + + value = posixtest (argc - 1); + + if (pos != argc) + test_syntax_error (_("extra argument %s"), quote (argv[pos])); + + test_exit (value ? TEST_TRUE : TEST_FALSE); +} diff --git a/src/touch.c b/src/touch.c new file mode 100644 index 0000000..a79c26d --- /dev/null +++ b/src/touch.c @@ -0,0 +1,420 @@ +/* touch -- change modification and access times of files + Copyright (C) 87, 1989-1991, 1995-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Paul Rubin, Arnold Robbins, Jim Kingdon, David MacKenzie, + and Randy Smith. */ + +#include +#include +#include +#include + +#include "system.h" +#include "argmatch.h" +#include "error.h" +#include "fd-reopen.h" +#include "getdate.h" +#include "posixtm.h" +#include "posixver.h" +#include "quote.h" +#include "safe-read.h" +#include "stat-time.h" +#include "utimens.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "touch" + +#define AUTHORS \ +"Paul Rubin", "Arnold Robbins, Jim Kingdon, David MacKenzie", "Randy Smith" + +/* Bitmasks for `change_times'. */ +#define CH_ATIME 1 +#define CH_MTIME 2 + +/* The name by which this program was run. */ +char *program_name; + +/* Which timestamps to change. */ +static int change_times; + +/* (-c) If true, don't create if not already there. */ +static bool no_create; + +/* (-r) If true, use times from a reference file. */ +static bool use_ref; + +/* If true, 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. + On some systems, we can do this if we own the file, even though + we have neither read nor write access to it. */ +static bool amtime_now; + +/* New access and modification times to use when setting time. */ +static struct timespec newtime[2]; + +/* File to use for -r. */ +static char *ref_file; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + TIME_OPTION = CHAR_MAX + 1 +}; + +static struct option const longopts[] = +{ + {"time", required_argument, NULL, TIME_OPTION}, + {"no-create", no_argument, NULL, 'c'}, + {"date", required_argument, NULL, 'd'}, + {"file", required_argument, NULL, 'r'}, /* FIXME: remove --file in 2006 */ + {"reference", required_argument, NULL, 'r'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Valid arguments to the `--time' option. */ +static char const* const time_args[] = +{ + "atime", "access", "use", "mtime", "modify", NULL +}; + +/* The bits in `change_times' that those arguments set. */ +static int const time_masks[] = +{ + CH_ATIME, CH_ATIME, CH_ATIME, CH_MTIME, CH_MTIME +}; + +/* Store into *RESULT the result of interpreting FLEX_DATE as a date, + relative to NOW. If NOW is null, use the current time. */ + +static void +get_reldate (struct timespec *result, + char const *flex_date, struct timespec const *now) +{ + if (! get_date (result, flex_date, now)) + error (EXIT_FAILURE, 0, _("invalid date format %s"), quote (flex_date)); +} + +/* Update the time of file FILE according to the options given. + Return true if successful. */ + +static bool +touch (const char *file) +{ + bool ok; + struct stat sbuf; + int fd = -1; + int open_errno = 0; + struct timespec timespec[2]; + struct timespec const *t; + + if (STREQ (file, "-")) + fd = STDOUT_FILENO; + else if (! no_create) + { + /* Try to open FILE, creating it if necessary. */ + fd = fd_reopen (STDIN_FILENO, file, + O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + + /* Don't save a copy of errno if it's EISDIR, since that would lead + touch to give a bogus diagnostic for e.g., `touch /' (assuming + we don't own / or have write access to it). On Solaris 5.6, + and probably other systems, it is EINVAL. On SunOS4, it's EPERM. */ + if (fd == -1 && errno != EISDIR && errno != EINVAL && errno != EPERM) + open_errno = errno; + } + + if (change_times != (CH_ATIME | CH_MTIME)) + { + /* We're setting only one of the time values. stat the target to get + the other one. If we have the file descriptor already, use fstat. + Otherwise, either we're in no-create mode (and hence didn't call open) + or FILE is inaccessible or a directory, so we have to use stat. */ + if (fd != -1 ? fstat (fd, &sbuf) : stat (file, &sbuf)) + { + if (open_errno) + error (0, open_errno, _("creating %s"), quote (file)); + else + { + if (no_create && (errno == ENOENT || errno == EBADF)) + return true; + error (0, errno, _("failed to get attributes of %s"), + quote (file)); + } + if (fd == STDIN_FILENO) + close (fd); + return false; + } + } + + if (amtime_now) + { + /* Pass NULL to futimens so it will not fail if we have + write access to the file, but don't own it. */ + t = NULL; + } + else + { + timespec[0] = (change_times & CH_ATIME + ? newtime[0] + : get_stat_atime (&sbuf)); + timespec[1] = (change_times & CH_MTIME + ? newtime[1] + : get_stat_mtime (&sbuf)); + t = timespec; + } + + ok = (futimens (fd, (fd == STDOUT_FILENO ? NULL : file), t) == 0); + + if (fd == STDIN_FILENO) + { + if (close (STDIN_FILENO) != 0) + { + error (0, errno, _("closing %s"), quote (file)); + return false; + } + } + else if (fd == STDOUT_FILENO) + { + /* Do not diagnose "touch -c - >&-". */ + if (!ok && errno == EBADF && no_create + && change_times == (CH_ATIME | CH_MTIME)) + return true; + } + + if (!ok) + { + if (open_errno) + { + /* The wording of this diagnostic should cover at least two cases: + - the file does not exist, but the parent directory is unwritable + - the file exists, but it isn't writable + I think it's not worth trying to distinguish them. */ + error (0, open_errno, _("cannot touch %s"), quote (file)); + } + else + { + if (no_create && errno == ENOENT) + return true; + error (0, errno, _("setting times of %s"), quote (file)); + } + return false; + } + + return true; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... FILE...\n"), program_name); + fputs (_("\ +Update the access and modification times of each FILE to the current time.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -a change only the access time\n\ + -c, --no-create do not create any files\n\ + -d, --date=STRING parse STRING and use it instead of current time\n\ + -f (ignored)\n\ + -m change only the modification time\n\ +"), stdout); + fputs (_("\ + -r, --reference=FILE use this file's times instead of current time\n\ + -t STAMP use [[CC]YY]MMDDhhmm[.ss] instead of current time\n\ + --time=WORD change the specified time:\n\ + WORD is access, atime, or use: equivalent to -a\n\ + WORD is modify or mtime: equivalent to -m\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +Note that the -d and -t options accept different time-date formats.\n\ +\n\ +If a FILE is -, touch standard output.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int c; + bool date_set = false; + bool ok = true; + char const *flex_date = NULL; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + change_times = 0; + no_create = use_ref = false; + + while ((c = getopt_long (argc, argv, "acd:fmr:t:", longopts, NULL)) != -1) + { + switch (c) + { + case 'a': + change_times |= CH_ATIME; + break; + + case 'c': + no_create = true; + break; + + case 'd': + flex_date = optarg; + break; + + case 'f': + break; + + case 'm': + change_times |= CH_MTIME; + break; + + case 'r': + use_ref = true; + ref_file = optarg; + break; + + case 't': + if (! posixtime (&newtime[0].tv_sec, optarg, + PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS)) + error (EXIT_FAILURE, 0, _("invalid date format %s"), + quote (optarg)); + newtime[0].tv_nsec = 0; + newtime[1] = newtime[0]; + date_set = true; + break; + + case TIME_OPTION: /* --time */ + change_times |= XARGMATCH ("--time", optarg, + time_args, time_masks); + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (change_times == 0) + change_times = CH_ATIME | CH_MTIME; + + if (date_set && (use_ref || flex_date)) + { + error (0, 0, _("cannot specify times from more than one source")); + usage (EXIT_FAILURE); + } + + if (use_ref) + { + struct stat ref_stats; + if (stat (ref_file, &ref_stats)) + error (EXIT_FAILURE, errno, + _("failed to get attributes of %s"), quote (ref_file)); + newtime[0] = get_stat_atime (&ref_stats); + newtime[1] = get_stat_mtime (&ref_stats); + date_set = true; + if (flex_date) + { + if (change_times & CH_ATIME) + get_reldate (&newtime[0], flex_date, &newtime[0]); + if (change_times & CH_MTIME) + get_reldate (&newtime[1], flex_date, &newtime[1]); + } + } + else + { + if (flex_date) + { + get_reldate (&newtime[0], flex_date, NULL); + newtime[1] = newtime[0]; + date_set = true; + } + } + + /* The obsolete `MMDDhhmm[YY]' form is valid IFF there are + two or more non-option arguments. */ + if (!date_set && 2 <= argc - optind && posix2_version () < 200112 + && posixtime (&newtime[0].tv_sec, argv[optind], + PDS_TRAILING_YEAR | PDS_PRE_2000)) + { + newtime[0].tv_nsec = 0; + newtime[1] = newtime[0]; + date_set = true; + + if (! getenv ("POSIXLY_CORRECT")) + { + struct tm const *tm = localtime (&newtime[0].tv_sec); + error (0, 0, + _("warning: `touch %s' is obsolete; use " + "`touch -t %04ld%02d%02d%02d%02d.%02d'"), + argv[optind], + tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + } + + optind++; + } + + if (!date_set) + { + if (change_times == (CH_ATIME | CH_MTIME)) + amtime_now = true; + else + { + gettime (&newtime[0]); + newtime[1] = newtime[0]; + } + } + + if (optind == argc) + { + error (0, 0, _("missing file operand")); + usage (EXIT_FAILURE); + } + + for (; optind < argc; ++optind) + ok &= touch (argv[optind]); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/tr.c b/src/tr.c new file mode 100644 index 0000000..214eb2b --- /dev/null +++ b/src/tr.c @@ -0,0 +1,1896 @@ +/* tr -- a filter to translate characters + Copyright (C) 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Jim Meyering */ + +#include + +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "quote.h" +#include "safe-read.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "tr" + +#define AUTHORS "Jim Meyering" + +enum { N_CHARS = UCHAR_MAX + 1 }; + +/* An unsigned integer type big enough to hold a repeat count or an + unsigned character. POSIX requires support for repeat counts as + high as 2**31 - 1. Since repeat counts might need to expand to + match the length of an argument string, we need at least size_t to + avoid arbitrary internal limits. It doesn't cost much to use + uintmax_t, though. */ +typedef uintmax_t count; + +/* The value for Spec_list->state that indicates to + get_next that it should initialize the tail pointer. + Its value should be as large as possible to avoid conflict + a valid value for the state field -- and that may be as + large as any valid repeat_count. */ +#define BEGIN_STATE (UINTMAX_MAX - 1) + +/* The value for Spec_list->state that indicates to + get_next that the element pointed to by Spec_list->tail is + being considered for the first time on this pass through the + list -- it indicates that get_next should make any necessary + initializations. */ +#define NEW_ELEMENT (BEGIN_STATE + 1) + +/* The maximum possible repeat count. Due to how the states are + implemented, it can be as much as BEGIN_STATE. */ +#define REPEAT_COUNT_MAXIMUM BEGIN_STATE + +/* The following (but not CC_NO_CLASS) are indices into the array of + valid character class strings. */ +enum Char_class + { + CC_ALNUM = 0, CC_ALPHA = 1, CC_BLANK = 2, CC_CNTRL = 3, + CC_DIGIT = 4, CC_GRAPH = 5, CC_LOWER = 6, CC_PRINT = 7, + CC_PUNCT = 8, CC_SPACE = 9, CC_UPPER = 10, CC_XDIGIT = 11, + CC_NO_CLASS = 9999 + }; + +/* Character class to which a character (returned by get_next) belonged; + but it is set only if the construct from which the character was obtained + was one of the character classes [:upper:] or [:lower:]. The value + is used only when translating and then, only to make sure that upper + and lower class constructs have the same relative positions in string1 + and string2. */ +enum Upper_Lower_class + { + UL_LOWER, + UL_UPPER, + UL_NONE + }; + +/* The type of a List_element. See build_spec_list for more details. */ +enum Range_element_type + { + RE_NORMAL_CHAR, + RE_RANGE, + RE_CHAR_CLASS, + RE_EQUIV_CLASS, + RE_REPEATED_CHAR + }; + +/* One construct in one of tr's argument strings. + For example, consider the POSIX version of the classic tr command: + tr -cs 'a-zA-Z_' '[\n*]' + String1 has 3 constructs, two of which are ranges (a-z and A-Z), + and a single normal character, `_'. String2 has one construct. */ +struct List_element + { + enum Range_element_type type; + struct List_element *next; + union + { + unsigned char normal_char; + struct /* unnamed */ + { + unsigned char first_char; + unsigned char last_char; + } + range; + enum Char_class char_class; + unsigned char equiv_code; + struct /* unnamed */ + { + unsigned char the_repeated_char; + count repeat_count; + } + repeated_char; + } + u; + }; + +/* Each of tr's argument strings is parsed into a form that is easier + to work with: a linked list of constructs (struct List_element). + Each Spec_list structure also encapsulates various attributes of + the corresponding argument string. The attributes are used mainly + to verify that the strings are valid in the context of any options + specified (like -s, -d, or -c). The main exception is the member + `tail', which is first used to construct the list. After construction, + it is used by get_next to save its state when traversing the list. + The member `state' serves a similar function. */ +struct Spec_list + { + /* Points to the head of the list of range elements. + The first struct is a dummy; its members are never used. */ + struct List_element *head; + + /* When appending, points to the last element. When traversing via + get_next(), points to the element to process next. Setting + Spec_list.state to the value BEGIN_STATE before calling get_next + signals get_next to initialize tail to point to head->next. */ + struct List_element *tail; + + /* Used to save state between calls to get_next. */ + count state; + + /* Length, in the sense that length ('a-z[:digit:]123abc') + is 42 ( = 26 + 10 + 6). */ + count length; + + /* The number of [c*] and [c*0] constructs that appear in this spec. */ + size_t n_indefinite_repeats; + + /* If n_indefinite_repeats is nonzero, this points to the List_element + corresponding to the last [c*] or [c*0] construct encountered in + this spec. Otherwise it is undefined. */ + struct List_element *indefinite_repeat_element; + + /* True if this spec contains at least one equivalence + class construct e.g. [=c=]. */ + bool has_equiv_class; + + /* True if this spec contains at least one character class + construct. E.g. [:digit:]. */ + bool has_char_class; + + /* True if this spec contains at least one of the character class + constructs (all but upper and lower) that aren't allowed in s2. */ + bool has_restricted_char_class; + }; + +/* A representation for escaped string1 or string2. As a string is parsed, + any backslash-escaped characters (other than octal or \a, \b, \f, \n, + etc.) are marked as such in this structure by setting the corresponding + entry in the ESCAPED vector. */ +struct E_string +{ + char *s; + bool *escaped; + size_t len; +}; + +/* Return nonzero if the Ith character of escaped string ES matches C + and is not escaped itself. */ +static inline bool +es_match (struct E_string const *es, size_t i, char c) +{ + return es->s[i] == c && !es->escaped[i]; +} + +/* The name by which this program was run. */ +char *program_name; + +/* When true, each sequence in the input of a repeated character + (call it c) is replaced (in the output) by a single occurrence of c + for every c in the squeeze set. */ +static bool squeeze_repeats = false; + +/* When true, removes characters in the delete set from input. */ +static bool delete = false; + +/* Use the complement of set1 in place of set1. */ +static bool complement = false; + +/* When tr is performing translation and string1 is longer than string2, + POSIX says that the result is unspecified. That gives the implementor + of a POSIX conforming version of tr two reasonable choices for the + semantics of this case. + + * The BSD tr pads string2 to the length of string1 by + repeating the last character in string2. + + * System V tr ignores characters in string1 that have no + corresponding character in string2. That is, string1 is effectively + truncated to the length of string2. + + When nonzero, this flag causes GNU tr to imitate the behavior + of System V tr when translating with string1 longer than string2. + The default is to emulate BSD tr. This flag is ignored in modes where + no translation is performed. Emulating the System V tr + in this exceptional case causes the relatively common BSD idiom: + + tr -cs A-Za-z0-9 '\012' + + to break (it would convert only zero bytes, rather than all + non-alphanumerics, to newlines). + + WARNING: This switch does not provide general BSD or System V + compatibility. For example, it doesn't disable the interpretation + of the POSIX constructs [:alpha:], [=c=], and [c*10], so if by + some unfortunate coincidence you use such constructs in scripts + expecting to use some other version of tr, the scripts will break. */ +static bool truncate_set1 = false; + +/* An alias for (!delete && non_option_args == 2). + It is set in main and used there and in validate(). */ +static bool translating; + +static char io_buf[BUFSIZ]; + +static char const *const char_class_name[] = +{ + "alnum", "alpha", "blank", "cntrl", "digit", "graph", + "lower", "print", "punct", "space", "upper", "xdigit" +}; +enum { N_CHAR_CLASSES = sizeof char_class_name / sizeof char_class_name[0] }; + +/* Array of boolean values. A character `c' is a member of the + squeeze set if and only if in_squeeze_set[c] is true. The squeeze + set is defined by the last (possibly, the only) string argument + on the command line when the squeeze option is given. */ +static bool in_squeeze_set[N_CHARS]; + +/* Array of boolean values. A character `c' is a member of the + delete set if and only if in_delete_set[c] is true. The delete + set is defined by the first (or only) string argument on the + command line when the delete option is given. */ +static bool in_delete_set[N_CHARS]; + +/* Array of character values defining the translation (if any) that + tr is to perform. Translation is performed only when there are + two specification strings and the delete switch is not given. */ +static char xlate[N_CHARS]; + +static struct option const long_options[] = +{ + {"complement", no_argument, NULL, 'c'}, + {"delete", no_argument, NULL, 'd'}, + {"squeeze-repeats", no_argument, NULL, 's'}, + {"truncate-set1", no_argument, NULL, 't'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... SET1 [SET2]\n\ +"), + program_name); + fputs (_("\ +Translate, squeeze, and/or delete characters from standard input,\n\ +writing to standard output.\n\ +\n\ + -c, -C, --complement first complement SET1\n\ + -d, --delete delete characters in SET1, do not translate\n\ + -s, --squeeze-repeats replace each input sequence of a repeated character\n\ + that is listed in SET1 with a single occurrence\n\ + of that character\n\ + -t, --truncate-set1 first truncate SET1 to length of SET2\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +SETs are specified as strings of characters. Most represent themselves.\n\ +Interpreted sequences are:\n\ +\n\ + \\NNN character with octal value NNN (1 to 3 octal digits)\n\ + \\\\ backslash\n\ + \\a audible BEL\n\ + \\b backspace\n\ + \\f form feed\n\ + \\n new line\n\ + \\r return\n\ + \\t horizontal tab\n\ +"), stdout); + fputs (_("\ + \\v vertical tab\n\ + CHAR1-CHAR2 all characters from CHAR1 to CHAR2 in ascending order\n\ + [CHAR*] in SET2, copies of CHAR until length of SET1\n\ + [CHAR*REPEAT] REPEAT copies of CHAR, REPEAT octal if starting with 0\n\ + [:alnum:] all letters and digits\n\ + [:alpha:] all letters\n\ + [:blank:] all horizontal whitespace\n\ + [:cntrl:] all control characters\n\ + [:digit:] all digits\n\ +"), stdout); + fputs (_("\ + [:graph:] all printable characters, not including space\n\ + [:lower:] all lower case letters\n\ + [:print:] all printable characters, including space\n\ + [:punct:] all punctuation characters\n\ + [:space:] all horizontal or vertical whitespace\n\ + [:upper:] all upper case letters\n\ + [:xdigit:] all hexadecimal digits\n\ + [=CHAR=] all characters which are equivalent to CHAR\n\ +"), stdout); + fputs (_("\ +\n\ +Translation occurs if -d is not given and both SET1 and SET2 appear.\n\ +-t may be used only when translating. SET2 is extended to length of\n\ +SET1 by repeating its last character as necessary. \ +"), stdout); + fputs (_("\ +Excess characters\n\ +of SET2 are ignored. Only [:lower:] and [:upper:] are guaranteed to\n\ +expand in ascending order; used in SET2 while translating, they may\n\ +only be used in pairs to specify case conversion. \ +"), stdout); + fputs (_("\ +-s uses SET1 if not\n\ +translating nor deleting; else squeezing uses SET2 and occurs after\n\ +translation or deletion.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Return nonzero if the character C is a member of the + equivalence class containing the character EQUIV_CLASS. */ + +static inline bool +is_equiv_class_member (unsigned char equiv_class, unsigned char c) +{ + return (equiv_class == c); +} + +/* Return true if the character C is a member of the + character class CHAR_CLASS. */ + +static bool +is_char_class_member (enum Char_class char_class, unsigned char c) +{ + int result; + + switch (char_class) + { + case CC_ALNUM: + result = isalnum (c); + break; + case CC_ALPHA: + result = isalpha (c); + break; + case CC_BLANK: + result = isblank (c); + break; + case CC_CNTRL: + result = iscntrl (c); + break; + case CC_DIGIT: + result = isdigit (c); + break; + case CC_GRAPH: + result = isgraph (c); + break; + case CC_LOWER: + result = islower (c); + break; + case CC_PRINT: + result = isprint (c); + break; + case CC_PUNCT: + result = ispunct (c); + break; + case CC_SPACE: + result = isspace (c); + break; + case CC_UPPER: + result = isupper (c); + break; + case CC_XDIGIT: + result = isxdigit (c); + break; + default: + abort (); + break; + } + + return !! result; +} + +static void +es_free (struct E_string *es) +{ + free (es->s); + free (es->escaped); +} + +/* Perform the first pass over each range-spec argument S, converting all + \c and \ddd escapes to their one-byte representations. If an invalid + quote sequence is found print an error message and return false; + Otherwise set *ES to the resulting string and return true. + The resulting array of characters may contain zero-bytes; + however, on input, S is assumed to be null-terminated, and hence + cannot contain actual (non-escaped) zero bytes. */ + +static bool +unquote (char const *s, struct E_string *es) +{ + size_t i, j; + size_t len = strlen (s); + + es->s = xmalloc (len); + es->escaped = xcalloc (len, sizeof es->escaped[0]); + + j = 0; + for (i = 0; s[i]; i++) + { + unsigned char c; + int oct_digit; + + switch (s[i]) + { + case '\\': + es->escaped[j] = true; + switch (s[i + 1]) + { + case '\\': + c = '\\'; + break; + case 'a': + c = '\a'; + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + c = s[i + 1] - '0'; + oct_digit = s[i + 2] - '0'; + if (0 <= oct_digit && oct_digit <= 7) + { + c = 8 * c + oct_digit; + ++i; + oct_digit = s[i + 2] - '0'; + if (0 <= oct_digit && oct_digit <= 7) + { + if (8 * c + oct_digit < N_CHARS) + { + c = 8 * c + oct_digit; + ++i; + } + else + { + /* A 3-digit octal number larger than \377 won't + fit in 8 bits. So we stop when adding the + next digit would put us over the limit and + give a warning about the ambiguity. POSIX + isn't clear on this, and we interpret this + lack of clarity as meaning the resulting behavior + is undefined, which means we're allowed to issue + a warning. */ + error (0, 0, _("warning: the ambiguous octal escape \ +\\%c%c%c is being\n\tinterpreted as the 2-byte sequence \\0%c%c, %c"), + s[i], s[i + 1], s[i + 2], + s[i], s[i + 1], s[i + 2]); + } + } + } + break; + case '\0': + /* POSIX seems to require that a trailing backslash must + stand for itself. Weird. */ + es->escaped[j] = false; + i--; + c = '\\'; + break; + default: + c = s[i + 1]; + break; + } + ++i; + es->s[j++] = c; + break; + default: + es->s[j++] = s[i]; + break; + } + } + es->len = j; + return true; +} + +/* If CLASS_STR is a valid character class string, return its index + in the global char_class_name array. Otherwise, return CC_NO_CLASS. */ + +static enum Char_class +look_up_char_class (char const *class_str, size_t len) +{ + enum Char_class i; + + for (i = 0; i < N_CHAR_CLASSES; i++) + if (strncmp (class_str, char_class_name[i], len) == 0 + && strlen (char_class_name[i]) == len) + return i; + return CC_NO_CLASS; +} + +/* Return a newly allocated string with a printable version of C. + This function is used solely for formatting error messages. */ + +static char * +make_printable_char (unsigned char c) +{ + char *buf = xmalloc (5); + + if (isprint (c)) + { + buf[0] = c; + buf[1] = '\0'; + } + else + { + sprintf (buf, "\\%03o", c); + } + return buf; +} + +/* Return a newly allocated copy of S which is suitable for printing. + LEN is the number of characters in S. Most non-printing + (isprint) characters are represented by a backslash followed by + 3 octal digits. However, the characters represented by \c escapes + where c is one of [abfnrtv] are represented by their 2-character \c + sequences. This function is used solely for printing error messages. */ + +static char * +make_printable_str (char const *s, size_t len) +{ + /* Worst case is that every character expands to a backslash + followed by a 3-character octal escape sequence. */ + char *printable_buf = xnmalloc (len + 1, 4); + char *p = printable_buf; + size_t i; + + for (i = 0; i < len; i++) + { + char buf[5]; + char const *tmp = NULL; + unsigned char c = s[i]; + + switch (c) + { + case '\\': + tmp = "\\"; + break; + case '\a': + tmp = "\\a"; + break; + case '\b': + tmp = "\\b"; + break; + case '\f': + tmp = "\\f"; + break; + case '\n': + tmp = "\\n"; + break; + case '\r': + tmp = "\\r"; + break; + case '\t': + tmp = "\\t"; + break; + case '\v': + tmp = "\\v"; + break; + default: + if (isprint (c)) + { + buf[0] = c; + buf[1] = '\0'; + } + else + sprintf (buf, "\\%03o", c); + tmp = buf; + break; + } + p = stpcpy (p, tmp); + } + return printable_buf; +} + +/* Append a newly allocated structure representing a + character C to the specification list LIST. */ + +static void +append_normal_char (struct Spec_list *list, unsigned char c) +{ + struct List_element *new; + + new = xmalloc (sizeof *new); + new->next = NULL; + new->type = RE_NORMAL_CHAR; + new->u.normal_char = c; + assert (list->tail); + list->tail->next = new; + list->tail = new; +} + +/* Append a newly allocated structure representing the range + of characters from FIRST to LAST to the specification list LIST. + Return false if LAST precedes FIRST in the collating sequence, + true otherwise. This means that '[c-c]' is acceptable. */ + +static bool +append_range (struct Spec_list *list, unsigned char first, unsigned char last) +{ + struct List_element *new; + + if (last < first) + { + char *tmp1 = make_printable_char (first); + char *tmp2 = make_printable_char (last); + + error (0, 0, + _("range-endpoints of `%s-%s' are in reverse collating sequence order"), + tmp1, tmp2); + free (tmp1); + free (tmp2); + return false; + } + new = xmalloc (sizeof *new); + new->next = NULL; + new->type = RE_RANGE; + new->u.range.first_char = first; + new->u.range.last_char = last; + assert (list->tail); + list->tail->next = new; + list->tail = new; + return true; +} + +/* If CHAR_CLASS_STR is a valid character class string, append a + newly allocated structure representing that character class to the end + of the specification list LIST and return true. If CHAR_CLASS_STR is not + a valid string return false. */ + +static bool +append_char_class (struct Spec_list *list, + char const *char_class_str, size_t len) +{ + enum Char_class char_class; + struct List_element *new; + + char_class = look_up_char_class (char_class_str, len); + if (char_class == CC_NO_CLASS) + return false; + new = xmalloc (sizeof *new); + new->next = NULL; + new->type = RE_CHAR_CLASS; + new->u.char_class = char_class; + assert (list->tail); + list->tail->next = new; + list->tail = new; + return true; +} + +/* Append a newly allocated structure representing a [c*n] + repeated character construct to the specification list LIST. + THE_CHAR is the single character to be repeated, and REPEAT_COUNT + is a non-negative repeat count. */ + +static void +append_repeated_char (struct Spec_list *list, unsigned char the_char, + count repeat_count) +{ + struct List_element *new; + + new = xmalloc (sizeof *new); + new->next = NULL; + new->type = RE_REPEATED_CHAR; + new->u.repeated_char.the_repeated_char = the_char; + new->u.repeated_char.repeat_count = repeat_count; + assert (list->tail); + list->tail->next = new; + list->tail = new; +} + +/* Given a string, EQUIV_CLASS_STR, from a [=str=] context and + the length of that string, LEN, if LEN is exactly one, append + a newly allocated structure representing the specified + equivalence class to the specification list, LIST and return true. + If LEN is not 1, return false. */ + +static bool +append_equiv_class (struct Spec_list *list, + char const *equiv_class_str, size_t len) +{ + struct List_element *new; + + if (len != 1) + return false; + new = xmalloc (sizeof *new); + new->next = NULL; + new->type = RE_EQUIV_CLASS; + new->u.equiv_code = *equiv_class_str; + assert (list->tail); + list->tail->next = new; + list->tail = new; + return true; +} + +/* Search forward starting at START_IDX for the 2-char sequence + (PRE_BRACKET_CHAR,']') in the string P of length P_LEN. If such + a sequence is found, set *RESULT_IDX to the index of the first + character and return true. Otherwise return false. P may contain + zero bytes. */ + +static bool +find_closing_delim (const struct E_string *es, size_t start_idx, + char pre_bracket_char, size_t *result_idx) +{ + size_t i; + + for (i = start_idx; i < es->len - 1; i++) + if (es->s[i] == pre_bracket_char && es->s[i + 1] == ']' + && !es->escaped[i] && !es->escaped[i + 1]) + { + *result_idx = i; + return true; + } + return false; +} + +/* Parse the bracketed repeat-char syntax. If the P_LEN characters + beginning with P[ START_IDX ] comprise a valid [c*n] construct, + then set *CHAR_TO_REPEAT, *REPEAT_COUNT, and *CLOSING_BRACKET_IDX + and return zero. If the second character following + the opening bracket is not `*' or if no closing bracket can be + found, return -1. If a closing bracket is found and the + second char is `*', but the string between the `*' and `]' isn't + empty, an octal number, or a decimal number, print an error message + and return -2. */ + +static int +find_bracketed_repeat (const struct E_string *es, size_t start_idx, + unsigned char *char_to_repeat, count *repeat_count, + size_t *closing_bracket_idx) +{ + size_t i; + + assert (start_idx + 1 < es->len); + if (!es_match (es, start_idx + 1, '*')) + return -1; + + for (i = start_idx + 2; i < es->len && !es->escaped[i]; i++) + { + if (es->s[i] == ']') + { + size_t digit_str_len = i - start_idx - 2; + + *char_to_repeat = es->s[start_idx]; + if (digit_str_len == 0) + { + /* We've matched [c*] -- no explicit repeat count. */ + *repeat_count = 0; + } + else + { + /* Here, we have found [c*s] where s should be a string + of octal (if it starts with `0') or decimal digits. */ + char const *digit_str = &es->s[start_idx + 2]; + char *d_end; + if ((xstrtoumax (digit_str, &d_end, *digit_str == '0' ? 8 : 10, + repeat_count, NULL) + != LONGINT_OK) + || REPEAT_COUNT_MAXIMUM < *repeat_count + || digit_str + digit_str_len != d_end) + { + char *tmp = make_printable_str (digit_str, digit_str_len); + error (0, 0, + _("invalid repeat count %s in [c*n] construct"), + quote (tmp)); + free (tmp); + return -2; + } + } + *closing_bracket_idx = i; + return 0; + } + } + return -1; /* No bracket found. */ +} + +/* Return true if the string at ES->s[IDX] matches the regular + expression `\*[0-9]*\]', false otherwise. The string does not + match if any of its characters are escaped. */ + +static bool +star_digits_closebracket (const struct E_string *es, size_t idx) +{ + size_t i; + + if (!es_match (es, idx, '*')) + return false; + + for (i = idx + 1; i < es->len; i++) + if (!ISDIGIT (to_uchar (es->s[i])) || es->escaped[i]) + return es_match (es, i, ']'); + return false; +} + +/* Convert string UNESCAPED_STRING (which has been preprocessed to + convert backslash-escape sequences) of length LEN characters into + a linked list of the following 5 types of constructs: + - [:str:] Character class where `str' is one of the 12 valid strings. + - [=c=] Equivalence class where `c' is any single character. + - [c*n] Repeat the single character `c' `n' times. n may be omitted. + However, if `n' is present, it must be a non-negative octal or + decimal integer. + - r-s Range of characters from `r' to `s'. The second endpoint must + not precede the first in the current collating sequence. + - c Any other character is interpreted as itself. */ + +static bool +build_spec_list (const struct E_string *es, struct Spec_list *result) +{ + char const *p; + size_t i; + + p = es->s; + + /* The main for-loop below recognizes the 4 multi-character constructs. + A character that matches (in its context) none of the multi-character + constructs is classified as `normal'. Since all multi-character + constructs have at least 3 characters, any strings of length 2 or + less are composed solely of normal characters. Hence, the index of + the outer for-loop runs only as far as LEN-2. */ + + for (i = 0; i + 2 < es->len; /* empty */) + { + if (es_match (es, i, '[')) + { + bool matched_multi_char_construct; + size_t closing_bracket_idx; + unsigned char char_to_repeat; + count repeat_count; + int err; + + matched_multi_char_construct = true; + if (es_match (es, i + 1, ':') || es_match (es, i + 1, '=')) + { + size_t closing_delim_idx; + + if (find_closing_delim (es, i + 2, p[i + 1], &closing_delim_idx)) + { + size_t opnd_str_len = closing_delim_idx - 1 - (i + 2) + 1; + char const *opnd_str = p + i + 2; + + if (opnd_str_len == 0) + { + if (p[i + 1] == ':') + error (0, 0, _("missing character class name `[::]'")); + else + error (0, 0, + _("missing equivalence class character `[==]'")); + return false; + } + + if (p[i + 1] == ':') + { + /* FIXME: big comment. */ + if (!append_char_class (result, opnd_str, opnd_str_len)) + { + if (star_digits_closebracket (es, i + 2)) + goto try_bracketed_repeat; + else + { + char *tmp = make_printable_str (opnd_str, + opnd_str_len); + error (0, 0, _("invalid character class %s"), + quote (tmp)); + free (tmp); + return false; + } + } + } + else + { + /* FIXME: big comment. */ + if (!append_equiv_class (result, opnd_str, opnd_str_len)) + { + if (star_digits_closebracket (es, i + 2)) + goto try_bracketed_repeat; + else + { + char *tmp = make_printable_str (opnd_str, + opnd_str_len); + error (0, 0, + _("%s: equivalence class operand must be a single character"), + tmp); + free (tmp); + return false; + } + } + } + + i = closing_delim_idx + 2; + continue; + } + /* Else fall through. This could be [:*] or [=*]. */ + } + + try_bracketed_repeat: + + /* Determine whether this is a bracketed repeat range + matching the RE \[.\*(dec_or_oct_number)?\]. */ + err = find_bracketed_repeat (es, i + 1, &char_to_repeat, + &repeat_count, + &closing_bracket_idx); + if (err == 0) + { + append_repeated_char (result, char_to_repeat, repeat_count); + i = closing_bracket_idx + 1; + } + else if (err == -1) + { + matched_multi_char_construct = false; + } + else + { + /* Found a string that looked like [c*n] but the + numeric part was invalid. */ + return false; + } + + if (matched_multi_char_construct) + continue; + + /* We reach this point if P does not match [:str:], [=c=], + [c*n], or [c*]. Now, see if P looks like a range `[-c' + (from `[' to `c'). */ + } + + /* Look ahead one char for ranges like a-z. */ + if (es_match (es, i + 1, '-')) + { + if (!append_range (result, p[i], p[i + 2])) + return false; + i += 3; + } + else + { + append_normal_char (result, p[i]); + ++i; + } + } + + /* Now handle the (2 or fewer) remaining characters p[i]..p[es->len - 1]. */ + for (; i < es->len; i++) + append_normal_char (result, p[i]); + + return true; +} + +/* Given a Spec_list S (with its saved state implicit in the values + of its members `tail' and `state'), return the next single character + in the expansion of S's constructs. If the last character of S was + returned on the previous call or if S was empty, this function + returns -1. For example, successive calls to get_next where S + represents the spec-string 'a-d[y*3]' will return the sequence + of values a, b, c, d, y, y, y, -1. Finally, if the construct from + which the returned character comes is [:upper:] or [:lower:], the + parameter CLASS is given a value to indicate which it was. Otherwise + CLASS is set to UL_NONE. This value is used only when constructing + the translation table to verify that any occurrences of upper and + lower class constructs in the spec-strings appear in the same relative + positions. */ + +static int +get_next (struct Spec_list *s, enum Upper_Lower_class *class) +{ + struct List_element *p; + int return_val; + int i; + + if (class) + *class = UL_NONE; + + if (s->state == BEGIN_STATE) + { + s->tail = s->head->next; + s->state = NEW_ELEMENT; + } + + p = s->tail; + if (p == NULL) + return -1; + + switch (p->type) + { + case RE_NORMAL_CHAR: + return_val = p->u.normal_char; + s->state = NEW_ELEMENT; + s->tail = p->next; + break; + + case RE_RANGE: + if (s->state == NEW_ELEMENT) + s->state = p->u.range.first_char; + else + ++(s->state); + return_val = s->state; + if (s->state == p->u.range.last_char) + { + s->tail = p->next; + s->state = NEW_ELEMENT; + } + break; + + case RE_CHAR_CLASS: + if (class) + { + bool upper_or_lower; + switch (p->u.char_class) + { + case CC_LOWER: + *class = UL_LOWER; + upper_or_lower = true; + break; + case CC_UPPER: + *class = UL_UPPER; + upper_or_lower = true; + break; + default: + upper_or_lower = false; + break; + } + + if (upper_or_lower) + { + s->tail = p->next; + s->state = NEW_ELEMENT; + return_val = 0; + break; + } + } + + if (s->state == NEW_ELEMENT) + { + for (i = 0; i < N_CHARS; i++) + if (is_char_class_member (p->u.char_class, i)) + break; + assert (i < N_CHARS); + s->state = i; + } + assert (is_char_class_member (p->u.char_class, s->state)); + return_val = s->state; + for (i = s->state + 1; i < N_CHARS; i++) + if (is_char_class_member (p->u.char_class, i)) + break; + if (i < N_CHARS) + s->state = i; + else + { + s->tail = p->next; + s->state = NEW_ELEMENT; + } + break; + + case RE_EQUIV_CLASS: + /* FIXME: this assumes that each character is alone in its own + equivalence class (which appears to be correct for my + LC_COLLATE. But I don't know of any function that allows + one to determine a character's equivalence class. */ + + return_val = p->u.equiv_code; + s->state = NEW_ELEMENT; + s->tail = p->next; + break; + + case RE_REPEATED_CHAR: + /* Here, a repeat count of n == 0 means don't repeat at all. */ + if (p->u.repeated_char.repeat_count == 0) + { + s->tail = p->next; + s->state = NEW_ELEMENT; + return_val = get_next (s, class); + } + else + { + if (s->state == NEW_ELEMENT) + { + s->state = 0; + } + ++(s->state); + return_val = p->u.repeated_char.the_repeated_char; + if (s->state == p->u.repeated_char.repeat_count) + { + s->tail = p->next; + s->state = NEW_ELEMENT; + } + } + break; + + default: + abort (); + break; + } + + return return_val; +} + +/* This is a minor kludge. This function is called from + get_spec_stats to determine the cardinality of a set derived + from a complemented string. It's a kludge in that some of the + same operations are (duplicated) performed in set_initialize. */ + +static int +card_of_complement (struct Spec_list *s) +{ + int c; + int cardinality = N_CHARS; + bool in_set[N_CHARS] = { 0, }; + + s->state = BEGIN_STATE; + while ((c = get_next (s, NULL)) != -1) + { + cardinality -= (!in_set[c]); + in_set[c] = true; + } + return cardinality; +} + +/* Gather statistics about the spec-list S in preparation for the tests + in validate that determine the consistency of the specs. This function + is called at most twice; once for string1, and again for any string2. + LEN_S1 < 0 indicates that this is the first call and that S represents + string1. When LEN_S1 >= 0, it is the length of the expansion of the + constructs in string1, and we can use its value to resolve any + indefinite repeat construct in S (which represents string2). Hence, + this function has the side-effect that it converts a valid [c*] + construct in string2 to [c*n] where n is large enough (or 0) to give + string2 the same length as string1. For example, with the command + tr a-z 'A[\n*]Z' on the second call to get_spec_stats, LEN_S1 would + be 26 and S (representing string2) would be converted to 'A[\n*24]Z'. */ + +static void +get_spec_stats (struct Spec_list *s) +{ + struct List_element *p; + count length = 0; + + s->n_indefinite_repeats = 0; + s->has_equiv_class = false; + s->has_restricted_char_class = false; + s->has_char_class = false; + for (p = s->head->next; p; p = p->next) + { + int i; + count len = 0; + count new_length; + + switch (p->type) + { + case RE_NORMAL_CHAR: + len = 1; + break; + + case RE_RANGE: + assert (p->u.range.last_char >= p->u.range.first_char); + len = p->u.range.last_char - p->u.range.first_char + 1; + break; + + case RE_CHAR_CLASS: + s->has_char_class = true; + for (i = 0; i < N_CHARS; i++) + if (is_char_class_member (p->u.char_class, i)) + ++len; + switch (p->u.char_class) + { + case CC_UPPER: + case CC_LOWER: + break; + default: + s->has_restricted_char_class = true; + break; + } + break; + + case RE_EQUIV_CLASS: + for (i = 0; i < N_CHARS; i++) + if (is_equiv_class_member (p->u.equiv_code, i)) + ++len; + s->has_equiv_class = true; + break; + + case RE_REPEATED_CHAR: + if (p->u.repeated_char.repeat_count > 0) + len = p->u.repeated_char.repeat_count; + else + { + s->indefinite_repeat_element = p; + ++(s->n_indefinite_repeats); + } + break; + + default: + abort (); + break; + } + + /* Check for arithmetic overflow in computing length. Also, reject + any length greater than the maximum repeat count, in case the + length is later used to compute the repeat count for an + indefinite element. */ + new_length = length + len; + if (! (length <= new_length && new_length <= REPEAT_COUNT_MAXIMUM)) + error (EXIT_FAILURE, 0, _("too many characters in set")); + length = new_length; + } + + s->length = length; +} + +static void +get_s1_spec_stats (struct Spec_list *s1) +{ + get_spec_stats (s1); + if (complement) + s1->length = card_of_complement (s1); +} + +static void +get_s2_spec_stats (struct Spec_list *s2, count len_s1) +{ + get_spec_stats (s2); + if (len_s1 >= s2->length && s2->n_indefinite_repeats == 1) + { + s2->indefinite_repeat_element->u.repeated_char.repeat_count = + len_s1 - s2->length; + s2->length = len_s1; + } +} + +static void +spec_init (struct Spec_list *spec_list) +{ + struct List_element *new = xmalloc (sizeof *new); + spec_list->head = spec_list->tail = new; + spec_list->head->next = NULL; +} + +/* This function makes two passes over the argument string S. The first + one converts all \c and \ddd escapes to their one-byte representations. + The second constructs a linked specification list, SPEC_LIST, of the + characters and constructs that comprise the argument string. If either + of these passes detects an error, this function returns false. */ + +static bool +parse_str (char const *s, struct Spec_list *spec_list) +{ + struct E_string es; + bool ok = unquote (s, &es) && build_spec_list (&es, spec_list); + es_free (&es); + return ok; +} + +/* Given two specification lists, S1 and S2, and assuming that + S1->length > S2->length, append a single [c*n] element to S2 where c + is the last character in the expansion of S2 and n is the difference + between the two lengths. + Upon successful completion, S2->length is set to S1->length. The only + way this function can fail to make S2 as long as S1 is when S2 has + zero-length, since in that case, there is no last character to repeat. + So S2->length is required to be at least 1. + + Providing this functionality allows the user to do some pretty + non-BSD (and non-portable) things: For example, the command + tr -cs '[:upper:]0-9' '[:lower:]' + is almost guaranteed to give results that depend on your collating + sequence. */ + +static void +string2_extend (const struct Spec_list *s1, struct Spec_list *s2) +{ + struct List_element *p; + unsigned char char_to_repeat; + int i; + + assert (translating); + assert (s1->length > s2->length); + assert (s2->length > 0); + + p = s2->tail; + switch (p->type) + { + case RE_NORMAL_CHAR: + char_to_repeat = p->u.normal_char; + break; + case RE_RANGE: + char_to_repeat = p->u.range.last_char; + break; + case RE_CHAR_CLASS: + for (i = N_CHARS - 1; i >= 0; i--) + if (is_char_class_member (p->u.char_class, i)) + break; + assert (i >= 0); + char_to_repeat = i; + break; + + case RE_REPEATED_CHAR: + char_to_repeat = p->u.repeated_char.the_repeated_char; + break; + + case RE_EQUIV_CLASS: + /* This shouldn't happen, because validate exits with an error + if it finds an equiv class in string2 when translating. */ + abort (); + break; + + default: + abort (); + break; + } + + append_repeated_char (s2, char_to_repeat, s1->length - s2->length); + s2->length = s1->length; +} + +/* Return true if S is a non-empty list in which exactly one + character (but potentially, many instances of it) appears. + E.g., [X*] or xxxxxxxx. */ + +static bool +homogeneous_spec_list (struct Spec_list *s) +{ + int b, c; + + s->state = BEGIN_STATE; + + if ((b = get_next (s, NULL)) == -1) + return false; + + while ((c = get_next (s, NULL)) != -1) + if (c != b) + return false; + + return true; +} + +/* Die with an error message if S1 and S2 describe strings that + are not valid with the given command line switches. + A side effect of this function is that if a valid [c*] or + [c*0] construct appears in string2, it is converted to [c*n] + with a value for n that makes s2->length == s1->length. By + the same token, if the --truncate-set1 option is not + given, S2 may be extended. */ + +static void +validate (struct Spec_list *s1, struct Spec_list *s2) +{ + get_s1_spec_stats (s1); + if (s1->n_indefinite_repeats > 0) + { + error (EXIT_FAILURE, 0, + _("the [c*] repeat construct may not appear in string1")); + } + + if (s2) + { + get_s2_spec_stats (s2, s1->length); + + if (s2->n_indefinite_repeats > 1) + { + error (EXIT_FAILURE, 0, + _("only one [c*] repeat construct may appear in string2")); + } + + if (translating) + { + if (s2->has_equiv_class) + { + error (EXIT_FAILURE, 0, + _("[=c=] expressions may not appear in string2 \ +when translating")); + } + + if (s1->length > s2->length) + { + if (!truncate_set1) + { + /* string2 must be non-empty unless --truncate-set1 is + given or string1 is empty. */ + + if (s2->length == 0) + error (EXIT_FAILURE, 0, + _("when not truncating set1, string2 must be non-empty")); + string2_extend (s1, s2); + } + } + + if (complement && s1->has_char_class + && ! (s2->length == s1->length && homogeneous_spec_list (s2))) + { + error (EXIT_FAILURE, 0, + _("when translating with complemented character classes,\ +\nstring2 must map all characters in the domain to one")); + } + + if (s2->has_restricted_char_class) + { + error (EXIT_FAILURE, 0, + _("when translating, the only character classes that may \ +appear in\nstring2 are `upper' and `lower'")); + } + } + else + /* Not translating. */ + { + if (s2->n_indefinite_repeats > 0) + error (EXIT_FAILURE, 0, + _("the [c*] construct may appear in string2 only \ +when translating")); + } + } +} + +/* Read buffers of SIZE bytes via the function READER (if READER is + NULL, read from stdin) until EOF. When non-NULL, READER is either + read_and_delete or read_and_xlate. After each buffer is read, it is + processed and written to stdout. The buffers are processed so that + multiple consecutive occurrences of the same character in the input + stream are replaced by a single occurrence of that character if the + character is in the squeeze set. */ + +static void +squeeze_filter (char *buf, size_t size, size_t (*reader) (char *, size_t)) +{ + /* A value distinct from any character that may have been stored in a + buffer as the result of a block-read in the function squeeze_filter. */ + enum { NOT_A_CHAR = CHAR_MAX + 1 }; + + int char_to_squeeze = NOT_A_CHAR; + size_t i = 0; + size_t nr = 0; + + for (;;) + { + size_t begin; + + if (i >= nr) + { + nr = reader (buf, size); + if (nr == 0) + break; + i = 0; + } + + begin = i; + + if (char_to_squeeze == NOT_A_CHAR) + { + size_t out_len; + /* Here, by being a little tricky, we can get a significant + performance increase in most cases when the input is + reasonably large. Since tr will modify the input only + if two consecutive (and identical) input characters are + in the squeeze set, we can step by two through the data + when searching for a character in the squeeze set. This + means there may be a little more work in a few cases and + perhaps twice as much work in the worst cases where most + of the input is removed by squeezing repeats. But most + uses of this functionality seem to remove less than 20-30% + of the input. */ + for (; i < nr && !in_squeeze_set[to_uchar (buf[i])]; i += 2) + continue; + + /* There is a special case when i == nr and we've just + skipped a character (the last one in buf) that is in + the squeeze set. */ + if (i == nr && in_squeeze_set[to_uchar (buf[i - 1])]) + --i; + + if (i >= nr) + out_len = nr - begin; + else + { + char_to_squeeze = buf[i]; + /* We're about to output buf[begin..i]. */ + out_len = i - begin + 1; + + /* But since we stepped by 2 in the loop above, + out_len may be one too large. */ + if (i > 0 && buf[i - 1] == char_to_squeeze) + --out_len; + + /* Advance i to the index of first character to be + considered when looking for a char different from + char_to_squeeze. */ + ++i; + } + if (out_len > 0 + && fwrite (&buf[begin], 1, out_len, stdout) != out_len) + error (EXIT_FAILURE, errno, _("write error")); + } + + if (char_to_squeeze != NOT_A_CHAR) + { + /* Advance i to index of first char != char_to_squeeze + (or to nr if all the rest of the characters in this + buffer are the same as char_to_squeeze). */ + for (; i < nr && buf[i] == char_to_squeeze; i++) + continue; + if (i < nr) + char_to_squeeze = NOT_A_CHAR; + /* If (i >= nr) we've squeezed the last character in this buffer. + So now we have to read a new buffer and continue comparing + characters against char_to_squeeze. */ + } + } +} + +static size_t +plain_read (char *buf, size_t size) +{ + size_t nr = safe_read (STDIN_FILENO, buf, size); + if (nr == SAFE_READ_ERROR) + error (EXIT_FAILURE, errno, _("read error")); + return nr; +} + +/* Read buffers of SIZE bytes from stdin until one is found that + contains at least one character not in the delete set. Store + in the array BUF, all characters from that buffer that are not + in the delete set, and return the number of characters saved + or 0 upon EOF. */ + +static size_t +read_and_delete (char *buf, size_t size) +{ + size_t n_saved; + + /* This enclosing do-while loop is to make sure that + we don't return zero (indicating EOF) when we've + just deleted all the characters in a buffer. */ + do + { + size_t i; + size_t nr = plain_read (buf, size); + + if (nr == 0) + return 0; + + /* This first loop may be a waste of code, but gives much + better performance when no characters are deleted in + the beginning of a buffer. It just avoids the copying + of buf[i] into buf[n_saved] when it would be a NOP. */ + + for (i = 0; i < nr && !in_delete_set[to_uchar (buf[i])]; i++) + continue; + n_saved = i; + + for (++i; i < nr; i++) + if (!in_delete_set[to_uchar (buf[i])]) + buf[n_saved++] = buf[i]; + } + while (n_saved == 0); + + return n_saved; +} + +/* Read at most SIZE bytes from stdin into the array BUF. Then + perform the in-place and one-to-one mapping specified by the global + array `xlate'. Return the number of characters read, or 0 upon EOF. */ + +static size_t +read_and_xlate (char *buf, size_t size) +{ + size_t bytes_read = plain_read (buf, size); + size_t i; + + for (i = 0; i < bytes_read; i++) + buf[i] = xlate[to_uchar (buf[i])]; + + return bytes_read; +} + +/* Initialize a boolean membership set, IN_SET, with the character + values obtained by traversing the linked list of constructs S + using the function `get_next'. IN_SET is expected to have been + initialized to all zeros by the caller. If COMPLEMENT_THIS_SET + is true the resulting set is complemented. */ + +static void +set_initialize (struct Spec_list *s, bool complement_this_set, bool *in_set) +{ + int c; + size_t i; + + s->state = BEGIN_STATE; + while ((c = get_next (s, NULL)) != -1) + in_set[c] = true; + if (complement_this_set) + for (i = 0; i < N_CHARS; i++) + in_set[i] = (!in_set[i]); +} + +int +main (int argc, char **argv) +{ + int c; + int non_option_args; + int min_operands; + int max_operands; + struct Spec_list buf1, buf2; + struct Spec_list *s1 = &buf1; + struct Spec_list *s2 = &buf2; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((c = getopt_long (argc, argv, "+cCdst", long_options, NULL)) != -1) + { + switch (c) + { + case 'c': + case 'C': + complement = true; + break; + + case 'd': + delete = true; + break; + + case 's': + squeeze_repeats = true; + break; + + case 't': + truncate_set1 = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + break; + } + } + + non_option_args = argc - optind; + translating = (non_option_args == 2 && !delete); + min_operands = 1 + (delete == squeeze_repeats); + max_operands = 1 + (delete <= squeeze_repeats); + + if (non_option_args < min_operands) + { + if (non_option_args == 0) + error (0, 0, _("missing operand")); + else + { + error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); + fprintf (stderr, "%s\n", + _(squeeze_repeats + ? ("Two strings must be given when " + "both deleting and squeezing repeats.") + : "Two strings must be given when translating.")); + } + usage (EXIT_FAILURE); + } + + if (max_operands < non_option_args) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + max_operands])); + if (non_option_args == 2) + fprintf (stderr, "%s\n", + _("Only one string may be given when " + "deleting without squeezing repeats.")); + usage (EXIT_FAILURE); + } + + spec_init (s1); + if (!parse_str (argv[optind], s1)) + exit (EXIT_FAILURE); + + if (non_option_args == 2) + { + spec_init (s2); + if (!parse_str (argv[optind + 1], s2)) + exit (EXIT_FAILURE); + } + else + s2 = NULL; + + validate (s1, s2); + + /* Use binary I/O, since `tr' is sometimes used to transliterate + non-printable characters, or characters which are stripped away + by text-mode reads (like CR and ^Z). */ + if (O_BINARY && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + if (O_BINARY && ! isatty (STDOUT_FILENO)) + freopen (NULL, "wb", stdout); + + if (squeeze_repeats && non_option_args == 1) + { + set_initialize (s1, complement, in_squeeze_set); + squeeze_filter (io_buf, sizeof io_buf, plain_read); + } + else if (delete && non_option_args == 1) + { + set_initialize (s1, complement, in_delete_set); + + for (;;) + { + size_t nr = read_and_delete (io_buf, sizeof io_buf); + if (nr == 0) + break; + if (fwrite (io_buf, 1, nr, stdout) != nr) + error (EXIT_FAILURE, errno, _("write error")); + } + } + else if (squeeze_repeats && delete && non_option_args == 2) + { + set_initialize (s1, complement, in_delete_set); + set_initialize (s2, false, in_squeeze_set); + squeeze_filter (io_buf, sizeof io_buf, read_and_delete); + } + else if (translating) + { + if (complement) + { + int i; + bool *in_s1 = in_delete_set; + + set_initialize (s1, false, in_s1); + s2->state = BEGIN_STATE; + for (i = 0; i < N_CHARS; i++) + xlate[i] = i; + for (i = 0; i < N_CHARS; i++) + { + if (!in_s1[i]) + { + int ch = get_next (s2, NULL); + assert (ch != -1 || truncate_set1); + if (ch == -1) + { + /* This will happen when tr is invoked like e.g. + tr -cs A-Za-z0-9 '\012'. */ + break; + } + xlate[i] = ch; + } + } + assert (get_next (s2, NULL) == -1 || truncate_set1); + } + else + { + int c1, c2; + int i; + enum Upper_Lower_class class_s1; + enum Upper_Lower_class class_s2; + + for (i = 0; i < N_CHARS; i++) + xlate[i] = i; + s1->state = BEGIN_STATE; + s2->state = BEGIN_STATE; + for (;;) + { + c1 = get_next (s1, &class_s1); + c2 = get_next (s2, &class_s2); + + /* When constructing the translation array, either one of the + values returned by paired calls to get_next must be from + [:upper:] and the other is [:lower:], or neither can be from + upper or lower. */ + + if ((class_s1 == UL_NONE) != (class_s2 == UL_NONE)) + error (EXIT_FAILURE, 0, + _("misaligned [:upper:] and/or [:lower:] construct")); + + if (class_s1 == UL_LOWER && class_s2 == UL_UPPER) + { + for (i = 0; i < N_CHARS; i++) + if (islower (i)) + xlate[i] = toupper (i); + } + else if (class_s1 == UL_UPPER && class_s2 == UL_LOWER) + { + for (i = 0; i < N_CHARS; i++) + if (isupper (i)) + xlate[i] = tolower (i); + } + else if ((class_s1 == UL_LOWER && class_s2 == UL_LOWER) + || (class_s1 == UL_UPPER && class_s2 == UL_UPPER)) + { + /* POSIX says the behavior of `tr "[:upper:]" "[:upper:]"' + is undefined. Treat it as a no-op. */ + } + else + { + /* The following should have been checked by validate... */ + if (c1 == -1 || c2 == -1) + break; + xlate[c1] = c2; + } + } + assert (c1 == -1 || truncate_set1); + } + if (squeeze_repeats) + { + set_initialize (s2, false, in_squeeze_set); + squeeze_filter (io_buf, sizeof io_buf, read_and_xlate); + } + else + { + for (;;) + { + size_t bytes_read = read_and_xlate (io_buf, sizeof io_buf); + if (bytes_read == 0) + break; + if (fwrite (io_buf, 1, bytes_read, stdout) != bytes_read) + error (EXIT_FAILURE, errno, _("write error")); + } + } + } + + if (close (STDIN_FILENO) != 0) + error (EXIT_FAILURE, errno, _("standard input")); + + exit (EXIT_SUCCESS); +} diff --git a/src/true.c b/src/true.c new file mode 100644 index 0000000..55490f9 --- /dev/null +++ b/src/true.c @@ -0,0 +1,82 @@ +/* Exit with a status code indicating success. + Copyright (C) 1999-2003, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#include +#include +#include +#include "system.h" + +/* Act like "true" by default; false.c overrides this. */ +#ifndef EXIT_STATUS +# define EXIT_STATUS EXIT_SUCCESS +#endif + +#if EXIT_STATUS == EXIT_SUCCESS +# define PROGRAM_NAME "true" +#else +# define PROGRAM_NAME "false" +#endif + +#define AUTHORS "Jim Meyering" + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + printf (_("\ +Usage: %s [ignored command line arguments]\n\ + or: %s OPTION\n\ +"), + program_name, program_name); + printf ("%s\n\n", + _(EXIT_STATUS == EXIT_SUCCESS + ? "Exit with a status code indicating success." + : "Exit with a status code indicating failure.")); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + exit (status); +} + +int +main (int argc, char **argv) +{ + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + /* Recognize --help or --version only if it's the only command-line + argument. */ + if (argc == 2) + { + if (STREQ (argv[1], "--help")) + usage (EXIT_STATUS); + + if (STREQ (argv[1], "--version")) + version_etc (stdout, PROGRAM_NAME, GNU_PACKAGE, VERSION, AUTHORS, + (char *) NULL); + } + + exit (EXIT_STATUS); +} diff --git a/src/tsort.c b/src/tsort.c new file mode 100644 index 0000000..9393232 --- /dev/null +++ b/src/tsort.c @@ -0,0 +1,559 @@ +/* tsort - topological sort. + Copyright (C) 1998-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Mark Kettenis . */ + +/* The topological sort is done according to Algorithm T (Topological + sort) in Donald E. Knuth, The Art of Computer Programming, Volume + 1/Fundamental Algorithms, page 262. */ + +#include + +#include +#include +#include +#include + +#include "system.h" +#include "long-options.h" +#include "error.h" +#include "quote.h" +#include "readtokens.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "tsort" + +#define AUTHORS "Mark Kettenis" + +/* Token delimiters when reading from a file. */ +#define DELIM " \t\n" + +/* Members of the list of successors. */ +struct successor +{ + struct item *suc; + struct successor *next; +}; + +/* Each string is held in core as the head of a list of successors. */ +struct item +{ + const char *str; + struct item *left, *right; + int balance; /* -1, 0, or +1 */ + size_t count; + struct item *qlink; + struct successor *top; +}; + +/* The name this program was run with. */ +char *program_name; + +/* The head of the sorted list. */ +static struct item *head = NULL; + +/* The tail of the list of `zeros', strings that have no predecessors. */ +static struct item *zeros = NULL; + +/* Used for loop detection. */ +static struct item *loop = NULL; + +/* The number of strings to sort. */ +static size_t n_strings = 0; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION] [FILE]\n\ +Write totally ordered list consistent with the partial ordering in FILE.\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +"), program_name); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + + exit (status); +} + +/* Create a new item/node for STR. */ +static struct item * +new_item (const char *str) +{ + struct item *k = xmalloc (sizeof *k); + + k->str = (str ? xstrdup (str): NULL); + k->left = k->right = NULL; + k->balance = 0; + + /* T1. Initialize (COUNT[k] <- 0 and TOP[k] <- ^). */ + k->count = 0; + k->qlink = NULL; + k->top = NULL; + + return k; +} + +/* Search binary tree rooted at *ROOT for STR. Allocate a new tree if + *ROOT is NULL. Insert a node/item for STR if not found. Return + the node/item found/created for STR. + + This is done according to Algorithm A (Balanced tree search and + insertion) in Donald E. Knuth, The Art of Computer Programming, + Volume 3/Searching and Sorting, pages 455--457. */ + +static struct item * +search_item (struct item *root, const char *str) +{ + struct item *p, *q, *r, *s, *t; + int a; + + assert (root); + + /* Make sure the tree is not empty, since that is what the algorithm + below expects. */ + if (root->right == NULL) + return (root->right = new_item (str)); + + /* A1. Initialize. */ + t = root; + s = p = root->right; + + for (;;) + { + /* A2. Compare. */ + a = strcmp (str, p->str); + if (a == 0) + return p; + + /* A3 & A4. Move left & right. */ + if (a < 0) + q = p->left; + else + q = p->right; + + if (q == NULL) + { + /* A5. Insert. */ + q = new_item (str); + + /* A3 & A4. (continued). */ + if (a < 0) + p->left = q; + else + p->right = q; + + /* A6. Adjust balance factors. */ + assert (!STREQ (str, s->str)); + if (strcmp (str, s->str) < 0) + { + r = p = s->left; + a = -1; + } + else + { + r = p = s->right; + a = 1; + } + + while (p != q) + { + assert (!STREQ (str, p->str)); + if (strcmp (str, p->str) < 0) + { + p->balance = -1; + p = p->left; + } + else + { + p->balance = 1; + p = p->right; + } + } + + /* A7. Balancing act. */ + if (s->balance == 0 || s->balance == -a) + { + s->balance += a; + return q; + } + + if (r->balance == a) + { + /* A8. Single Rotation. */ + p = r; + if (a < 0) + { + s->left = r->right; + r->right = s; + } + else + { + s->right = r->left; + r->left = s; + } + s->balance = r->balance = 0; + } + else + { + /* A9. Double rotation. */ + if (a < 0) + { + p = r->right; + r->right = p->left; + p->left = r; + s->left = p->right; + p->right = s; + } + else + { + p = r->left; + r->left = p->right; + p->right = r; + s->right = p->left; + p->left = s; + } + + s->balance = 0; + r->balance = 0; + if (p->balance == a) + s->balance = -a; + else if (p->balance == -a) + r->balance = a; + p->balance = 0; + } + + /* A10. Finishing touch. */ + if (s == t->right) + t->right = p; + else + t->left = p; + + return q; + } + + /* A3 & A4. (continued). */ + if (q->balance) + { + t = p; + s = q; + } + + p = q; + } + + /* NOTREACHED */ +} + +/* Record the fact that J precedes K. */ + +static void +record_relation (struct item *j, struct item *k) +{ + struct successor *p; + + if (!STREQ (j->str, k->str)) + { + k->count++; + p = xmalloc (sizeof *p); + p->suc = k; + p->next = j->top; + j->top = p; + } +} + +static bool +count_items (struct item *unused ATTRIBUTE_UNUSED) +{ + n_strings++; + return false; +} + +static bool +scan_zeros (struct item *k) +{ + /* Ignore strings that have already been printed. */ + if (k->count == 0 && k->str) + { + if (head == NULL) + head = k; + else + zeros->qlink = k; + + zeros = k; + } + + return false; +} + +/* Try to detect the loop. If we have detected that K is part of a + loop, print the loop on standard error, remove a relation to break + the loop, and return true. + + The loop detection strategy is as follows: Realise that what we're + dealing with is essentially a directed graph. If we find an item + that is part of a graph that contains a cycle we traverse the graph + in backwards direction. In general there is no unique way to do + this, but that is no problem. If we encounter an item that we have + encountered before, we know that we've found a cycle. All we have + to do now is retrace our steps, printing out the items until we + encounter that item again. (This is not necessarily the item that + we started from originally.) Since the order in which the items + are stored in the tree is not related to the specified partial + ordering, we may need to walk the tree several times before the + loop has completely been constructed. If the loop was found, the + global variable LOOP will be NULL. */ + +static bool +detect_loop (struct item *k) +{ + if (k->count > 0) + { + /* K does not have to be part of a cycle. It is however part of + a graph that contains a cycle. */ + + if (loop == NULL) + /* Start traversing the graph at K. */ + loop = k; + else + { + struct successor **p = &k->top; + + while (*p) + { + if ((*p)->suc == loop) + { + if (k->qlink) + { + /* We have found a loop. Retrace our steps. */ + while (loop) + { + struct item *tmp = loop->qlink; + + fprintf (stderr, "%s: %s\n", program_name, + loop->str); + + /* Until we encounter K again. */ + if (loop == k) + { + /* Remove relation. */ + (*p)->suc->count--; + *p = (*p)->next; + break; + } + + /* Tidy things up since we might have to + detect another loop. */ + loop->qlink = NULL; + loop = tmp; + } + + while (loop) + { + struct item *tmp = loop->qlink; + + loop->qlink = NULL; + loop = tmp; + } + + /* Since we have found the loop, stop walking + the tree. */ + return true; + } + else + { + k->qlink = loop; + loop = k; + break; + } + } + + p = &(*p)->next; + } + } + } + + return false; +} + +/* Recurse (sub)tree rooted at ROOT, calling ACTION for each node. + Stop when ACTION returns true. */ + +static bool +recurse_tree (struct item *root, bool (*action) (struct item *)) +{ + if (root->left == NULL && root->right == NULL) + return (*action) (root); + else + { + if (root->left != NULL) + if (recurse_tree (root->left, action)) + return true; + if ((*action) (root)) + return true; + if (root->right != NULL) + if (recurse_tree (root->right, action)) + return true; + } + + return false; +} + +/* Walk the tree specified by the head ROOT, calling ACTION for + each node. */ + +static void +walk_tree (struct item *root, bool (*action) (struct item *)) +{ + if (root->right) + recurse_tree (root->right, action); +} + +/* Do a topological sort on FILE. Return true if successful. */ + +static bool +tsort (const char *file) +{ + bool ok = true; + struct item *root; + struct item *j = NULL; + struct item *k = NULL; + token_buffer tokenbuffer; + bool is_stdin = STREQ (file, "-"); + + /* Intialize the head of the tree will hold the strings we're sorting. */ + root = new_item (NULL); + + if (!is_stdin && ! freopen (file, "r", stdin)) + error (EXIT_FAILURE, errno, "%s", file); + + init_tokenbuffer (&tokenbuffer); + + while (1) + { + /* T2. Next Relation. */ + size_t len = readtoken (stdin, DELIM, sizeof (DELIM) - 1, &tokenbuffer); + if (len == (size_t) -1) + break; + + assert (len != 0); + + k = search_item (root, tokenbuffer.buffer); + if (j) + { + /* T3. Record the relation. */ + record_relation (j, k); + k = NULL; + } + + j = k; + } + + if (k != NULL) + error (EXIT_FAILURE, 0, _("%s: input contains an odd number of tokens"), + file); + + /* T1. Initialize (N <- n). */ + walk_tree (root, count_items); + + while (n_strings > 0) + { + /* T4. Scan for zeros. */ + walk_tree (root, scan_zeros); + + while (head) + { + struct successor *p = head->top; + + /* T5. Output front of queue. */ + printf ("%s\n", head->str); + head->str = NULL; /* Avoid printing the same string twice. */ + n_strings--; + + /* T6. Erase relations. */ + while (p) + { + p->suc->count--; + if (p->suc->count == 0) + { + zeros->qlink = p->suc; + zeros = p->suc; + } + + p = p->next; + } + + /* T7. Remove from queue. */ + head = head->qlink; + } + + /* T8. End of process. */ + if (n_strings > 0) + { + /* The input contains a loop. */ + error (0, 0, _("%s: input contains a loop:"), file); + ok = false; + + /* Print the loop and remove a relation to break it. */ + do + walk_tree (root, detect_loop); + while (loop); + } + } + + if (fclose (stdin) != 0) + error (EXIT_FAILURE, errno, "%s", + is_stdin ? _("standard input") : quote (file)); + + return ok; +} + +int +main (int argc, char **argv) +{ + bool ok; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (1 < argc - optind) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); + usage (EXIT_FAILURE); + } + + ok = tsort (optind == argc ? "-" : argv[optind]); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/tty.c b/src/tty.c new file mode 100644 index 0000000..5228e7a --- /dev/null +++ b/src/tty.c @@ -0,0 +1,129 @@ +/* tty -- print the name of the terminal connected to standard input + Copyright (C) 1990-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Displays "not a tty" if stdin is not a terminal. + Displays nothing if -s option is given. + Exit status 0 if stdin is a tty, 1 if not, 2 if usage error, + 3 if write error. + + Written by David MacKenzie . */ + +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "quote.h" + +/* Exit statuses. */ +enum + { + TTY_FAILURE = 2, + TTY_WRITE_ERROR = 3 + }; + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "tty" + +#define AUTHORS "David MacKenzie" + +/* The name under which this program was run. */ +char *program_name; + +/* If true, return an exit status but produce no output. */ +static bool silent; + +static struct option const longopts[] = +{ + {"silent", no_argument, NULL, 's'}, + {"quiet", no_argument, NULL, 's'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]...\n"), program_name); + fputs (_("\ +Print the file name of the terminal connected to standard input.\n\ +\n\ + -s, --silent, --quiet print nothing, only return an exit status\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + char *tty; + int optc; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (TTY_WRITE_ERROR); + atexit (close_stdout); + + silent = false; + + while ((optc = getopt_long (argc, argv, "s", longopts, NULL)) != -1) + { + switch (optc) + { + case 's': + silent = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (TTY_FAILURE); + } + } + + if (optind < argc) + error (0, 0, _("extra operand %s"), quote (argv[optind])); + + tty = ttyname (STDIN_FILENO); + if (!silent) + { + if (tty) + puts (tty); + else + puts (_("not a tty")); + } + + exit (isatty (STDIN_FILENO) ? EXIT_SUCCESS : EXIT_FAIL); +} diff --git a/src/uname.c b/src/uname.c new file mode 100644 index 0000000..0715e07 --- /dev/null +++ b/src/uname.c @@ -0,0 +1,325 @@ +/* uname -- print system information + + Copyright (C) 1989, 1992, 1993, 1996, 1997, 1999, 2000, 2001, 2002, + 2003, 2004, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by David MacKenzie */ + +#include +#include +#include +#include +#include + +#if HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H +# include +#endif + +#if HAVE_SYS_SYSCTL_H +# if HAVE_SYS_PARAM_H +# include /* needed for OpenBSD 3.0 */ +# endif +# include +# ifdef HW_MODEL +# ifdef HW_MACHINE_ARCH +/* E.g., FreeBSD 4.5, NetBSD 1.5.2 */ +# define UNAME_HARDWARE_PLATFORM HW_MODEL +# define UNAME_PROCESSOR HW_MACHINE_ARCH +# else +/* E.g., OpenBSD 3.0 */ +# define UNAME_PROCESSOR HW_MODEL +# endif +# endif +#endif + +#ifdef __APPLE__ +# include +# include +#endif + +#include "system.h" +#include "error.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "uname" + +#define AUTHORS "David MacKenzie" + +/* Values that are bitwise or'd into `toprint'. */ +/* Kernel name. */ +#define PRINT_KERNEL_NAME 1 + +/* Node name on a communications network. */ +#define PRINT_NODENAME 2 + +/* Kernel release. */ +#define PRINT_KERNEL_RELEASE 4 + +/* Kernel version. */ +#define PRINT_KERNEL_VERSION 8 + +/* Machine hardware name. */ +#define PRINT_MACHINE 16 + +/* Processor type. */ +#define PRINT_PROCESSOR 32 + +/* Hardware platform. */ +#define PRINT_HARDWARE_PLATFORM 64 + +/* Operating system. */ +#define PRINT_OPERATING_SYSTEM 128 + +/* The name this program was run with, for error messages. */ +char *program_name; + +static struct option const long_options[] = +{ + {"all", no_argument, NULL, 'a'}, + {"kernel-name", no_argument, NULL, 's'}, + {"sysname", no_argument, NULL, 's'}, /* Obsolescent. */ + {"nodename", no_argument, NULL, 'n'}, + {"kernel-release", no_argument, NULL, 'r'}, + {"release", no_argument, NULL, 'r'}, /* Obsolescent. */ + {"kernel-version", no_argument, NULL, 'v'}, + {"machine", no_argument, NULL, 'm'}, + {"processor", no_argument, NULL, 'p'}, + {"hardware-platform", no_argument, NULL, 'i'}, + {"operating-system", no_argument, NULL, 'o'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]...\n"), program_name); + fputs (_("\ +Print certain system information. With no OPTION, same as -s.\n\ +\n\ + -a, --all print all information, in the following order,\n\ + except omit -p and -i if unknown:\n\ + -s, --kernel-name print the kernel name\n\ + -n, --nodename print the network node hostname\n\ + -r, --kernel-release print the kernel release\n\ +"), stdout); + fputs (_("\ + -v, --kernel-version print the kernel version\n\ + -m, --machine print the machine hardware name\n\ + -p, --processor print the processor type or \"unknown\"\n\ + -i, --hardware-platform print the hardware platform or \"unknown\"\n\ + -o, --operating-system print the operating system\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Print ELEMENT, preceded by a space if something has already been + printed. */ + +static void +print_element (char const *element) +{ + static bool printed; + if (printed) + putchar (' '); + printed = true; + fputs (element, stdout); +} + +int +main (int argc, char **argv) +{ + int c; + static char const unknown[] = "unknown"; + + /* Mask indicating which elements to print. */ + unsigned int toprint = 0; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((c = getopt_long (argc, argv, "asnrvmpio", long_options, NULL)) != -1) + { + switch (c) + { + case 'a': + toprint = UINT_MAX; + break; + + case 's': + toprint |= PRINT_KERNEL_NAME; + break; + + case 'n': + toprint |= PRINT_NODENAME; + break; + + case 'r': + toprint |= PRINT_KERNEL_RELEASE; + break; + + case 'v': + toprint |= PRINT_KERNEL_VERSION; + break; + + case 'm': + toprint |= PRINT_MACHINE; + break; + + case 'p': + toprint |= PRINT_PROCESSOR; + break; + + case 'i': + toprint |= PRINT_HARDWARE_PLATFORM; + break; + + case 'o': + toprint |= PRINT_OPERATING_SYSTEM; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (argc != optind) + { + error (0, 0, _("extra operand %s"), quote (argv[optind])); + usage (EXIT_FAILURE); + } + + if (toprint == 0) + toprint = PRINT_KERNEL_NAME; + + if (toprint + & (PRINT_KERNEL_NAME | PRINT_NODENAME | PRINT_KERNEL_RELEASE + | PRINT_KERNEL_VERSION | PRINT_MACHINE)) + { + struct utsname name; + + if (uname (&name) == -1) + error (EXIT_FAILURE, errno, _("cannot get system name")); + + if (toprint & PRINT_KERNEL_NAME) + print_element (name.sysname); + if (toprint & PRINT_NODENAME) + print_element (name.nodename); + if (toprint & PRINT_KERNEL_RELEASE) + print_element (name.release); + if (toprint & PRINT_KERNEL_VERSION) + print_element (name.version); + if (toprint & PRINT_MACHINE) + print_element (name.machine); + } + + if (toprint & PRINT_PROCESSOR) + { + char const *element = unknown; +#if HAVE_SYSINFO && defined SI_ARCHITECTURE + { + static char processor[257]; + if (0 <= sysinfo (SI_ARCHITECTURE, processor, sizeof processor)) + element = processor; + } +#endif +#ifdef UNAME_PROCESSOR + if (element == unknown) + { + static char processor[257]; + size_t s = sizeof processor; + static int mib[] = { CTL_HW, UNAME_PROCESSOR }; + if (sysctl (mib, 2, processor, &s, 0, 0) >= 0) + element = processor; + +# ifdef __APPLE__ + /* This kludge works around a bug in Mac OS X. */ + if (element == unknown) + { + cpu_type_t cputype; + size_t s = sizeof cputype; + NXArchInfo const *ai; + if (sysctlbyname ("hw.cputype", &cputype, &s, NULL, 0) == 0 + && (ai = NXGetArchInfoFromCpuType (cputype, + CPU_SUBTYPE_MULTIPLE)) + != NULL) + element = ai->name; + + /* Hack "safely" around the ppc vs. powerpc return value. */ + if (cputype == CPU_TYPE_POWERPC + && strncmp (element, "ppc", 3) == 0) + element = "powerpc"; + } +# endif + } +#endif + if (! (toprint == UINT_MAX && element == unknown)) + print_element (element); + } + + if (toprint & PRINT_HARDWARE_PLATFORM) + { + char const *element = unknown; +#if HAVE_SYSINFO && defined SI_PLATFORM + { + static char hardware_platform[257]; + if (0 <= sysinfo (SI_PLATFORM, + hardware_platform, sizeof hardware_platform)) + element = hardware_platform; + } +#endif +#ifdef UNAME_HARDWARE_PLATFORM + if (element == unknown) + { + static char hardware_platform[257]; + size_t s = sizeof hardware_platform; + static int mib[] = { CTL_HW, UNAME_HARDWARE_PLATFORM }; + if (sysctl (mib, 2, hardware_platform, &s, 0, 0) >= 0) + element = hardware_platform; + } +#endif + if (! (toprint == UINT_MAX && element == unknown)) + print_element (element); + } + + if (toprint & PRINT_OPERATING_SYSTEM) + print_element (HOST_OPERATING_SYSTEM); + + putchar ('\n'); + + exit (EXIT_SUCCESS); +} diff --git a/src/unexpand.c b/src/unexpand.c new file mode 100644 index 0000000..cbceca0 --- /dev/null +++ b/src/unexpand.c @@ -0,0 +1,540 @@ +/* unexpand - convert blanks to tabs + Copyright (C) 89, 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* By default, convert only maximal strings of initial blanks and tabs + into tabs. + Preserves backspace characters in the output; they decrement the + column count for tab calculations. + The default action is equivalent to -8. + + Options: + --tabs=tab1[,tab2[,...]] + -t tab1[,tab2[,...]] + -tab1[,tab2[,...]] If only one tab stop is given, set the tabs tab1 + columns apart instead of the default 8. Otherwise, + set the tabs at columns tab1, tab2, etc. (numbered from + 0); preserve any blanks beyond the tab stops given. + --all + -a Use tabs wherever they would replace 2 or more blanks, + not just at the beginnings of lines. + + David MacKenzie */ + +#include + +#include +#include +#include +#include "system.h" +#include "error.h" +#include "quote.h" +#include "xstrndup.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "unexpand" + +#define AUTHORS "David MacKenzie" + +/* The number of bytes added at a time to the amount of memory + allocated for the output line. */ +#define OUTPUT_BLOCK 256 + +/* The name this program was run with. */ +char *program_name; + +/* If true, convert blanks even after nonblank characters have been + read on the line. */ +static bool convert_entire_line; + +/* If nonzero, the size of all tab stops. If zero, use `tab_list' instead. */ +static size_t tab_size; + +/* The maximum distance between tab stops. */ +static size_t max_column_width; + +/* Array of the explicit column numbers of the tab stops; + after `tab_list' is exhausted, the rest of the line is printed + unchanged. The first column is column 0. */ +static uintmax_t *tab_list; + +/* The number of allocated entries in `tab_list'. */ +static size_t n_tabs_allocated; + +/* The index of the first invalid element of `tab_list', + where the next element can be added. */ +static size_t first_free_tab; + +/* Null-terminated array of input filenames. */ +static char **file_list; + +/* Default for `file_list' if no files are given on the command line. */ +static char *stdin_argv[] = +{ + "-", NULL +}; + +/* True if we have ever read standard input. */ +static bool have_read_stdin; + +/* The desired exit status. */ +static int exit_status; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + CONVERT_FIRST_ONLY_OPTION = CHAR_MAX + 1 +}; + +static struct option const longopts[] = +{ + {"tabs", required_argument, NULL, 't'}, + {"all", no_argument, NULL, 'a'}, + {"first-only", no_argument, NULL, CONVERT_FIRST_ONLY_OPTION}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + fputs (_("\ +Convert blanks in each FILE to tabs, writing to standard output.\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -a, --all convert all blanks, instead of just initial blanks\n\ + --first-only convert only leading sequences of blanks (overrides -a)\n\ + -t, --tabs=N have tabs N characters apart instead of 8 (enables -a)\n\ + -t, --tabs=LIST use comma separated LIST of tab positions (enables -a)\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Add tab stop TABVAL to the end of `tab_list'. */ + +static void +add_tab_stop (uintmax_t tabval) +{ + uintmax_t prev_column = first_free_tab ? tab_list[first_free_tab - 1] : 0; + uintmax_t column_width = prev_column <= tabval ? tabval - prev_column : 0; + + if (first_free_tab == n_tabs_allocated) + tab_list = X2NREALLOC (tab_list, &n_tabs_allocated); + tab_list[first_free_tab++] = tabval; + + if (max_column_width < column_width) + { + if (SIZE_MAX < column_width) + error (EXIT_FAILURE, 0, _("tabs are too far apart")); + max_column_width = column_width; + } +} + +/* Add the comma or blank separated list of tab stops STOPS + to the list of tab stops. */ + +static void +parse_tab_stops (char const *stops) +{ + bool have_tabval = false; + uintmax_t tabval IF_LINT (= 0); + char const *num_start IF_LINT (= NULL); + bool ok = true; + + for (; *stops; stops++) + { + if (*stops == ',' || isblank (to_uchar (*stops))) + { + if (have_tabval) + add_tab_stop (tabval); + have_tabval = false; + } + else if (ISDIGIT (*stops)) + { + if (!have_tabval) + { + tabval = 0; + have_tabval = true; + num_start = stops; + } + + /* Detect overflow. */ + if (!DECIMAL_DIGIT_ACCUMULATE (tabval, *stops - '0', uintmax_t)) + { + size_t len = strspn (num_start, "0123456789"); + char *bad_num = xstrndup (num_start, len); + error (0, 0, _("tab stop is too large %s"), quote (bad_num)); + free (bad_num); + ok = false; + stops = num_start + len - 1; + } + } + else + { + error (0, 0, _("tab size contains invalid character(s): %s"), + quote (stops)); + ok = false; + break; + } + } + + if (!ok) + exit (EXIT_FAILURE); + + if (have_tabval) + add_tab_stop (tabval); +} + +/* Check that the list of tab stops TABS, with ENTRIES entries, + contains only nonzero, ascending values. */ + +static void +validate_tab_stops (uintmax_t const *tabs, size_t entries) +{ + uintmax_t prev_tab = 0; + size_t i; + + for (i = 0; i < entries; i++) + { + if (tabs[i] == 0) + error (EXIT_FAILURE, 0, _("tab size cannot be 0")); + if (tabs[i] <= prev_tab) + error (EXIT_FAILURE, 0, _("tab sizes must be ascending")); + prev_tab = tabs[i]; + } +} + +/* Close the old stream pointer FP if it is non-NULL, + and return a new one opened to read the next input file. + Open a filename of `-' as the standard input. + Return NULL if there are no more input files. */ + +static FILE * +next_file (FILE *fp) +{ + static char *prev_file; + char *file; + + if (fp) + { + if (ferror (fp)) + { + error (0, errno, "%s", prev_file); + exit_status = EXIT_FAILURE; + } + if (STREQ (prev_file, "-")) + clearerr (fp); /* Also clear EOF. */ + else if (fclose (fp) != 0) + { + error (0, errno, "%s", prev_file); + exit_status = EXIT_FAILURE; + } + } + + while ((file = *file_list++) != NULL) + { + if (STREQ (file, "-")) + { + have_read_stdin = true; + prev_file = file; + return stdin; + } + fp = fopen (file, "r"); + if (fp) + { + prev_file = file; + return fp; + } + error (0, errno, "%s", file); + exit_status = EXIT_FAILURE; + } + return NULL; +} + +/* Change blanks to tabs, writing to stdout. + Read each file in `file_list', in order. */ + +static void +unexpand (void) +{ + /* Input stream. */ + FILE *fp = next_file (NULL); + + /* The array of pending blanks. In non-POSIX locales, blanks can + include characters other than spaces, so the blanks must be + stored, not merely counted. */ + char *pending_blank; + + if (!fp) + return; + + /* The worst case is a non-blank character, then one blank, then a + tab stop, then MAX_COLUMN_WIDTH - 1 blanks, then a non-blank; so + allocate MAX_COLUMN_WIDTH bytes to store the blanks. */ + pending_blank = xmalloc (max_column_width); + + for (;;) + { + /* Input character, or EOF. */ + int c; + + /* If true, perform translations. */ + bool convert = true; + + + /* The following variables have valid values only when CONVERT + is true: */ + + /* Column of next input character. */ + uintmax_t column = 0; + + /* Column the next input tab stop is on. */ + uintmax_t next_tab_column = 0; + + /* Index in TAB_LIST of next tab stop to examine. */ + size_t tab_index = 0; + + /* If true, the first pending blank came just before a tab stop. */ + bool one_blank_before_tab_stop = false; + + /* If true, the previous input character was a blank. This is + initially true, since initial strings of blanks are treated + as if the line was preceded by a blank. */ + bool prev_blank = true; + + /* Number of pending columns of blanks. */ + size_t pending = 0; + + + /* Convert a line of text. */ + + do + { + while ((c = getc (fp)) < 0 && (fp = next_file (fp))) + continue; + + if (convert) + { + bool blank = !! isblank (c); + + if (blank) + { + if (next_tab_column <= column) + { + if (tab_size) + next_tab_column = + column + (tab_size - column % tab_size); + else + for (;;) + if (tab_index == first_free_tab) + { + convert = false; + break; + } + else + { + uintmax_t tab = tab_list[tab_index++]; + if (column < tab) + { + next_tab_column = tab; + break; + } + } + } + + if (convert) + { + if (next_tab_column < column) + error (EXIT_FAILURE, 0, _("input line is too long")); + + if (c == '\t') + { + column = next_tab_column; + + /* Discard pending blanks, unless it was a single + blank just before the previous tab stop. */ + if (! (pending == 1 && one_blank_before_tab_stop)) + { + pending = 0; + one_blank_before_tab_stop = false; + } + } + else + { + column++; + + if (! (prev_blank && column == next_tab_column)) + { + /* It is not yet known whether the pending blanks + will be replaced by tabs. */ + if (column == next_tab_column) + one_blank_before_tab_stop = true; + pending_blank[pending++] = c; + prev_blank = true; + continue; + } + + /* Replace the pending blanks by a tab or two. */ + pending_blank[0] = c = '\t'; + pending = one_blank_before_tab_stop; + } + } + } + else if (c == '\b') + { + /* Go back one column, and force recalculation of the + next tab stop. */ + column -= !!column; + next_tab_column = column; + tab_index -= !!tab_index; + } + else + { + column++; + if (!column) + error (EXIT_FAILURE, 0, _("input line is too long")); + } + + if (pending) + { + if (fwrite (pending_blank, 1, pending, stdout) != pending) + error (EXIT_FAILURE, errno, _("write error")); + pending = 0; + one_blank_before_tab_stop = false; + } + + prev_blank = blank; + convert &= convert_entire_line | blank; + } + + if (c < 0) + { + free (pending_blank); + return; + } + + if (putchar (c) < 0) + error (EXIT_FAILURE, errno, _("write error")); + } + while (c != '\n'); + } +} + +int +main (int argc, char **argv) +{ + bool have_tabval = false; + uintmax_t tabval IF_LINT (= 0); + int c; + + /* If true, cancel the effect of any -a (explicit or implicit in -t), + so that only leading blanks will be considered. */ + bool convert_first_only = false; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + have_read_stdin = false; + exit_status = EXIT_SUCCESS; + convert_entire_line = false; + tab_list = NULL; + first_free_tab = 0; + + while ((c = getopt_long (argc, argv, ",0123456789at:", longopts, NULL)) + != -1) + { + switch (c) + { + case '?': + usage (EXIT_FAILURE); + case 'a': + convert_entire_line = true; + break; + case 't': + convert_entire_line = true; + parse_tab_stops (optarg); + break; + case CONVERT_FIRST_ONLY_OPTION: + convert_first_only = true; + break; + case ',': + if (have_tabval) + add_tab_stop (tabval); + have_tabval = false; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + if (!have_tabval) + { + tabval = 0; + have_tabval = true; + } + if (!DECIMAL_DIGIT_ACCUMULATE (tabval, c - '0', uintmax_t)) + error (EXIT_FAILURE, 0, _("tab stop value is too large")); + break; + } + } + + if (convert_first_only) + convert_entire_line = false; + + if (have_tabval) + add_tab_stop (tabval); + + validate_tab_stops (tab_list, first_free_tab); + + if (first_free_tab == 0) + tab_size = max_column_width = 8; + else if (first_free_tab == 1) + tab_size = tab_list[0]; + else + tab_size = 0; + + file_list = (optind < argc ? &argv[optind] : stdin_argv); + + unexpand (); + + if (have_read_stdin && fclose (stdin) != 0) + error (EXIT_FAILURE, errno, "-"); + + exit (exit_status); +} diff --git a/src/uniq.c b/src/uniq.c new file mode 100644 index 0000000..6c38ed8 --- /dev/null +++ b/src/uniq.c @@ -0,0 +1,552 @@ +/* uniq -- remove duplicate lines from a sorted file + Copyright (C) 86, 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Richard Stallman and David MacKenzie. */ + +#include + +#include +#include +#include + +#include "system.h" +#include "argmatch.h" +#include "linebuffer.h" +#include "error.h" +#include "hard-locale.h" +#include "posixver.h" +#include "quote.h" +#include "xmemcoll.h" +#include "xstrtol.h" +#include "memcasecmp.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "uniq" + +#define AUTHORS "Richard Stallman", "David MacKenzie" + +#define SWAP_LINES(A, B) \ + do \ + { \ + struct linebuffer *_tmp; \ + _tmp = (A); \ + (A) = (B); \ + (B) = _tmp; \ + } \ + while (0) + +/* The name this program was run with. */ +char *program_name; + +/* True if the LC_COLLATE locale is hard. */ +static bool hard_LC_COLLATE; + +/* Number of fields to skip on each line when doing comparisons. */ +static size_t skip_fields; + +/* Number of chars to skip after skipping any fields. */ +static size_t skip_chars; + +/* Number of chars to compare. */ +static size_t check_chars; + +enum countmode +{ + count_occurrences, /* -c Print count before output lines. */ + count_none /* Default. Do not print counts. */ +}; + +/* Whether and how to precede the output lines with a count of the number of + times they occurred in the input. */ +static enum countmode countmode; + +/* Which lines to output: unique lines, the first of a group of + repeated lines, and the second and subsequented of a group of + repeated lines. */ +static bool output_unique; +static bool output_first_repeated; +static bool output_later_repeated; + +/* If true, ignore case when comparing. */ +static bool ignore_case; + +enum delimit_method +{ + /* No delimiters output. --all-repeated[=none] */ + DM_NONE, + + /* Delimiter precedes all groups. --all-repeated=prepend */ + DM_PREPEND, + + /* Delimit all groups. --all-repeated=separate */ + DM_SEPARATE +}; + +static char const *const delimit_method_string[] = +{ + "none", "prepend", "separate", NULL +}; + +static enum delimit_method const delimit_method_map[] = +{ + DM_NONE, DM_PREPEND, DM_SEPARATE +}; + +/* Select whether/how to delimit groups of duplicate lines. */ +static enum delimit_method delimit_groups; + +static struct option const longopts[] = +{ + {"count", no_argument, NULL, 'c'}, + {"repeated", no_argument, NULL, 'd'}, + {"all-repeated", optional_argument, NULL, 'D'}, + {"ignore-case", no_argument, NULL, 'i'}, + {"unique", no_argument, NULL, 'u'}, + {"skip-fields", required_argument, NULL, 'f'}, + {"skip-chars", required_argument, NULL, 's'}, + {"check-chars", required_argument, NULL, 'w'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [INPUT [OUTPUT]]\n\ +"), + program_name); + fputs (_("\ +Discard all but one of successive identical lines from INPUT (or\n\ +standard input), writing to OUTPUT (or standard output).\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -c, --count prefix lines by the number of occurrences\n\ + -d, --repeated only print duplicate lines\n\ +"), stdout); + fputs (_("\ + -D, --all-repeated[=delimit-method] print all duplicate lines\n\ + delimit-method={none(default),prepend,separate}\n\ + Delimiting is done with blank lines.\n\ + -f, --skip-fields=N avoid comparing the first N fields\n\ + -i, --ignore-case ignore differences in case when comparing\n\ + -s, --skip-chars=N avoid comparing the first N characters\n\ + -u, --unique only print unique lines\n\ +"), stdout); + fputs (_("\ + -w, --check-chars=N compare no more than N characters in lines\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +A field is a run of whitespace, then non-whitespace characters.\n\ +Fields are skipped before chars.\n\ +"), stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* Convert OPT to size_t, reporting an error using MSGID if OPT is + invalid. Silently convert too-large values to SIZE_MAX. */ + +static size_t +size_opt (char const *opt, char const *msgid) +{ + unsigned long int size; + verify (SIZE_MAX <= ULONG_MAX); + + switch (xstrtoul (opt, NULL, 10, &size, "")) + { + case LONGINT_OK: + case LONGINT_OVERFLOW: + break; + + default: + error (EXIT_FAILURE, 0, "%s: %s", opt, _(msgid)); + } + + return MIN (size, SIZE_MAX); +} + +/* Given a linebuffer LINE, + return a pointer to the beginning of the line's field to be compared. */ + +static char * +find_field (const struct linebuffer *line) +{ + size_t count; + char *lp = line->buffer; + size_t size = line->length - 1; + size_t i = 0; + + for (count = 0; count < skip_fields && i < size; count++) + { + while (i < size && isblank (lp[i])) + i++; + while (i < size && !isblank (lp[i])) + i++; + } + + for (count = 0; count < skip_chars && i < size; count++) + i++; + + return lp + i; +} + +/* Return false if two strings OLD and NEW match, true if not. + OLD and NEW point not to the beginnings of the lines + but rather to the beginnings of the fields to compare. + OLDLEN and NEWLEN are their lengths. */ + +static bool +different (char *old, char *new, size_t oldlen, size_t newlen) +{ + if (check_chars < oldlen) + oldlen = check_chars; + if (check_chars < newlen) + newlen = check_chars; + + if (ignore_case) + { + /* FIXME: This should invoke strcoll somehow. */ + return oldlen != newlen || memcasecmp (old, new, oldlen); + } + else if (hard_LC_COLLATE) + return xmemcoll (old, oldlen, new, newlen) != 0; + else + return oldlen != newlen || memcmp (old, new, oldlen); +} + +/* Output the line in linebuffer LINE to standard output + provided that the switches say it should be output. + MATCH is true if the line matches the previous line. + If requested, print the number of times it occurred, as well; + LINECOUNT + 1 is the number of times that the line occurred. */ + +static void +writeline (struct linebuffer const *line, + bool match, uintmax_t linecount) +{ + if (! (linecount == 0 ? output_unique + : !match ? output_first_repeated + : output_later_repeated)) + return; + + if (countmode == count_occurrences) + printf ("%7" PRIuMAX " ", linecount + 1); + + fwrite (line->buffer, sizeof (char), line->length, stdout); +} + +/* Process input file INFILE with output to OUTFILE. + If either is "-", use the standard I/O stream for it instead. */ + +static void +check_file (const char *infile, const char *outfile) +{ + struct linebuffer lb1, lb2; + struct linebuffer *thisline, *prevline; + + if (! (STREQ (infile, "-") || freopen (infile, "r", stdin))) + error (EXIT_FAILURE, errno, "%s", infile); + if (! (STREQ (outfile, "-") || freopen (outfile, "w", stdout))) + error (EXIT_FAILURE, errno, "%s", outfile); + + thisline = &lb1; + prevline = &lb2; + + initbuffer (thisline); + initbuffer (prevline); + + /* The duplication in the following `if' and `else' blocks is an + optimization to distinguish the common case (in which none of + the following options has been specified: --count, -repeated, + --all-repeated, --unique) from the others. In the common case, + this optimization lets uniq output each different line right away, + without waiting to see if the next one is different. */ + + if (output_unique && output_first_repeated && countmode == count_none) + { + char *prevfield IF_LINT (= NULL); + size_t prevlen IF_LINT (= 0); + + while (!feof (stdin)) + { + char *thisfield; + size_t thislen; + if (readlinebuffer (thisline, stdin) == 0) + break; + thisfield = find_field (thisline); + thislen = thisline->length - 1 - (thisfield - thisline->buffer); + if (prevline->length == 0 + || different (thisfield, prevfield, thislen, prevlen)) + { + fwrite (thisline->buffer, sizeof (char), + thisline->length, stdout); + + SWAP_LINES (prevline, thisline); + prevfield = thisfield; + prevlen = thislen; + } + } + } + else + { + char *prevfield; + size_t prevlen; + uintmax_t match_count = 0; + bool first_delimiter = true; + + if (readlinebuffer (prevline, stdin) == 0) + goto closefiles; + prevfield = find_field (prevline); + prevlen = prevline->length - 1 - (prevfield - prevline->buffer); + + while (!feof (stdin)) + { + bool match; + char *thisfield; + size_t thislen; + if (readlinebuffer (thisline, stdin) == 0) + { + if (ferror (stdin)) + goto closefiles; + break; + } + thisfield = find_field (thisline); + thislen = thisline->length - 1 - (thisfield - thisline->buffer); + match = !different (thisfield, prevfield, thislen, prevlen); + match_count += match; + + if (match_count == UINTMAX_MAX) + { + if (count_occurrences) + error (EXIT_FAILURE, 0, _("too many repeated lines")); + match_count--; + } + + if (delimit_groups != DM_NONE) + { + if (!match) + { + if (match_count) /* a previous match */ + first_delimiter = false; /* Only used when DM_SEPARATE */ + } + else if (match_count == 1) + { + if ((delimit_groups == DM_PREPEND) + || (delimit_groups == DM_SEPARATE + && !first_delimiter)) + putchar ('\n'); + } + } + + if (!match || output_later_repeated) + { + writeline (prevline, match, match_count); + SWAP_LINES (prevline, thisline); + prevfield = thisfield; + prevlen = thislen; + if (!match) + match_count = 0; + } + } + + writeline (prevline, false, match_count); + } + + closefiles: + if (ferror (stdin) || fclose (stdin) != 0) + error (EXIT_FAILURE, 0, _("error reading %s"), infile); + + /* stdout is handled via the atexit-invoked close_stdout function. */ + + free (lb1.buffer); + free (lb2.buffer); +} + +enum Skip_field_option_type + { + SFO_NONE, + SFO_OBSOLETE, + SFO_NEW + }; + +int +main (int argc, char **argv) +{ + int optc = 0; + bool posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL); + enum Skip_field_option_type skip_field_option_type = SFO_NONE; + int nfiles = 0; + char const *file[2]; + + file[0] = file[1] = "-"; + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + hard_LC_COLLATE = hard_locale (LC_COLLATE); + + atexit (close_stdout); + + skip_chars = 0; + skip_fields = 0; + check_chars = SIZE_MAX; + output_unique = output_first_repeated = true; + output_later_repeated = false; + countmode = count_none; + delimit_groups = DM_NONE; + + for (;;) + { + /* Parse an operand with leading "+" as a file after "--" was + seen; or if pedantic and a file was seen; or if not + obsolete. */ + + if (optc == -1 + || (posixly_correct && nfiles != 0) + || ((optc = getopt_long (argc, argv, + "-0123456789Dcdf:is:uw:", longopts, NULL)) + == -1)) + { + if (argc <= optind) + break; + if (nfiles == 2) + { + error (0, 0, _("extra operand %s"), quote (argv[optind])); + usage (EXIT_FAILURE); + } + file[nfiles++] = argv[optind++]; + } + else switch (optc) + { + case 1: + { + unsigned long int size; + if (optarg[0] == '+' + && posix2_version () < 200112 + && xstrtoul (optarg, NULL, 10, &size, "") == LONGINT_OK + && size <= SIZE_MAX) + skip_chars = size; + else if (nfiles == 2) + { + error (0, 0, _("extra operand %s"), quote (optarg)); + usage (EXIT_FAILURE); + } + else + file[nfiles++] = optarg; + } + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + if (skip_field_option_type == SFO_NEW) + skip_fields = 0; + + if (!DECIMAL_DIGIT_ACCUMULATE (skip_fields, optc - '0', size_t)) + skip_fields = SIZE_MAX; + + skip_field_option_type = SFO_OBSOLETE; + } + break; + + case 'c': + countmode = count_occurrences; + break; + + case 'd': + output_unique = false; + break; + + case 'D': + output_unique = false; + output_later_repeated = true; + if (optarg == NULL) + delimit_groups = DM_NONE; + else + delimit_groups = XARGMATCH ("--all-repeated", optarg, + delimit_method_string, + delimit_method_map); + break; + + case 'f': + skip_field_option_type = SFO_NEW; + skip_fields = size_opt (optarg, + N_("invalid number of fields to skip")); + break; + + case 'i': + ignore_case = true; + break; + + case 's': + skip_chars = size_opt (optarg, + N_("invalid number of bytes to skip")); + break; + + case 'u': + output_first_repeated = false; + break; + + case 'w': + check_chars = size_opt (optarg, + N_("invalid number of bytes to compare")); + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (countmode == count_occurrences && output_later_repeated) + { + error (0, 0, + _("printing all duplicated lines and repeat counts is meaningless")); + usage (EXIT_FAILURE); + } + + check_file (file[0], file[1]); + + exit (EXIT_SUCCESS); +} diff --git a/src/unlink.c b/src/unlink.c new file mode 100644 index 0000000..7255076 --- /dev/null +++ b/src/unlink.c @@ -0,0 +1,94 @@ +/* unlink utility for GNU. + Copyright (C) 2001, 2002, 2004 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Michael Stone */ + +/* Implementation overview: + + Simply call the system 'unlink' function */ + +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "long-options.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "unlink" + +#define AUTHORS "Michael Stone" + +/* Name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s FILE\n\ + or: %s OPTION\n"), program_name, program_name); + fputs (_("Call the unlink function to remove the specified FILE.\n\n"), + stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (argc < optind + 1) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + if (optind + 1 < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); + usage (EXIT_FAILURE); + } + + if (unlink (argv[optind]) != 0) + error (EXIT_FAILURE, errno, _("cannot unlink %s"), quote (argv[optind])); + + exit (EXIT_SUCCESS); +} diff --git a/src/uptime.c b/src/uptime.c new file mode 100644 index 0000000..6b2a724 --- /dev/null +++ b/src/uptime.c @@ -0,0 +1,245 @@ +/* GNU's uptime. + Copyright (C) 1992-2002, 2004, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Created by hacking who.c by Kaveh Ghazi ghazi@caip.rutgers.edu. */ + +#include +#include +#include + +#include +#include "system.h" + +#if HAVE_SYSCTL && HAVE_SYS_SYSCTL_H +# include +#endif + +#if HAVE_OS_H +# include +#endif + +#include "c-strtod.h" +#include "error.h" +#include "long-options.h" +#include "quote.h" +#include "readutmp.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "uptime" + +#define AUTHORS "Joseph Arceneaux", "David MacKenzie", "Kaveh Ghazi" + +int getloadavg (); + +/* The name this program was run with. */ +char *program_name; + +static void +print_uptime (size_t n, const STRUCT_UTMP *this) +{ + size_t entries = 0; + time_t boot_time = 0; + time_t time_now; + time_t uptime = 0; + long int updays; + int uphours; + int upmins; + struct tm *tmn; + double avg[3]; + int loads; +#ifdef HAVE_PROC_UPTIME + FILE *fp; + + fp = fopen ("/proc/uptime", "r"); + if (fp != NULL) + { + char buf[BUFSIZ]; + char *b = fgets (buf, BUFSIZ, fp); + if (b == buf) + { + char *end_ptr; + double upsecs = c_strtod (buf, &end_ptr); + if (buf != end_ptr) + uptime = (0 <= upsecs && upsecs < TYPE_MAXIMUM (time_t) + ? upsecs : -1); + } + + fclose (fp); + } +#endif /* HAVE_PROC_UPTIME */ + +#if HAVE_SYSCTL && defined CTL_KERN && defined KERN_BOOTTIME + { + /* FreeBSD specific: fetch sysctl "kern.boottime". */ + static int request[2] = { CTL_KERN, KERN_BOOTTIME }; + struct timeval result; + size_t result_len = sizeof result; + + if (sysctl (request, 2, &result, &result_len, NULL, 0) >= 0) + boot_time = result.tv_sec; + } +#endif + +#if HAVE_OS_H /* BeOS */ + { + system_info si; + + get_system_info (&si); + boot_time = si.boot_time / 1000000; + } +#endif + +#if HAVE_UTMPX_H || HAVE_UTMP_H + /* Loop through all the utmp entries we just read and count up the valid + ones, also in the process possibly gleaning boottime. */ + while (n--) + { + entries += IS_USER_PROCESS (this); + if (UT_TYPE_BOOT_TIME (this)) + boot_time = UT_TIME_MEMBER (this); + ++this; + } +#endif + time_now = time (NULL); +#if defined HAVE_PROC_UPTIME + if (uptime == 0) +#endif + { + if (boot_time == 0) + error (EXIT_FAILURE, errno, _("couldn't get boot time")); + uptime = time_now - boot_time; + } + updays = uptime / 86400; + uphours = (uptime - (updays * 86400)) / 3600; + upmins = (uptime - (updays * 86400) - (uphours * 3600)) / 60; + tmn = localtime (&time_now); + if (tmn) + printf (_(" %2d:%02d%s up "), + ((tmn->tm_hour % 12) == 0 ? 12 : tmn->tm_hour % 12), + /* FIXME: use strftime, not am, pm. Uli reports that + the german translation is meaningless. */ + tmn->tm_min, (tmn->tm_hour < 12 ? _("am") : _("pm"))); + else + printf (_(" ??:???? up ")); + if (uptime == (time_t) -1) + printf (_("???? days ??:??, ")); + else + { + if (0 < updays) + printf (ngettext ("%ld day", "%ld days", select_plural (updays)), + updays); + printf (" %2d:%02d, ", uphours, upmins); + } + printf (ngettext ("%lu user", "%lu users", entries), + (unsigned long int) entries); + +#if defined HAVE_GETLOADAVG || defined C_GETLOADAVG + loads = getloadavg (avg, 3); +#else + loads = -1; +#endif + + if (loads == -1) + putchar ('\n'); + else + { + if (loads > 0) + printf (_(", load average: %.2f"), avg[0]); + if (loads > 1) + printf (", %.2f", avg[1]); + if (loads > 2) + printf (", %.2f", avg[2]); + if (loads > 0) + putchar ('\n'); + } +} + +/* Display the system uptime and the number of users on the system, + according to utmp file FILENAME. Use read_utmp OPTIONS to read the + utmp file. */ + +static void +uptime (const char *filename, int options) +{ + size_t n_users; + STRUCT_UTMP *utmp_buf; + +#if HAVE_UTMPX_H || HAVE_UTMP_H + if (read_utmp (filename, &n_users, &utmp_buf, options) != 0) + error (EXIT_FAILURE, errno, "%s", filename); +#endif + + print_uptime (n_users, utmp_buf); +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... [ FILE ]\n"), program_name); + printf (_("\ +Print the current time, the length of time the system has been up,\n\ +the number of users on the system, and the average number of jobs\n\ +in the run queue over the last 1, 5 and 15 minutes.\n\ +If FILE is not specified, use %s. %s as FILE is common.\n\ +\n\ +"), + UTMP_FILE, WTMP_FILE); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + switch (argc - optind) + { + case 0: /* uptime */ + uptime (UTMP_FILE, READ_UTMP_CHECK_PIDS); + break; + + case 1: /* uptime */ + uptime (argv[optind], 0); + break; + + default: /* lose */ + error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); + usage (EXIT_FAILURE); + } + + exit (EXIT_SUCCESS); +} diff --git a/src/users.c b/src/users.c new file mode 100644 index 0000000..dba4701 --- /dev/null +++ b/src/users.c @@ -0,0 +1,154 @@ +/* GNU's users. + Copyright (C) 1992-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by jla; revised by djm */ + +#include +#include +#include + +#include +#include "system.h" + +#include "error.h" +#include "long-options.h" +#include "quote.h" +#include "readutmp.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "users" + +#define AUTHORS "Joseph Arceneaux", "David MacKenzie" + +/* The name this program was run with. */ +char *program_name; + +static int +userid_compare (const void *v_a, const void *v_b) +{ + char **a = (char **) v_a; + char **b = (char **) v_b; + return strcmp (*a, *b); +} + +static void +list_entries_users (size_t n, const STRUCT_UTMP *this) +{ + char **u = xnmalloc (n, sizeof *u); + size_t i; + size_t n_entries = 0; + + while (n--) + { + if (IS_USER_PROCESS (this)) + { + char *trimmed_name; + + trimmed_name = extract_trimmed_name (this); + + u[n_entries] = trimmed_name; + ++n_entries; + } + this++; + } + + qsort (u, n_entries, sizeof (u[0]), userid_compare); + + for (i = 0; i < n_entries; i++) + { + char c = (i < n_entries - 1 ? ' ' : '\n'); + fputs (u[i], stdout); + putchar (c); + } + + for (i = 0; i < n_entries; i++) + free (u[i]); + free (u); +} + +/* Display a list of users on the system, according to utmp file FILENAME. + Use read_utmp OPTIONS to read FILENAME. */ + +static void +users (const char *filename, int options) +{ + size_t n_users; + STRUCT_UTMP *utmp_buf; + + if (read_utmp (filename, &n_users, &utmp_buf, options) != 0) + error (EXIT_FAILURE, errno, "%s", filename); + + list_entries_users (n_users, utmp_buf); + + free (utmp_buf); +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... [ FILE ]\n"), program_name); + printf (_("\ +Output who is currently logged in according to FILE.\n\ +If FILE is not specified, use %s. %s as FILE is common.\n\ +\n\ +"), + UTMP_FILE, WTMP_FILE); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + switch (argc - optind) + { + case 0: /* users */ + users (UTMP_FILE, READ_UTMP_CHECK_PIDS); + break; + + case 1: /* users */ + users (argv[optind], 0); + break; + + default: /* lose */ + error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); + usage (EXIT_FAILURE); + } + + exit (EXIT_SUCCESS); +} diff --git a/src/wc.c b/src/wc.c new file mode 100644 index 0000000..332f32d --- /dev/null +++ b/src/wc.c @@ -0,0 +1,704 @@ +/* wc - print the number of lines, words, and bytes in files + Copyright (C) 85, 91, 1995-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Paul Rubin, phr@ocf.berkeley.edu + and David MacKenzie, djm@gnu.ai.mit.edu. */ + +#include + +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "inttostr.h" +#include "quote.h" +#include "readtokens0.h" +#include "safe-read.h" +#include "wcwidth.h" + +#if !defined iswspace && !HAVE_ISWSPACE +# define iswspace(wc) \ + ((wc) == to_uchar (wc) && isspace (to_uchar (wc))) +#endif + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "wc" + +#define AUTHORS "Paul Rubin", "David MacKenzie" + +/* Size of atomic reads. */ +#define BUFFER_SIZE (16 * 1024) + +/* The name this program was run with. */ +char *program_name; + +/* Cumulative number of lines, words, chars and bytes in all files so far. + max_line_length is the maximum over all files processed so far. */ +static uintmax_t total_lines; +static uintmax_t total_words; +static uintmax_t total_chars; +static uintmax_t total_bytes; +static uintmax_t max_line_length; + +/* Which counts to print. */ +static bool print_lines, print_words, print_chars, print_bytes; +static bool print_linelength; + +/* The print width of each count. */ +static int number_width; + +/* True if we have ever read the standard input. */ +static bool have_read_stdin; + +/* The result of calling fstat or stat on a file descriptor or file. */ +struct fstatus +{ + /* If positive, fstat or stat has not been called yet. Otherwise, + this is the value returned from fstat or stat. */ + int failed; + + /* If FAILED is zero, this is the file's status. */ + struct stat st; +}; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + FILES0_FROM_OPTION = CHAR_MAX + 1 +}; + +static struct option const longopts[] = +{ + {"bytes", no_argument, NULL, 'c'}, + {"chars", no_argument, NULL, 'm'}, + {"lines", no_argument, NULL, 'l'}, + {"words", no_argument, NULL, 'w'}, + {"files0-from", required_argument, NULL, FILES0_FROM_OPTION}, + {"max-line-length", no_argument, NULL, 'L'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ + or: %s [OPTION]... --files0-from=F\n\ +"), + program_name, program_name); + fputs (_("\ +Print newline, word, and byte counts for each FILE, and a total line if\n\ +more than one FILE is specified. With no FILE, or when FILE is -,\n\ +read standard input.\n\ + -c, --bytes print the byte counts\n\ + -m, --chars print the character counts\n\ + -l, --lines print the newline counts\n\ +"), stdout); + fputs (_("\ + --files0-from=F read input from the files specified by\n\ + NUL-terminated names in file F\n\ + -L, --max-line-length print the length of the longest line\n\ + -w, --words print the word counts\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +/* FILE is the name of the file (or NULL for standard input) + associated with the specified counters. */ +static void +write_counts (uintmax_t lines, + uintmax_t words, + uintmax_t chars, + uintmax_t bytes, + uintmax_t linelength, + const char *file) +{ + static char const format_sp_int[] = " %*s"; + char const *format_int = format_sp_int + 1; + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + + if (print_lines) + { + printf (format_int, number_width, umaxtostr (lines, buf)); + format_int = format_sp_int; + } + if (print_words) + { + printf (format_int, number_width, umaxtostr (words, buf)); + format_int = format_sp_int; + } + if (print_chars) + { + printf (format_int, number_width, umaxtostr (chars, buf)); + format_int = format_sp_int; + } + if (print_bytes) + { + printf (format_int, number_width, umaxtostr (bytes, buf)); + format_int = format_sp_int; + } + if (print_linelength) + { + printf (format_int, number_width, umaxtostr (linelength, buf)); + } + if (file) + printf (" %s", file); + putchar ('\n'); +} + +/* Count words. FILE_X is the name of the file (or NULL for standard + input) that is open on descriptor FD. *FSTATUS is its status. + Return true if successful. */ +static bool +wc (int fd, char const *file_x, struct fstatus *fstatus) +{ + bool ok = true; + char buf[BUFFER_SIZE + 1]; + size_t bytes_read; + uintmax_t lines, words, chars, bytes, linelength; + bool count_bytes, count_chars, count_complicated; + char const *file = file_x ? file_x : _("standard input"); + + lines = words = chars = bytes = linelength = 0; + + /* If in the current locale, chars are equivalent to bytes, we prefer + counting bytes, because that's easier. */ +#if HAVE_MBRTOWC && (MB_LEN_MAX > 1) + if (MB_CUR_MAX > 1) + { + count_bytes = print_bytes; + count_chars = print_chars; + } + else +#endif + { + count_bytes = print_bytes | print_chars; + count_chars = false; + } + count_complicated = print_words | print_linelength; + + /* When counting only bytes, save some line- and word-counting + overhead. If FD is a `regular' Unix file, using lseek is enough + to get its `size' in bytes. Otherwise, read blocks of BUFFER_SIZE + bytes at a time until EOF. Note that the `size' (number of bytes) + that wc reports is smaller than stats.st_size when the file is not + positioned at its beginning. That's why the lseek calls below are + necessary. For example the command + `(dd ibs=99k skip=1 count=0; ./wc -c) < /etc/group' + should make wc report `0' bytes. */ + + if (count_bytes & !count_chars & !print_lines & !count_complicated) + { + off_t current_pos, end_pos; + + if (0 < fstatus->failed) + fstatus->failed = fstat (fd, &fstatus->st); + + if (! fstatus->failed && S_ISREG (fstatus->st.st_mode) + && (current_pos = lseek (fd, (off_t) 0, SEEK_CUR)) != -1 + && (end_pos = lseek (fd, (off_t) 0, SEEK_END)) != -1) + { + /* Be careful here. The current position may actually be + beyond the end of the file. As in the example above. */ + bytes = end_pos < current_pos ? 0 : end_pos - current_pos; + } + else + { + while ((bytes_read = safe_read (fd, buf, BUFFER_SIZE)) > 0) + { + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, "%s", file); + ok = false; + break; + } + bytes += bytes_read; + } + } + } + else if (!count_chars & !count_complicated) + { + /* Use a separate loop when counting only lines or lines and bytes -- + but not chars or words. */ + while ((bytes_read = safe_read (fd, buf, BUFFER_SIZE)) > 0) + { + char *p = buf; + + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, "%s", file); + ok = false; + break; + } + + while ((p = memchr (p, '\n', (buf + bytes_read) - p))) + { + ++p; + ++lines; + } + bytes += bytes_read; + } + } +#if HAVE_MBRTOWC && (MB_LEN_MAX > 1) +# define SUPPORT_OLD_MBRTOWC 1 + else if (MB_CUR_MAX > 1) + { + bool in_word = false; + uintmax_t linepos = 0; + mbstate_t state = { 0, }; + uintmax_t last_error_line = 0; + int last_error_errno = 0; +# if SUPPORT_OLD_MBRTOWC + /* Back-up the state before each multibyte character conversion and + move the last incomplete character of the buffer to the front + of the buffer. This is needed because we don't know whether + the `mbrtowc' function updates the state when it returns -2, - + this is the ISO C 99 and glibc-2.2 behaviour - or not - amended + ANSI C, glibc-2.1 and Solaris 5.7 behaviour. We don't have an + autoconf test for this, yet. */ + size_t prev = 0; /* number of bytes carried over from previous round */ +# else + const size_t prev = 0; +# endif + + while ((bytes_read = safe_read (fd, buf + prev, BUFFER_SIZE - prev)) > 0) + { + const char *p; +# if SUPPORT_OLD_MBRTOWC + mbstate_t backup_state; +# endif + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, "%s", file); + ok = false; + break; + } + + bytes += bytes_read; + p = buf; + bytes_read += prev; + do + { + wchar_t wide_char; + size_t n; + +# if SUPPORT_OLD_MBRTOWC + backup_state = state; +# endif + n = mbrtowc (&wide_char, p, bytes_read, &state); + if (n == (size_t) -2) + { +# if SUPPORT_OLD_MBRTOWC + state = backup_state; +# endif + break; + } + if (n == (size_t) -1) + { + /* Signal repeated errors only once per line. */ + if (!(lines + 1 == last_error_line + && errno == last_error_errno)) + { + char line_number_buf[INT_BUFSIZE_BOUND (uintmax_t)]; + last_error_line = lines + 1; + last_error_errno = errno; + error (0, errno, "%s:%s", file, + umaxtostr (last_error_line, line_number_buf)); + ok = false; + } + p++; + bytes_read--; + } + else + { + if (n == 0) + { + wide_char = 0; + n = 1; + } + p += n; + bytes_read -= n; + chars++; + switch (wide_char) + { + case '\n': + lines++; + /* Fall through. */ + case '\r': + case '\f': + if (linepos > linelength) + linelength = linepos; + linepos = 0; + goto mb_word_separator; + case '\t': + linepos += 8 - (linepos % 8); + goto mb_word_separator; + case ' ': + linepos++; + /* Fall through. */ + case '\v': + mb_word_separator: + words += in_word; + in_word = false; + break; + default: + if (iswprint (wide_char)) + { + int width = wcwidth (wide_char); + if (width > 0) + linepos += width; + if (iswspace (wide_char)) + goto mb_word_separator; + in_word = true; + } + break; + } + } + } + while (bytes_read > 0); + +# if SUPPORT_OLD_MBRTOWC + if (bytes_read > 0) + { + if (bytes_read == BUFFER_SIZE) + { + /* Encountered a very long redundant shift sequence. */ + p++; + bytes_read--; + } + memmove (buf, p, bytes_read); + } + prev = bytes_read; +# endif + } + if (linepos > linelength) + linelength = linepos; + words += in_word; + } +#endif + else + { + bool in_word = false; + uintmax_t linepos = 0; + + while ((bytes_read = safe_read (fd, buf, BUFFER_SIZE)) > 0) + { + const char *p = buf; + if (bytes_read == SAFE_READ_ERROR) + { + error (0, errno, "%s", file); + ok = false; + break; + } + + bytes += bytes_read; + do + { + switch (*p++) + { + case '\n': + lines++; + /* Fall through. */ + case '\r': + case '\f': + if (linepos > linelength) + linelength = linepos; + linepos = 0; + goto word_separator; + case '\t': + linepos += 8 - (linepos % 8); + goto word_separator; + case ' ': + linepos++; + /* Fall through. */ + case '\v': + word_separator: + words += in_word; + in_word = false; + break; + default: + if (isprint (to_uchar (p[-1]))) + { + linepos++; + if (isspace (to_uchar (p[-1]))) + goto word_separator; + in_word = true; + } + break; + } + } + while (--bytes_read); + } + if (linepos > linelength) + linelength = linepos; + words += in_word; + } + + if (count_chars < print_chars) + chars = bytes; + + write_counts (lines, words, chars, bytes, linelength, file_x); + total_lines += lines; + total_words += words; + total_chars += chars; + total_bytes += bytes; + if (linelength > max_line_length) + max_line_length = linelength; + + return ok; +} + +static bool +wc_file (char const *file, struct fstatus *fstatus) +{ + if (! file || STREQ (file, "-")) + { + have_read_stdin = true; + if (O_BINARY && ! isatty (STDIN_FILENO)) + freopen (NULL, "rb", stdin); + return wc (STDIN_FILENO, file, fstatus); + } + else + { + int fd = open (file, O_RDONLY | O_BINARY); + if (fd == -1) + { + error (0, errno, "%s", file); + return false; + } + else + { + bool ok = wc (fd, file, fstatus); + if (close (fd) != 0) + { + error (0, errno, "%s", file); + return false; + } + return ok; + } + } +} + +/* Return the file status for the NFILES files addressed by FILE. + Optimize the case where only one number is printed, for just one + file; in that case we can use a print width of 1, so we don't need + to stat the file. */ + +static struct fstatus * +get_input_fstatus (int nfiles, char * const *file) +{ + struct fstatus *fstatus = xnmalloc (nfiles, sizeof *fstatus); + + if (nfiles == 1 + && ((print_lines + print_words + print_chars + + print_bytes + print_linelength) + == 1)) + fstatus[0].failed = 1; + else + { + int i; + + for (i = 0; i < nfiles; i++) + fstatus[i].failed = (! file[i] || STREQ (file[i], "-") + ? fstat (STDIN_FILENO, &fstatus[i].st) + : stat (file[i], &fstatus[i].st)); + } + + return fstatus; +} + +/* Return a print width suitable for the NFILES files whose status is + recorded in FSTATUS. Optimize the same special case that + get_input_fstatus optimizes. */ + +static int +compute_number_width (int nfiles, struct fstatus const *fstatus) +{ + int width = 1; + + if (0 < nfiles && fstatus[0].failed <= 0) + { + int minimum_width = 1; + uintmax_t regular_total = 0; + int i; + + for (i = 0; i < nfiles; i++) + if (! fstatus[i].failed) + { + if (S_ISREG (fstatus[i].st.st_mode)) + regular_total += fstatus[i].st.st_size; + else + minimum_width = 7; + } + + for (; 10 <= regular_total; regular_total /= 10) + width++; + if (width < minimum_width) + width = minimum_width; + } + + return width; +} + + +int +main (int argc, char **argv) +{ + int i; + bool ok; + int optc; + int nfiles; + char **files; + char *files_from = NULL; + struct fstatus *fstatus; + struct Tokens tok; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + print_lines = print_words = print_chars = print_bytes = false; + print_linelength = false; + total_lines = total_words = total_chars = total_bytes = max_line_length = 0; + + while ((optc = getopt_long (argc, argv, "clLmw", longopts, NULL)) != -1) + switch (optc) + { + case 'c': + print_bytes = true; + break; + + case 'm': + print_chars = true; + break; + + case 'l': + print_lines = true; + break; + + case 'w': + print_words = true; + break; + + case 'L': + print_linelength = true; + break; + + case FILES0_FROM_OPTION: + files_from = optarg; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + + if (! (print_lines | print_words | print_chars | print_bytes + | print_linelength)) + print_lines = print_words = print_bytes = true; + + if (files_from) + { + FILE *stream; + + /* When using --files0-from=F, you may not specify any files + on the command-line. */ + if (optind < argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind])); + fprintf (stderr, "%s\n", + _("File operands cannot be combined with --files0-from.")); + usage (EXIT_FAILURE); + } + + if (STREQ (files_from, "-")) + stream = stdin; + else + { + stream = fopen (files_from, "r"); + if (stream == NULL) + error (EXIT_FAILURE, errno, _("cannot open %s for reading"), + quote (files_from)); + } + + readtokens0_init (&tok); + + if (! readtokens0 (stream, &tok) || fclose (stream) != 0) + error (EXIT_FAILURE, 0, _("cannot read file names from %s"), + quote (files_from)); + + files = tok.tok; + nfiles = tok.n_tok; + } + else + { + static char *stdin_only[2]; + files = (optind < argc ? argv + optind : stdin_only); + nfiles = (optind < argc ? argc - optind : 1); + stdin_only[0] = NULL; + } + + fstatus = get_input_fstatus (nfiles, files); + number_width = compute_number_width (nfiles, fstatus); + + ok = true; + for (i = 0; i < nfiles; i++) + { + if (files_from && STREQ (files_from, "-") && STREQ (files[i], "-")) + { + ok = false; + error (0, 0, + _("when reading file names from stdin, " + "no file name of %s allowed"), + quote ("-")); + continue; + } + ok &= wc_file (files[i], &fstatus[i]); + } + + if (1 < nfiles) + write_counts (total_lines, total_words, total_chars, total_bytes, + max_line_length, _("total")); + + free (fstatus); + + if (have_read_stdin && close (STDIN_FILENO) != 0) + error (EXIT_FAILURE, errno, "-"); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/src/wheel-gen.pl b/src/wheel-gen.pl new file mode 100755 index 0000000..a225830 --- /dev/null +++ b/src/wheel-gen.pl @@ -0,0 +1,115 @@ +#!/usr/bin/perl -w +# Generate the spokes of a wheel, for wheel factorization. + +# Copyright (C) 2001, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +eval 'exec /usr/bin/perl -S $0 ${1+"$@"}' + if 0; + +use strict; +(my $ME = $0) =~ s|.*/||; + +# A global destructor to close standard output with error checking. +sub END +{ + defined fileno STDOUT + or return; + close STDOUT + and return; + warn "$ME: closing standard output: $!\n"; + $? ||= 1; +} + +sub is_prime ($) +{ + my ($n) = @_; + use integer; + + $n == 2 + and return 1; + + my $d = 2; + my $w = 1; + while (1) + { + my $q = $n / $d; + $n == $q * $d + and return 0; + $d += $w; + $q < $d + and last; + $w = 2; + } + return 1; +} + +{ + @ARGV == 1 + or die "$ME: missing argument\n"; + + my $wheel_size = $ARGV[0]; + + my @primes = (2); + my $product = $primes[0]; + my $n_primes = 1; + for (my $i = 3; ; $i += 2) + { + if (is_prime $i) + { + push @primes, $i; + $product *= $i; + ++$n_primes == $wheel_size + and last; + } + } + + my $ws_m1 = $wheel_size - 1; + print < +#include +#include + +#include +#include "system.h" + +#include "canon-host.h" +#include "readutmp.h" +#include "error.h" +#include "hard-locale.h" +#include "inttostr.h" +#include "quote.h" +#include "vasprintf.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "who" + +#define AUTHORS "Joseph Arceneaux", "David MacKenzie", "Michael Stone" + +#ifndef MAXHOSTNAMELEN +# define MAXHOSTNAMELEN 64 +#endif + +#ifdef RUN_LVL +# define UT_TYPE_RUN_LVL(U) UT_TYPE_EQ (U, RUN_LVL) +#else +# define UT_TYPE_RUN_LVL(U) false +#endif + +#ifdef INIT_PROCESS +# define UT_TYPE_INIT_PROCESS(U) UT_TYPE_EQ (U, INIT_PROCESS) +#else +# define UT_TYPE_INIT_PROCESS(U) false +#endif + +#ifdef LOGIN_PROCESS +# define UT_TYPE_LOGIN_PROCESS(U) UT_TYPE_EQ (U, LOGIN_PROCESS) +#else +# define UT_TYPE_LOGIN_PROCESS(U) false +#endif + +#ifdef DEAD_PROCESS +# define UT_TYPE_DEAD_PROCESS(U) UT_TYPE_EQ (U, DEAD_PROCESS) +#else +# define UT_TYPE_DEAD_PROCESS(U) false +#endif + +#ifdef NEW_TIME +# define UT_TYPE_NEW_TIME(U) UT_TYPE_EQ (U, NEW_TIME) +#else +# define UT_TYPE_NEW_TIME(U) false +#endif + +#define IDLESTR_LEN 6 + +#if HAVE_STRUCT_XTMP_UT_PID +# define PIDSTR_DECL_AND_INIT(Var, Utmp_ent) \ + char Var[INT_STRLEN_BOUND (Utmp_ent->ut_pid) + 1]; \ + sprintf (Var, "%ld", (long int) (Utmp_ent->ut_pid)) +#else +# define PIDSTR_DECL_AND_INIT(Var, Utmp_ent) \ + const char *Var = "" +#endif + +#if HAVE_STRUCT_XTMP_UT_ID +# define UT_ID(U) ((U)->ut_id) +#else +# define UT_ID(U) "??" +#endif + +char *ttyname (); + +/* The name this program was run with. */ +char *program_name; + +/* If true, attempt to canonicalize hostnames via a DNS lookup. */ +static bool do_lookup; + +/* If true, display only a list of usernames and count of + the users logged on. + Ignored for `who am i'. */ +static bool short_list; + +/* If true, display only name, line, and time fields. */ +static bool short_output; + +/* If true, display the hours:minutes since each user has touched + the keyboard, or "." if within the last minute, or "old" if + not within the last day. */ +static bool include_idle; + +/* If true, display a line at the top describing each field. */ +static bool include_heading; + +/* If true, display a `+' for each user if mesg y, a `-' if mesg n, + or a `?' if their tty cannot be statted. */ +static bool include_mesg; + +/* If true, display process termination & exit status. */ +static bool include_exit; + +/* If true, display the last boot time. */ +static bool need_boottime; + +/* If true, display dead processes. */ +static bool need_deadprocs; + +/* If true, display processes waiting for user login. */ +static bool need_login; + +/* If true, display processes started by init. */ +static bool need_initspawn; + +/* If true, display the last clock change. */ +static bool need_clockchange; + +/* If true, display the current runlevel. */ +static bool need_runlevel; + +/* If true, display user processes. */ +static bool need_users; + +/* If true, display info only for the controlling tty. */ +static bool my_line_only; + +/* The strftime format to use for login times, and its expected + output width. */ +static char const *time_format; +static int time_format_width; + +/* for long options with no corresponding short option, use enum */ +enum +{ + LOOKUP_OPTION = CHAR_MAX + 1 +}; + +static struct option const longopts[] = { + {"all", no_argument, NULL, 'a'}, + {"boot", no_argument, NULL, 'b'}, + {"count", no_argument, NULL, 'q'}, + {"dead", no_argument, NULL, 'd'}, + {"heading", no_argument, NULL, 'H'}, + {"idle", no_argument, NULL, 'i'}, /* FIXME: deprecated: remove in late 2006 */ + {"login", no_argument, NULL, 'l'}, + {"lookup", no_argument, NULL, LOOKUP_OPTION}, + {"message", no_argument, NULL, 'T'}, + {"mesg", no_argument, NULL, 'T'}, + {"process", no_argument, NULL, 'p'}, + {"runlevel", no_argument, NULL, 'r'}, + {"short", no_argument, NULL, 's'}, + {"time", no_argument, NULL, 't'}, + {"users", no_argument, NULL, 'u'}, + {"writable", no_argument, NULL, 'T'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Return a string representing the time between WHEN and now. + BOOTTIME is the time of last reboot. + FIXME: locale? */ +static const char * +idle_string (time_t when, time_t boottime) +{ + static time_t now = TYPE_MINIMUM (time_t); + + if (now == TYPE_MINIMUM (time_t)) + time (&now); + + if (boottime < when && now - 24 * 60 * 60 < when && when <= now) + { + int seconds_idle = now - when; + if (seconds_idle < 60) + return " . "; + else + { + static char idle_hhmm[IDLESTR_LEN]; + sprintf (idle_hhmm, "%02d:%02d", + seconds_idle / (60 * 60), + (seconds_idle % (60 * 60)) / 60); + return idle_hhmm; + } + } + + return _(" old "); +} + +/* Return a time string. */ +static const char * +time_string (const STRUCT_UTMP *utmp_ent) +{ + static char buf[INT_STRLEN_BOUND (intmax_t) + sizeof "-%m-%d %H:%M"]; + + /* Don't take the address of UT_TIME_MEMBER directly. + Ulrich Drepper wrote: + ``... GNU libc (and perhaps other libcs as well) have extended + utmp file formats which do not use a simple time_t ut_time field. + In glibc, ut_time is a macro which selects for backward compatibility + the tv_sec member of a struct timeval value.'' */ + time_t t = UT_TIME_MEMBER (utmp_ent); + struct tm *tmp = localtime (&t); + + if (tmp) + { + strftime (buf, sizeof buf, time_format, tmp); + return buf; + } + else + return TYPE_SIGNED (time_t) ? imaxtostr (t, buf) : umaxtostr (t, buf); +} + +/* Print formatted output line. Uses mostly arbitrary field sizes, probably + will need tweaking if any of the localization stuff is done, or for 64 bit + pids, etc. */ +static void +print_line (int userlen, const char *user, const char state, + int linelen, const char *line, + const char *time_str, const char *idle, const char *pid, + const char *comment, const char *exitstr) +{ + static char mesg[3] = { ' ', 'x', '\0' }; + char *buf; + char x_idle[1 + IDLESTR_LEN + 1]; + char x_pid[1 + INT_STRLEN_BOUND (pid_t) + 1]; + char *x_exitstr; + int err; + + mesg[1] = state; + + if (include_idle && !short_output && strlen (idle) < sizeof x_idle - 1) + sprintf (x_idle, " %-6s", idle); + else + *x_idle = '\0'; + + if (!short_output && strlen (pid) < sizeof x_pid - 1) + sprintf (x_pid, " %10s", pid); + else + *x_pid = '\0'; + + x_exitstr = xmalloc (include_exit ? 1 + MAX (12, strlen (exitstr)) + 1 : 1); + if (include_exit) + sprintf (x_exitstr, " %-12s", exitstr); + else + *x_exitstr = '\0'; + + err = asprintf (&buf, + "%-8.*s" + "%s" + " %-12.*s" + " %-*s" + "%s" + "%s" + " %-8s" + "%s" + , + userlen, user ? user : " .", + include_mesg ? mesg : "", + linelen, line, + time_format_width, + time_str, + x_idle, + x_pid, + /* FIXME: it's not really clear whether the following + field should be in the short_output. A strict reading + of SUSv2 would suggest not, but I haven't seen any + implementations that actually work that way... */ + comment, + x_exitstr + ); + if (err == -1) + xalloc_die (); + + { + /* Remove any trailing spaces. */ + char *p = buf + strlen (buf); + while (*--p == ' ') + /* empty */; + *(p + 1) = '\0'; + } + + puts (buf); + free (buf); + free (x_exitstr); +} + +/* Send properly parsed USER_PROCESS info to print_line. The most + recent boot time is BOOTTIME. */ +static void +print_user (const STRUCT_UTMP *utmp_ent, time_t boottime) +{ + struct stat stats; + time_t last_change; + char mesg; + char idlestr[IDLESTR_LEN + 1]; + static char *hoststr; +#if HAVE_UT_HOST + static size_t hostlen; +#endif + +#define DEV_DIR_WITH_TRAILING_SLASH "/dev/" +#define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1) + + char line[sizeof (utmp_ent->ut_line) + DEV_DIR_LEN + 1]; + PIDSTR_DECL_AND_INIT (pidstr, utmp_ent); + + /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not + already an absolute file name. Some systems may put the full, + absolute file name in ut_line. */ + if (utmp_ent->ut_line[0] == '/') + { + strncpy (line, utmp_ent->ut_line, sizeof (utmp_ent->ut_line)); + line[sizeof (utmp_ent->ut_line)] = '\0'; + } + else + { + strcpy (line, DEV_DIR_WITH_TRAILING_SLASH); + strncpy (line + DEV_DIR_LEN, utmp_ent->ut_line, + sizeof (utmp_ent->ut_line)); + line[DEV_DIR_LEN + sizeof (utmp_ent->ut_line)] = '\0'; + } + + if (stat (line, &stats) == 0) + { + mesg = (stats.st_mode & S_IWGRP) ? '+' : '-'; + last_change = stats.st_atime; + } + else + { + mesg = '?'; + last_change = 0; + } + + if (last_change) + sprintf (idlestr, "%.*s", IDLESTR_LEN, idle_string (last_change, boottime)); + else + sprintf (idlestr, " ?"); + +#if HAVE_UT_HOST + if (utmp_ent->ut_host[0]) + { + char ut_host[sizeof (utmp_ent->ut_host) + 1]; + char *host = NULL; + char *display = NULL; + + /* Copy the host name into UT_HOST, and ensure it's nul terminated. */ + strncpy (ut_host, utmp_ent->ut_host, sizeof (utmp_ent->ut_host)); + ut_host[sizeof (utmp_ent->ut_host)] = '\0'; + + /* Look for an X display. */ + display = strchr (ut_host, ':'); + if (display) + *display++ = '\0'; + + if (*ut_host && do_lookup) + { + /* See if we can canonicalize it. */ + host = canon_host (ut_host); + } + + if (! host) + host = ut_host; + + if (display) + { + if (hostlen < strlen (host) + strlen (display) + 4) + { + hostlen = strlen (host) + strlen (display) + 4; + hoststr = xrealloc (hoststr, hostlen); + } + sprintf (hoststr, "(%s:%s)", host, display); + } + else + { + if (hostlen < strlen (host) + 3) + { + hostlen = strlen (host) + 3; + hoststr = xrealloc (hoststr, hostlen); + } + sprintf (hoststr, "(%s)", host); + } + + if (host != ut_host) + free (host); + } + else + { + if (hostlen < 1) + { + hostlen = 1; + hoststr = xrealloc (hoststr, hostlen); + } + *hoststr = '\0'; + } +#endif + + print_line (sizeof UT_USER (utmp_ent), UT_USER (utmp_ent), mesg, + sizeof utmp_ent->ut_line, utmp_ent->ut_line, + time_string (utmp_ent), idlestr, pidstr, + hoststr ? hoststr : "", ""); +} + +static void +print_boottime (const STRUCT_UTMP *utmp_ent) +{ + print_line (-1, "", ' ', -1, "system boot", + time_string (utmp_ent), "", "", "", ""); +} + +static char * +make_id_equals_comment (STRUCT_UTMP const *utmp_ent) +{ + char *comment = xmalloc (strlen (_("id=")) + sizeof UT_ID (utmp_ent) + 1); + + strcpy (comment, _("id=")); + strncat (comment, UT_ID (utmp_ent), sizeof UT_ID (utmp_ent)); + return comment; +} + +static void +print_deadprocs (const STRUCT_UTMP *utmp_ent) +{ + static char *exitstr; + char *comment = make_id_equals_comment (utmp_ent); + PIDSTR_DECL_AND_INIT (pidstr, utmp_ent); + + if (!exitstr) + exitstr = xmalloc (strlen (_("term=")) + + INT_STRLEN_BOUND (UT_EXIT_E_TERMINATION (utmp_ent)) + 1 + + strlen (_("exit=")) + + INT_STRLEN_BOUND (UT_EXIT_E_EXIT (utmp_ent)) + + 1); + sprintf (exitstr, "%s%d %s%d", _("term="), UT_EXIT_E_TERMINATION (utmp_ent), + _("exit="), UT_EXIT_E_EXIT (utmp_ent)); + + /* FIXME: add idle time? */ + + print_line (-1, "", ' ', sizeof utmp_ent->ut_line, utmp_ent->ut_line, + time_string (utmp_ent), "", pidstr, comment, exitstr); + free (comment); +} + +static void +print_login (const STRUCT_UTMP *utmp_ent) +{ + char *comment = make_id_equals_comment (utmp_ent); + PIDSTR_DECL_AND_INIT (pidstr, utmp_ent); + + /* FIXME: add idle time? */ + + print_line (-1, "LOGIN", ' ', sizeof utmp_ent->ut_line, utmp_ent->ut_line, + time_string (utmp_ent), "", pidstr, comment, ""); + free (comment); +} + +static void +print_initspawn (const STRUCT_UTMP *utmp_ent) +{ + char *comment = make_id_equals_comment (utmp_ent); + PIDSTR_DECL_AND_INIT (pidstr, utmp_ent); + + print_line (-1, "", ' ', sizeof utmp_ent->ut_line, utmp_ent->ut_line, + time_string (utmp_ent), "", pidstr, comment, ""); + free (comment); +} + +static void +print_clockchange (const STRUCT_UTMP *utmp_ent) +{ + /* FIXME: handle NEW_TIME & OLD_TIME both */ + print_line (-1, "", ' ', -1, _("clock change"), + time_string (utmp_ent), "", "", "", ""); +} + +static void +print_runlevel (const STRUCT_UTMP *utmp_ent) +{ + static char *runlevline, *comment; + unsigned char last = UT_PID (utmp_ent) / 256; + unsigned char curr = UT_PID (utmp_ent) % 256; + + if (!runlevline) + runlevline = xmalloc (strlen (_("run-level")) + 3); + sprintf (runlevline, "%s %c", _("run-level"), curr); + + if (!comment) + comment = xmalloc (strlen (_("last=")) + 2); + sprintf (comment, "%s%c", _("last="), (last == 'N') ? 'S' : last); + + print_line (-1, "", ' ', -1, runlevline, time_string (utmp_ent), + "", "", comment, ""); + + return; +} + +/* Print the username of each valid entry and the number of valid entries + in UTMP_BUF, which should have N elements. */ +static void +list_entries_who (size_t n, const STRUCT_UTMP *utmp_buf) +{ + unsigned long int entries = 0; + char const *separator = ""; + + while (n--) + { + if (IS_USER_PROCESS (utmp_buf)) + { + char *trimmed_name; + + trimmed_name = extract_trimmed_name (utmp_buf); + + printf ("%s%s", separator, trimmed_name); + free (trimmed_name); + separator = " "; + entries++; + } + utmp_buf++; + } + printf (_("\n# users=%lu\n"), entries); +} + +static void +print_heading (void) +{ + print_line (-1, _("NAME"), ' ', -1, _("LINE"), _("TIME"), _("IDLE"), + _("PID"), _("COMMENT"), _("EXIT")); +} + +/* Display UTMP_BUF, which should have N entries. */ +static void +scan_entries (size_t n, const STRUCT_UTMP *utmp_buf) +{ + char *ttyname_b IF_LINT ( = NULL); + time_t boottime = TYPE_MINIMUM (time_t); + + if (include_heading) + print_heading (); + + if (my_line_only) + { + ttyname_b = ttyname (STDIN_FILENO); + if (!ttyname_b) + return; + if (strncmp (ttyname_b, DEV_DIR_WITH_TRAILING_SLASH, DEV_DIR_LEN) == 0) + ttyname_b += DEV_DIR_LEN; /* Discard /dev/ prefix. */ + } + + while (n--) + { + if (!my_line_only || + strncmp (ttyname_b, utmp_buf->ut_line, + sizeof (utmp_buf->ut_line)) == 0) + { + if (need_users && IS_USER_PROCESS (utmp_buf)) + print_user (utmp_buf, boottime); + else if (need_runlevel && UT_TYPE_RUN_LVL (utmp_buf)) + print_runlevel (utmp_buf); + else if (need_boottime && UT_TYPE_BOOT_TIME (utmp_buf)) + print_boottime (utmp_buf); + /* I've never seen one of these, so I don't know what it should + look like :^) + FIXME: handle OLD_TIME also, perhaps show the delta? */ + else if (need_clockchange && UT_TYPE_NEW_TIME (utmp_buf)) + print_clockchange (utmp_buf); + else if (need_initspawn && UT_TYPE_INIT_PROCESS (utmp_buf)) + print_initspawn (utmp_buf); + else if (need_login && UT_TYPE_LOGIN_PROCESS (utmp_buf)) + print_login (utmp_buf); + else if (need_deadprocs && UT_TYPE_DEAD_PROCESS (utmp_buf)) + print_deadprocs (utmp_buf); + } + + if (UT_TYPE_BOOT_TIME (utmp_buf)) + boottime = UT_TIME_MEMBER (utmp_buf); + + utmp_buf++; + } +} + +/* Display a list of who is on the system, according to utmp file FILENAME. + Use read_utmp OPTIONS to read the file. */ +static void +who (const char *filename, int options) +{ + size_t n_users; + STRUCT_UTMP *utmp_buf; + + if (read_utmp (filename, &n_users, &utmp_buf, options) != 0) + error (EXIT_FAILURE, errno, "%s", filename); + + if (short_list) + list_entries_who (n_users, utmp_buf); + else + scan_entries (n_users, utmp_buf); + + free (utmp_buf); +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name); + fputs (_("\ +\n\ + -a, --all same as -b -d --login -p -r -t -T -u\n\ + -b, --boot time of last system boot\n\ + -d, --dead print dead processes\n\ + -H, --heading print line of column headings\n\ +"), stdout); + fputs (_("\ + -l, --login print system login processes\n\ +"), stdout); + fputs (_("\ + --lookup attempt to canonicalize hostnames via DNS\n\ + -m only hostname and user associated with stdin\n\ + -p, --process print active processes spawned by init\n\ +"), stdout); + fputs (_("\ + -q, --count all login names and number of users logged on\n\ + -r, --runlevel print current runlevel\n\ + -s, --short print only name, line, and time (default)\n\ + -t, --time print last system clock change\n\ +"), stdout); + fputs (_("\ + -T, -w, --mesg add user's message status as +, - or ?\n\ + -u, --users list users logged in\n\ + --message same as -T\n\ + --writable same as -T\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\ +\n\ +If FILE is not specified, use %s. %s as FILE is common.\n\ +If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\ +"), UTMP_FILE, WTMP_FILE); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int optc; + bool assumptions = true; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "abdilmpqrstuwHT", longopts, NULL)) + != -1) + { + switch (optc) + { + case 'a': + need_boottime = true; + need_deadprocs = true; + need_login = true; + need_initspawn = true; + need_runlevel = true; + need_clockchange = true; + need_users = true; + include_mesg = true; + include_idle = true; + include_exit = true; + assumptions = false; + break; + + case 'b': + need_boottime = true; + assumptions = false; + break; + + case 'd': + need_deadprocs = true; + include_idle = true; + include_exit = true; + assumptions = false; + break; + + case 'H': + include_heading = true; + break; + + case 'l': + need_login = true; + include_idle = true; + assumptions = false; + break; + + case 'm': + my_line_only = true; + break; + + case 'p': + need_initspawn = true; + assumptions = false; + break; + + case 'q': + short_list = true; + break; + + case 'r': + need_runlevel = true; + include_idle = true; + assumptions = false; + break; + + case 's': + short_output = true; + break; + + case 't': + need_clockchange = true; + assumptions = false; + break; + + case 'T': + case 'w': + include_mesg = true; + break; + + case 'i': + error (0, 0, + _("Warning: -i will be removed in a future release; \ + use -u instead")); + /* Fall through. */ + case 'u': + need_users = true; + include_idle = true; + assumptions = false; + break; + + case LOOKUP_OPTION: + do_lookup = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (assumptions) + { + need_users = true; + short_output = true; + } + + if (include_exit) + { + short_output = false; + } + + if (hard_locale (LC_TIME)) + { + time_format = "%Y-%m-%d %H:%M"; + time_format_width = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2; + } + else + { + time_format = "%b %e %H:%M"; + time_format_width = 3 + 1 + 2 + 1 + 2 + 1 + 2; + } + + switch (argc - optind) + { + case 2: /* who */ + my_line_only = true; + /* Fall through. */ + case -1: + case 0: /* who */ + who (UTMP_FILE, READ_UTMP_CHECK_PIDS); + break; + + case 1: /* who */ + who (argv[optind], 0); + break; + + default: /* lose */ + error (0, 0, _("extra operand %s"), quote (argv[optind + 2])); + usage (EXIT_FAILURE); + } + + exit (EXIT_SUCCESS); +} diff --git a/src/whoami.c b/src/whoami.c new file mode 100644 index 0000000..c4a2b5e --- /dev/null +++ b/src/whoami.c @@ -0,0 +1,98 @@ +/* whoami -- print effective userid + + Copyright (C) 89,90, 1991-1997, 1999-2002, 2004, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Equivalent to `id -un'. */ +/* Written by Richard Mlynarik. */ + +#include +#include +#include +#include +#include + +#include "system.h" +#include "error.h" +#include "long-options.h" +#include "quote.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "whoami" + +#define AUTHORS "Richard Mlynarik" + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]...\n"), program_name); + fputs (_("\ +Print the user name associated with the current effective user ID.\n\ +Same as id -un.\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + struct passwd *pw; + uid_t uid; + + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (optind != argc) + { + error (0, 0, _("extra operand %s"), quote (argv[optind])); + usage (EXIT_FAILURE); + } + + uid = geteuid (); + pw = getpwuid (uid); + if (pw) + { + puts (pw->pw_name); + exit (EXIT_SUCCESS); + } + fprintf (stderr, _("%s: cannot find name for user ID %lu\n"), + program_name, (unsigned long int) uid); + exit (EXIT_FAILURE); +} diff --git a/src/yes.c b/src/yes.c new file mode 100644 index 0000000..4f09f68 --- /dev/null +++ b/src/yes.c @@ -0,0 +1,96 @@ +/* yes - output a string repeatedly until killed + Copyright (C) 1991-1997, 1999-2004 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* David MacKenzie */ + +#include +#include +#include +#include + +#include "system.h" + +#include "error.h" +#include "long-options.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "yes" + +#define AUTHORS "David MacKenzie" + +/* The name this program was run with. */ +char *program_name; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [STRING]...\n\ + or: %s OPTION\n\ +"), + program_name, program_name); + + fputs (_("\ +Repeatedly output a line with all specified STRING(s), or `y'.\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + initialize_main (&argc, &argv); + program_name = argv[0]; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + parse_long_options (argc, argv, PROGRAM_NAME, GNU_PACKAGE, VERSION, + usage, AUTHORS, (char const *) NULL); + if (getopt_long (argc, argv, "+", NULL, NULL) != -1) + usage (EXIT_FAILURE); + + if (argc <= optind) + { + optind = argc; + argv[argc++] = "y"; + } + + for (;;) + { + int i; + for (i = optind; i < argc; i++) + if (fputs (argv[i], stdout) == EOF + || putchar (i == argc - 1 ? '\n' : ' ') == EOF) + { + error (0, errno, _("standard output")); + exit (EXIT_FAILURE); + } + } +} -- cgit v1.2.1