summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2007-03-22 21:23:21 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2007-03-22 21:23:21 +0000
commitcbf5993c43f49281173f185863577d86bfac6eae (patch)
tree90737c96cf15b97273a2bdc5950b3cf09f1d94ca /src
downloadcoreutils-tarball-cbf5993c43f49281173f185863577d86bfac6eae.tar.gz
coreutils-6.9coreutils-6.9
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am365
-rw-r--r--src/Makefile.in2065
-rw-r--r--src/base64.c322
-rw-r--r--src/basename.c144
-rw-r--r--src/c99-to-c89.diff118
-rw-r--r--src/cat.c788
-rw-r--r--src/chgrp.c314
-rw-r--r--src/chmod.c532
-rw-r--r--src/chown-core.c514
-rw-r--r--src/chown-core.h87
-rw-r--r--src/chown.c338
-rw-r--r--src/chroot.c118
-rw-r--r--src/cksum.c316
-rw-r--r--src/comm.c285
-rw-r--r--src/copy.c2007
-rw-r--r--src/copy.h218
-rw-r--r--src/cp-hash.c187
-rw-r--r--src/cp-hash.h6
-rw-r--r--src/cp.c1072
-rw-r--r--src/csplit.c1515
-rw-r--r--src/cut.c884
-rw-r--r--src/date.c563
-rwxr-xr-xsrc/dcgen57
-rw-r--r--src/dd.c1744
-rw-r--r--src/df.c967
-rw-r--r--src/dircolors.c512
-rw-r--r--src/dircolors.h164
-rw-r--r--src/dircolors.hin171
-rw-r--r--src/dirname.c117
-rw-r--r--src/du.c1021
-rw-r--r--src/echo.c276
-rw-r--r--src/env.c205
-rw-r--r--src/expand.c438
-rw-r--r--src/expr.c873
-rw-r--r--src/extract-magic135
-rw-r--r--src/factor.c220
-rw-r--r--src/false.c2
-rw-r--r--src/fmt.c1015
-rw-r--r--src/fold.c318
-rw-r--r--src/fs.h43
-rwxr-xr-xsrc/groups.sh83
-rw-r--r--src/head.c1064
-rw-r--r--src/hostid.c96
-rw-r--r--src/hostname.c125
-rw-r--r--src/id.c388
-rw-r--r--src/install.c708
-rw-r--r--src/join.c940
-rw-r--r--src/kill.c373
-rw-r--r--src/lbracket.c2
-rw-r--r--src/link.c99
-rw-r--r--src/ln.c534
-rw-r--r--src/logname.c91
-rw-r--r--src/ls-dir.c2
-rw-r--r--src/ls-ls.c2
-rw-r--r--src/ls-vdir.c2
-rw-r--r--src/ls.c4430
-rw-r--r--src/ls.h10
-rw-r--r--src/md5sum.c723
-rw-r--r--src/mkdir.c205
-rw-r--r--src/mkfifo.c129
-rw-r--r--src/mknod.c221
-rw-r--r--src/mv.c486
-rw-r--r--src/nice.c195
-rw-r--r--src/nl.c617
-rw-r--r--src/nohup.c216
-rw-r--r--src/od.c1937
-rw-r--r--src/paste.c497
-rw-r--r--src/pathchk.c433
-rw-r--r--src/pinky.c623
-rw-r--r--src/pr.c2878
-rw-r--r--src/printenv.c134
-rw-r--r--src/printf.c687
-rw-r--r--src/ptx.c2224
-rw-r--r--src/pwd.c323
-rw-r--r--src/readlink.c174
-rw-r--r--src/remove.c1565
-rw-r--r--src/remove.h96
-rw-r--r--src/rm.c374
-rw-r--r--src/rmdir.c226
-rw-r--r--src/seq.c375
-rw-r--r--src/setuidgid.c129
-rw-r--r--src/shred.c1214
-rw-r--r--src/shuf.c421
-rw-r--r--src/sleep.c152
-rw-r--r--src/sort.c3174
-rw-r--r--src/split.c582
-rw-r--r--src/stat.c979
-rw-r--r--src/stty.c1892
-rw-r--r--src/su.c526
-rw-r--r--src/sum.c269
-rw-r--r--src/sync.c78
-rw-r--r--src/system.h583
-rw-r--r--src/tac-pipe.c263
-rw-r--r--src/tac.c666
-rw-r--r--src/tail.c1697
-rw-r--r--src/tee.c220
-rw-r--r--src/test.c849
-rw-r--r--src/touch.c420
-rw-r--r--src/tr.c1896
-rw-r--r--src/true.c82
-rw-r--r--src/tsort.c559
-rw-r--r--src/tty.c129
-rw-r--r--src/uname.c325
-rw-r--r--src/unexpand.c540
-rw-r--r--src/uniq.c552
-rw-r--r--src/unlink.c94
-rw-r--r--src/uptime.c245
-rw-r--r--src/users.c154
-rw-r--r--src/wc.c704
-rwxr-xr-xsrc/wheel-gen.pl115
-rw-r--r--src/wheel-size.h1
-rw-r--r--src/wheel.h491
-rw-r--r--src/who.c827
-rw-r--r--src/whoami.c98
-rw-r--r--src/yes.c96
115 files changed, 66345 insertions, 0 deletions
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 <simon@josefsson.org>. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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,
+ &current_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 <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#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 <config.h>
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#if HAVE_STROPTS_H
+# include <stropts.h>
+#endif
+#if HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#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 <djm@gnu.ai.mit.edu>. */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <grp.h>
+#include <getopt.h>
+
+#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 <djm@gnu.ai.mit.edu> */
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+#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 <djm@gnu.ai.mit.edu>. */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#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 <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#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 <config.h>
+
+/* The official name of this program (e.g., no `g' prefix). */
+#define PROGRAM_NAME "cksum"
+
+#define AUTHORS "Q. Frank Xia"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdint.h>
+#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 <getopt.h>
+# 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 <config.h>
+
+#include <getopt.h>
+#include <sys/types.h>
+#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 <config.h>
+#include <stdio.h>
+#include <assert.h>
+#include <sys/types.h>
+
+#if HAVE_HURD_H
+# include <hurd.h>
+#endif
+#if HAVE_PRIV_H
+# include <priv.h>
+#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.*\<E[A-Z]*\>.*\<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 <stdbool.h>
+# 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 <config.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#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 <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#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, &copy_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 <config.h>
+
+#include <getopt.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#include "system.h"
+
+#include <regex.h>
+
+#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 <djm@gnu.ai.mit.edu>.
+
+ Rewrite cut_fields and cut_bytes -- Jim Meyering. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <assert.h>
+#include <getopt.h>
+#include <sys/types.h>
+#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 <djm@gnu.ai.mit.edu> */
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#if HAVE_LANGINFO_CODESET
+# include <langinfo.h>
+#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 <config.h>
+
+#define SWAB_ALIGN_OFFSET 2
+
+#include <sys/types.h>
+#include <signal.h>
+#include <getopt.h>
+
+#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 <sys/mtio.h>
+
+# 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 <sys/mtio.h> 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 <djm@gnu.ai.mit.edu>.
+ --human-readable and --megabyte options added by lm@sgi.com.
+ --si and large file support added by eggert@twinsun.com. */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#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 <config.h>
+
+#include <sys/types.h>
+#include <getopt.h>
+#include <stdio.h>
+
+#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) : _("<internal>")),
+ (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 <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#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 = &dot;
+ 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 <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <assert.h>
+#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 <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#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 <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#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 <djm@gnu.ai.mit.edu> */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#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 <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include "system.h"
+
+#include <regex.h>
+#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 <<EOF;
+Usage: $ME [OPTIONS] FILE
+
+FIXME: describe
+
+OPTIONS:
+
+ Derive #define directives from specially formatted `case ...:' statements.
+
+ --help display this help and exit
+ --version output version information and exit
+
+EOF
+ }
+ exit $exit_code;
+}
+
+{
+ GetOptions
+ (
+ help => 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 <<EOF;
+/* 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 $file. */
+
+#if defined __linux__
+EOF
+
+ while (defined (my $line = <FH>))
+ {
+ $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 <hurd/hurd_types.h>
+#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 <phr@ocf.berkeley.edu>.
+ Adapted for GNU, fixed to factor UINT_MAX by Jim Meyering. */
+
+#include <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include <assert.h>
+#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 <rap@doc.ic.ac.uk>. */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+/* 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 = &parabuf[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 <config.h>
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <hurd/hurd_types.h>
+#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 <djm@gnu.ai.mit.edu>.
+
+# Make sure we get GNU id, if possible; also allow
+# it to be somewhere else in PATH if not installed yet.
+PATH=@bindir@:$PATH
+
+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 <http://www.gnu.org/licenses/gpl.html>.
+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 <djm@gnu.ai.mit.edu> */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#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 <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#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 <sys/systeminfo.h>
+
+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 <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <getopt.h>
+
+#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 <djm@gnu.ai.mit.edu> */
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+
+#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 <sys/wait.h>
+#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, &copy_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 <config.h>
+
+#include <assert.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#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 <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#if HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#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 <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#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
+ <http://www.opengroup.org/austin/mailarchives/ag-review/msg01794.html>
+ (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 <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#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 <Peter.Anvin@linux.org> and Dennis
+ Flaherty <dennisf@denix.elk.miles.com> based on original patches by
+ Greg Lee <lee@uhunix.uhcc.hawaii.edu>. */
+
+#include <config.h>
+#include <sys/types.h>
+
+#if HAVE_TERMIOS_H
+# include <termios.h>
+#endif
+#if HAVE_STROPTS_H
+# include <stropts.h>
+#endif
+#if HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+
+#ifdef WINSIZE_IN_PTEM
+# include <sys/stream.h>
+# include <sys/ptem.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <setjmp.h>
+#include <grp.h>
+#include <pwd.h>
+#include <getopt.h>
+#include <signal.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
+#ifndef SA_RESTART
+# define SA_RESTART 0
+#endif
+
+#include "system.h"
+#include <fnmatch.h>
+
+#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, &timespec) == 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\
+ FORMAT1<newline>FORMAT2, 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 <drepper@gnu.ai.mit.edu>. */
+
+#include <config.h>
+
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <djm@ai.mit.edu> */
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <djm@ai.mit.edu> */
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <djm@ai.mit.edu> */
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <assert.h>
+
+#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, &copy_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 <djm@gnu.ai.mit.edu> */
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <sys/resource.h>
+#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 <config.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#include "system.h"
+
+#include <regex.h>
+
+#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 <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#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 <config.h>
+
+#include <stdio.h>
+#include <assert.h>
+#include <getopt.h>
+#include <sys/types.h>
+#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 <float.h>
+
+#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 <djm@gnu.ai.mit.edu>.
+
+ 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 <config.h>
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#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 <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#if HAVE_WCHAR_H
+# include <wchar.h>
+#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 <config.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#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[="<TAB>"|:]
+ 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 <config.h>
+
+#include <getopt.h>
+#include <sys/types.h>
+#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 (&timespec);
+ 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 <TAB> 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 <TAB> with -J and <space>\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 <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#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 <djm@gnu.ai.mit.edu> */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#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 <pinard@iro.umontreal.ca>, 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 <pinard@iro.umontreal.ca> */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#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 <dmalloc.h>
+#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 = &regex->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 <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#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 <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <setjmp.h>
+#include <assert.h>
+
+#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 <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <assert.h>
+
+#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 <djm@ai.mit.edu> */
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "system.h"
+#include "c-strtod.h"
+#include "error.h"
+#include "quote.h"
+#include "xstrtod.h"
+
+/* Roll our own isfinite rather than using <math.h>, 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 <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+#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 <config.h>
+
+#include <getopt.h>
+#include <stdio.h>
+#include <assert.h>
+#include <setjmp.h>
+#include <sys/types.h>
+
+#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 <config.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#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 <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#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 <config.h>
+
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#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 <sys/resource.h>
+#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 <langinfo.h>
+#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 </tmp/sort.in
+ a b c 2 d
+ pq rs 1 t
+ $ /bin/sort -k1.7,1.7 </tmp/sort.in
+ pq rs 1 t
+ a b c 2 d
+
+ Unix sort produced the answer I expected: sort on the single character
+ in column 7. GNU sort produced different results, because it disagrees
+ on the interpretation of the key-end spec "M.N". Unix sort reads this
+ as "skip M-1 fields, then N-1 characters"; but GNU sort wants it to mean
+ "skip M-1 fields, then either N-1 characters or the rest of the current
+ field, whichever comes first". This extra clause applies only to
+ key-ends, not key-starts.
+ */
+
+ /* Make LIM point to the end of (one byte past) the current field. */
+ if (tab != TAB_DEFAULT)
+ {
+ char *newlim;
+ newlim = memchr (ptr, tab, lim - ptr);
+ if (newlim)
+ lim = newlim;
+ }
+ else
+ {
+ char *newlim;
+ newlim = ptr;
+ while (newlim < lim && blanks[to_uchar (*newlim)])
+ ++newlim;
+ while (newlim < lim && !blanks[to_uchar (*newlim)])
+ ++newlim;
+ lim = newlim;
+ }
+#endif
+
+ /* If we're ignoring leading blanks when computing the End
+ of the field, don't start counting bytes until after skipping
+ past any leading blanks. */
+ if (key->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
+ <http://lists.gnu.org/archive/html/bug-coreutils/2005-10/msg00086.html>
+ 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 = &minus;
+ }
+
+ 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 <config.h>
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <config.h>
+
+/* 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 <stddef.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#if USE_STATVFS
+# include <sys/statvfs.h>
+#elif HAVE_SYS_VFS_H
+# include <sys/vfs.h>
+#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 <sys/param.h>
+# include <sys/mount.h>
+# 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 <netinet/in.h>
+# include <nfs/nfs_clnt.h>
+# include <nfs/vfs.h>
+# endif
+#elif HAVE_OS_H /* BeOS */
+# include <fs_info.h>
+#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 <djm@gnu.ai.mit.edu> */
+
+#include <config.h>
+
+#ifdef TERMIOS_NEEDS_XOPEN_SOURCE
+# define _XOPEN_SOURCE
+#endif
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#if HAVE_TERMIOS_H
+# include <termios.h>
+#endif
+#if HAVE_STROPTS_H
+# include <stropts.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+
+#ifdef WINSIZE_IN_PTEM
+# include <sys/stream.h>
+# include <sys/ptem.h>
+#endif
+#ifdef GWINSZ_IN_SYS_PTY
+# include <sys/tty.h>
+# include <sys/pty.h>
+#endif
+#include <getopt.h>
+#include <stdarg.h>
+
+#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 <dupuy@cs.columbia.edu> 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 "<undef>";
+
+ if (ch >= 32)
+ {
+ if (ch < 127)
+ *bpout++ = ch;
+ else if (ch == 127)
+ {
+ *bpout++ = '^';
+ *bpout++ = '?';
+ }
+ else
+ {
+ *bpout++ = 'M',
+ *bpout++ = '-';
+ if (ch >= 128 + 32)
+ {
+ if (ch < 128 + 127)
+ *bpout++ = ch - 128;
+ else
+ {
+ *bpout++ = '^';
+ *bpout++ = '?';
+ }
+ }
+ else
+ {
+ *bpout++ = '^';
+ *bpout++ = ch - 128 + 64;
+ }
+ }
+ }
+ else
+ {
+ *bpout++ = '^';
+ *bpout++ = ch + 64;
+ }
+ *bpout = '\0';
+ return (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 <djm@gnu.ai.mit.edu>. */
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+/* Hide any system prototype for getusershell.
+ This is necessary because some Cray systems have a conflicting
+ prototype (returning `int') in <unistd.h>. */
+#define getusershell _getusershell_sys_proto_
+
+#include "system.h"
+#include "getpass.h"
+
+#undef getusershell
+
+#if HAVE_SYSLOG_H && HAVE_SYSLOG
+# include <syslog.h>
+#else
+# undef SYSLOG_SUCCESS
+# undef SYSLOG_FAILURE
+# undef SYSLOG_NON_ROOT
+#endif
+
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+
+#ifndef HAVE_ENDGRENT
+# define endgrent() ((void) 0)
+#endif
+
+#ifndef HAVE_ENDPWENT
+# define endpwent() ((void) 0)
+#endif
+
+#if HAVE_SHADOW_H
+# include <shadow.h>
+#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 <paths.h>
+#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 <config.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+#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 <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#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 <alloca.h>
+
+/* Include sys/types.h before this file. */
+
+#if 2 <= __GLIBC__ && 2 <= __GLIBC_MINOR__
+# if ! defined _SYS_TYPES_H
+you must include <sys/types.h> before including this file
+# endif
+#endif
+
+#include <sys/stat.h>
+
+#if !defined HAVE_MKFIFO
+# define mkfifo(name, mode) mknod (name, (mode) | S_IFIFO, 0)
+#endif
+
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+
+#include <unistd.h>
+
+#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 <limits.h>
+
+#include "pathmax.h"
+
+#include "configmake.h"
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+/* Since major is a function on SVR4, we can't use `ifndef major'. */
+#if MAJOR_IN_MKDEV
+# include <sys/mkdev.h>
+# define HAVE_MAJOR
+#endif
+#if MAJOR_IN_SYSMACROS
+# include <sys/sysmacros.h>
+# 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 <string.h>
+
+#include <errno.h>
+
+/* 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 <stdbool.h>
+#include <stdlib.h>
+
+/* 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 <fcntl.h>
+
+#ifndef F_OK
+# define F_OK 0
+# define X_OK 1
+# define W_OK 2
+# define R_OK 4
+#endif
+
+#include <dirent.h>
+#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 <inttypes.h>
+
+#include <ctype.h>
+
+#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 <locale.h>
+
+/* 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 <assert.h>
+
+/* 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 <config.h>
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include "system.h"
+
+#include <regex.h>
+
+#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, &regs))
+ == -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 <phr@ocf.berkeley.edu>.
+ Extensions by David MacKenzie <djm@gnu.ai.mit.edu>.
+ tail -f for multiple files by Ian Lance Taylor <ian@airs.com>. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <assert.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#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 <dev,inode> 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 <config.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <getopt.h>
+
+#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 <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#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 <sys/param.h>
+#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
+ * <int> '-'(eq|ne|le|lt|ge|gt) <int>
+ * file '-'(nt|ot|ef) file
+ * '(' <expr> ')'
+ * 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], &lt);
+ 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], &lt);
+ 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 <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <config.h>
+
+#include <stdio.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#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 <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#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 <kettenis@phys.uva.nl>. */
+
+/* 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 <config.h>
+
+#include <stdio.h>
+#include <assert.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <djm@gnu.ai.mit.edu>. */
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <djm@gnu.ai.mit.edu> */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <getopt.h>
+
+#if HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H
+# include <sys/systeminfo.h>
+#endif
+
+#if HAVE_SYS_SYSCTL_H
+# if HAVE_SYS_PARAM_H
+# include <sys/param.h> /* needed for OpenBSD 3.0 */
+# endif
+# include <sys/sysctl.h>
+# 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 <mach/machine.h>
+# include <mach-o/arch.h>
+#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 <djm@gnu.ai.mit.edu> */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+#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 <config.h>
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <config.h>
+#include <getopt.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include "system.h"
+
+#if HAVE_SYSCTL && HAVE_SYS_SYSCTL_H
+# include <sys/sysctl.h>
+#endif
+
+#if HAVE_OS_H
+# include <OS.h>
+#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 <utmp file> */
+ 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 <config.h>
+#include <getopt.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#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 <utmp file> */
+ 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 <config.h>
+
+#include <stdio.h>
+#include <getopt.h>
+#include <sys/types.h>
+
+#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 <<EOF;
+/* The first $ws_m1 elements correspond to the incremental offsets of the
+ first $wheel_size primes (@primes). The $wheel_size(th) element is the
+ difference between that last prime and the next largest integer
+ that is not a multiple of those primes. The remaining numbers
+ define the wheel. For more information, see
+ http://www.utm.edu/research/primes/glossary/WheelFactorization.html. */
+EOF
+
+ my @increments;
+ my $prev = 2;
+ for (my $i = 3; ; $i += 2)
+ {
+ my $rel_prime = 1;
+ foreach my $divisor (@primes)
+ {
+ $i != $divisor && $i % $divisor == 0
+ and $rel_prime = 0;
+ }
+
+ if ($rel_prime)
+ {
+ #warn $i, ' ', $i - $prev, "\n";
+ push @increments, $i - $prev;
+ $prev = $i;
+
+ $product + 1 < $i
+ and last;
+ }
+ }
+
+ print join (",\n", @increments), "\n";
+
+ exit 0;
+}
diff --git a/src/wheel-size.h b/src/wheel-size.h
new file mode 100644
index 0000000..1f2d609
--- /dev/null
+++ b/src/wheel-size.h
@@ -0,0 +1 @@
+#define WHEEL_SIZE 5
diff --git a/src/wheel.h b/src/wheel.h
new file mode 100644
index 0000000..1c04d59
--- /dev/null
+++ b/src/wheel.h
@@ -0,0 +1,491 @@
+/* The first 4 elements correspond to the incremental offsets of the
+ first 5 primes (2 3 5 7 11). The 5(th) element is the
+ difference between that last prime and the next largest integer
+ that is not a multiple of those primes. The remaining numbers
+ define the wheel. For more information, see
+ http://www.utm.edu/research/primes/glossary/WheelFactorization.html. */
+1,
+2,
+2,
+4,
+2,
+4,
+2,
+4,
+6,
+2,
+6,
+4,
+2,
+4,
+6,
+6,
+2,
+6,
+4,
+2,
+6,
+4,
+6,
+8,
+4,
+2,
+4,
+2,
+4,
+14,
+4,
+6,
+2,
+10,
+2,
+6,
+6,
+4,
+2,
+4,
+6,
+2,
+10,
+2,
+4,
+2,
+12,
+10,
+2,
+4,
+2,
+4,
+6,
+2,
+6,
+4,
+6,
+6,
+6,
+2,
+6,
+4,
+2,
+6,
+4,
+6,
+8,
+4,
+2,
+4,
+6,
+8,
+6,
+10,
+2,
+4,
+6,
+2,
+6,
+6,
+4,
+2,
+4,
+6,
+2,
+6,
+4,
+2,
+6,
+10,
+2,
+10,
+2,
+4,
+2,
+4,
+6,
+8,
+4,
+2,
+4,
+12,
+2,
+6,
+4,
+2,
+6,
+4,
+6,
+12,
+2,
+4,
+2,
+4,
+8,
+6,
+4,
+6,
+2,
+4,
+6,
+2,
+6,
+10,
+2,
+4,
+6,
+2,
+6,
+4,
+2,
+4,
+2,
+10,
+2,
+10,
+2,
+4,
+6,
+6,
+2,
+6,
+6,
+4,
+6,
+6,
+2,
+6,
+4,
+2,
+6,
+4,
+6,
+8,
+4,
+2,
+6,
+4,
+8,
+6,
+4,
+6,
+2,
+4,
+6,
+8,
+6,
+4,
+2,
+10,
+2,
+6,
+4,
+2,
+4,
+2,
+10,
+2,
+10,
+2,
+4,
+2,
+4,
+8,
+6,
+4,
+2,
+4,
+6,
+6,
+2,
+6,
+4,
+8,
+4,
+6,
+8,
+4,
+2,
+4,
+2,
+4,
+8,
+6,
+4,
+6,
+6,
+6,
+2,
+6,
+6,
+4,
+2,
+4,
+6,
+2,
+6,
+4,
+2,
+4,
+2,
+10,
+2,
+10,
+2,
+6,
+4,
+6,
+2,
+6,
+4,
+2,
+4,
+6,
+6,
+8,
+4,
+2,
+6,
+10,
+8,
+4,
+2,
+4,
+2,
+4,
+8,
+10,
+6,
+2,
+4,
+8,
+6,
+6,
+4,
+2,
+4,
+6,
+2,
+6,
+4,
+6,
+2,
+10,
+2,
+10,
+2,
+4,
+2,
+4,
+6,
+2,
+6,
+4,
+2,
+4,
+6,
+6,
+2,
+6,
+6,
+6,
+4,
+6,
+8,
+4,
+2,
+4,
+2,
+4,
+8,
+6,
+4,
+8,
+4,
+6,
+2,
+6,
+6,
+4,
+2,
+4,
+6,
+8,
+4,
+2,
+4,
+2,
+10,
+2,
+10,
+2,
+4,
+2,
+4,
+6,
+2,
+10,
+2,
+4,
+6,
+8,
+6,
+4,
+2,
+6,
+4,
+6,
+8,
+4,
+6,
+2,
+4,
+8,
+6,
+4,
+6,
+2,
+4,
+6,
+2,
+6,
+6,
+4,
+6,
+6,
+2,
+6,
+6,
+4,
+2,
+10,
+2,
+10,
+2,
+4,
+2,
+4,
+6,
+2,
+6,
+4,
+2,
+10,
+6,
+2,
+6,
+4,
+2,
+6,
+4,
+6,
+8,
+4,
+2,
+4,
+2,
+12,
+6,
+4,
+6,
+2,
+4,
+6,
+2,
+12,
+4,
+2,
+4,
+8,
+6,
+4,
+2,
+4,
+2,
+10,
+2,
+10,
+6,
+2,
+4,
+6,
+2,
+6,
+4,
+2,
+4,
+6,
+6,
+2,
+6,
+4,
+2,
+10,
+6,
+8,
+6,
+4,
+2,
+4,
+8,
+6,
+4,
+6,
+2,
+4,
+6,
+2,
+6,
+6,
+6,
+4,
+6,
+2,
+6,
+4,
+2,
+4,
+2,
+10,
+12,
+2,
+4,
+2,
+10,
+2,
+6,
+4,
+2,
+4,
+6,
+6,
+2,
+10,
+2,
+6,
+4,
+14,
+4,
+2,
+4,
+2,
+4,
+8,
+6,
+4,
+6,
+2,
+4,
+6,
+2,
+6,
+6,
+4,
+2,
+4,
+6,
+2,
+6,
+4,
+2,
+4,
+12,
+2,
+12
diff --git a/src/who.c b/src/who.c
new file mode 100644
index 0000000..db3af6e
--- /dev/null
+++ b/src/who.c
@@ -0,0 +1,827 @@
+/* GNU's who.
+ 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. */
+
+/* Written by jla; revised by djm; revised again by mstone */
+
+/* Output format:
+ name [state] line time [activity] [pid] [comment] [exit]
+ state: -T
+ name, line, time: not -q
+ idle: -u
+*/
+
+#include <config.h>
+#include <getopt.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#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 <blurf> <glop> */
+ my_line_only = true;
+ /* Fall through. */
+ case -1:
+ case 0: /* who */
+ who (UTMP_FILE, READ_UTMP_CHECK_PIDS);
+ break;
+
+ case 1: /* who <utmp file> */
+ 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 <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <getopt.h>
+
+#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 <djm@gnu.ai.mit.edu> */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+
+#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);
+ }
+ }
+}