summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoey Hess <joeyh@debian.org>2013-07-10 16:21:41 -0400
committerJoey Hess <joeyh@debian.org>2013-07-10 16:21:41 -0400
commit48ca6c6dab953dc0915cda78ac6c3c5bd4bd95ca (patch)
treed616e72a2afd9b29ec085ef5b57d925bfec6c4e6
downloadmoreutils-48ca6c6dab953dc0915cda78ac6c3c5bd4bd95ca.tar.gz
moreutils (0.49) unstable; urgency=low
* ts: Fix timezone used with -i * errno, isutf8: Fix zero-termination of option list, which could lead to getopt crash. Closes: #715867 (Thanks, Mayhem Team) # imported from the archive
-rw-r--r--COPYING340
-rw-r--r--Makefile60
-rw-r--r--README27
-rwxr-xr-xcheck-isutf846
-rwxr-xr-xchronic63
-rwxr-xr-xcombine167
-rw-r--r--debian/changelog450
-rw-r--r--debian/compat1
-rw-r--r--debian/control35
-rw-r--r--debian/copyright78
-rw-r--r--debian/docs1
-rwxr-xr-xdebian/rules12
-rw-r--r--errno.c228
-rw-r--r--errno.docbook136
-rw-r--r--ifdata.c546
-rw-r--r--ifdata.docbook321
-rw-r--r--ifne.c127
-rw-r--r--ifne.docbook91
-rw-r--r--isutf8.c280
-rw-r--r--isutf8.docbook125
-rw-r--r--lckdo.c233
-rw-r--r--lckdo.docbook157
-rw-r--r--mispipe.c179
-rw-r--r--mispipe.docbook93
-rw-r--r--parallel.c243
-rw-r--r--parallel.docbook162
-rw-r--r--pee.c61
-rw-r--r--pee.docbook89
-rw-r--r--physmem.c301
-rw-r--r--sponge.c373
-rw-r--r--sponge.docbook80
-rwxr-xr-xts154
-rwxr-xr-xvidir232
-rwxr-xr-xvipe74
-rwxr-xr-xzrun105
35 files changed, 5670 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..b7b5f53
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..be379ab
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,60 @@
+BINS=isutf8 ifdata ifne pee sponge mispipe lckdo parallel errno
+PERLSCRIPTS=vidir vipe ts combine zrun chronic
+MANS=sponge.1 vidir.1 vipe.1 isutf8.1 ts.1 combine.1 ifdata.1 ifne.1 pee.1 zrun.1 chronic.1 mispipe.1 lckdo.1 parallel.1 errno.1
+CFLAGS?=-O2 -g -Wall
+INSTALL_BIN?=install -s
+PREFIX?=/usr
+
+DOCBOOK2XMAN=docbook2x-man
+
+all: $(BINS) $(MANS)
+
+clean:
+ rm -f $(BINS) $(MANS) dump.c errnos.h errno.o
+
+install:
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ $(INSTALL_BIN) $(BINS) $(DESTDIR)$(PREFIX)/bin
+ install $(PERLSCRIPTS) $(DESTDIR)$(PREFIX)/bin
+
+ mkdir -p $(DESTDIR)$(PREFIX)/share/man/man1
+ install $(MANS) $(DESTDIR)$(PREFIX)/share/man/man1
+
+check: isutf8
+ ./check-isutf8
+
+isutf8.1: isutf8.docbook
+ $(DOCBOOK2XMAN) $<
+
+ifdata.1: ifdata.docbook
+ $(DOCBOOK2XMAN) $<
+
+ifne.1: ifne.docbook
+ $(DOCBOOK2XMAN) $<
+
+pee.1: pee.docbook
+ $(DOCBOOK2XMAN) $<
+
+sponge.1: sponge.docbook
+ $(DOCBOOK2XMAN) $<
+
+mispipe.1: mispipe.docbook
+ $(DOCBOOK2XMAN) $<
+
+lckdo.1: lckdo.docbook
+ $(DOCBOOK2XMAN) $<
+
+parallel.1: parallel.docbook
+ $(DOCBOOK2XMAN) $<
+
+errno.o: errnos.h
+errnos.h:
+ echo '#include <errno.h>' > dump.c
+ $(CC) -E -dD dump.c | awk '/^#define E/ { printf "{\"%s\",%s},\n", $$2, $$2 }' > errnos.h
+ rm -f dump.c
+
+errno.1: errno.docbook
+ $(DOCBOOK2XMAN) $<
+
+%.1: %
+ pod2man --center=" " --release="moreutils" $< > $@;
diff --git a/README b/README
new file mode 100644
index 0000000..c60df6c
--- /dev/null
+++ b/README
@@ -0,0 +1,27 @@
+This is a collection of the unix tools that nobody thought to write
+long ago, when unix was young. Currently it consists of these tools:
+
+chronic: runs a command quietly unless it fails
+combine: combine the lines in two files using boolean operations
+errno: look up errno names and descriptions
+ifdata: get network interface info without parsing ifconfig output
+isutf8: check if a file or standard input is utf-8
+ifne: run a command if the standard input is not empty
+lckdo: execute a program with a lock held (deprecated)
+mispipe: pipe two commands, returning the exit status of the first
+parallel: run multiple jobs at once
+pee: tee standard input to pipes
+sponge: soak up standard input and write to a file
+ts: timestamp standard input
+vidir: edit a directory in your text editor
+vipe: insert a text editor into a pipe
+zrun: automatically uncompress arguments to command
+
+Its web page is here: http://kitenet.net/~joey/code/moreutils/
+
+Your suggestions of additional tools to add to this collection are
+appreciated. The web page lists some that are under consideration but
+have not yet been included, I also welcome feedback on which of these to
+include.
+
+-- Joey Hess <joey@kitenet.net>
diff --git a/check-isutf8 b/check-isutf8
new file mode 100755
index 0000000..83a4eed
--- /dev/null
+++ b/check-isutf8
@@ -0,0 +1,46 @@
+#!/bin/bash
+# Bash needed for the character encodings below.
+#
+# Run checks for ./isutf8.
+#
+# Lars Wirzenius <liw@iki.fi>
+
+failed=0
+
+check() {
+ printf "$2" | ./isutf8 -q
+ ret=$?
+ if [ $ret != $1 ]; then
+ echo "Failure:"
+ echo " input: $2"
+ echo " expected: $1"
+ echo " got: $ret"
+ failed=1
+ fi
+ printf "$2" > check-isutf8.tmp.$$
+ ./isutf8 -q check-isutf8.tmp.$$
+ ret=$?
+ rm -f check-isutf8.tmp.$$
+ if [ $ret != $1 ]; then
+ echo "Failure (from file):"
+ echo " input: $2"
+ echo " expected: $1"
+ echo " got: $ret"
+ failed=1
+ fi
+}
+
+check 0 ''
+check 0 'a'
+check 0 'ab'
+check 0 '\xc2\xa9'
+
+check 1 '\xc2'
+check 1 '\xc2\x20'
+check 1 '\x20\xc2'
+check 1 '\300\200'
+check 1 '\xed\xa0\x88\xed\xbd\x85' # UTF-16 surrogates
+check 1 '\xef\xbf\xbe' # 0xFFFE
+check 1 '\xef\xbf\xbf' # 0xFFFF
+
+exit $failed
diff --git a/chronic b/chronic
new file mode 100755
index 0000000..fe012f6
--- /dev/null
+++ b/chronic
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+chronic - runs a command quietly unless it fails
+
+=head1 SYNOPSIS
+
+chronic COMMAND...
+
+=head1 DESCRIPTION
+
+chronic runs a command, and arranges for its standard out and standard
+error to only be displayed if the command fails (exits nonzero or crashes).
+If the command succeeds, any extraneous output will be hidden.
+
+A common use for chronic is for running a cron job. Rather than
+trying to keep the command quiet, and having to deal with mails containing
+accidental output when it succeeds, and not verbose enough output when it
+fails, you can just run it verbosely always, and use chronic to hide
+the successful output.
+
+ 0 1 * * * chronic backup # instead of backup >/dev/null 2>&1
+
+=head1 AUTHOR
+
+Copyright 2010 by Joey Hess <joey@kitenet.net>
+
+Original concept and "chronic" name by Chuck Houpt.
+
+Licensed under the GNU GPL version 2 or higher.
+
+=cut
+
+use warnings;
+use strict;
+use IPC::Run qw( start pump finish timeout );
+
+if (! @ARGV) {
+ die "usage: chronic COMMAND...\n";
+}
+
+my ($out, $err);
+my $h = IPC::Run::start \@ARGV, \*STDIN, \$out, \$err;
+$h->finish;
+my $ret=$h->full_result;
+
+if ($ret >> 8) { # child failed
+ showout();
+ exit ($ret >> 8);
+}
+elsif ($ret != 0) { # child killed by signal
+ showout();
+ exit 1;
+}
+else {
+ exit 0;
+}
+
+sub showout {
+ print STDOUT $out;
+ print STDERR $err;
+}
diff --git a/combine b/combine
new file mode 100755
index 0000000..99a4de6
--- /dev/null
+++ b/combine
@@ -0,0 +1,167 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+combine - combine sets of lines from two files using boolean operations
+
+=head1 SYNOPSIS
+
+combine file1 and file2
+
+combine file1 not file2
+
+combine file1 or file2
+
+combine file1 xor file2
+
+_ file1 and file2 _
+
+_ file1 not file2 _
+
+_ file1 or file2 _
+
+_ file1 xor file2 _
+
+=head1 DESCRIPTION
+
+B<combine> combines the lines in two files. Depending on the boolean
+operation specified, the contents will be combined in different ways:
+
+=over 4
+
+=item and
+
+Outputs lines that are in file1 if they are also present in file2.
+
+=item not
+
+Outputs lines that are in file1 but not in file2.
+
+=item or
+
+Outputs lines that are in file1 or file2.
+
+=item xor
+
+Outputs lines that are in either file1 or file2, but not in both files.
+
+=back
+
+"-" can be specified for either file to read stdin for that file.
+
+The input files need not be sorted, and the lines are output in the order
+they occur in file1 (followed by the order they occur in file2 for the two
+"or" operations). Bear in mind that this means that the operations are not
+commutative; "a and b" will not necessarily be the same as "b and a". To
+obtain commutative behavior sort and uniq the result.
+
+Note that this program can be installed as "_" to allow for the syntactic
+sugar shown in the latter half of the synopsis (similar to the test/[
+command). It is not currently installed as "_" by default, but you can
+alias it to that if you like.
+
+=head1 SEE ALSO
+
+join(1)
+
+=head1 AUTHOR
+
+Copyright 2006 by Joey Hess <joey@kitenet.net>
+
+Licensed under the GNU GPL.
+
+=cut
+
+use warnings;
+use strict;
+
+sub filemap {
+ my $file=shift;
+ my $sub=shift;
+
+ open (IN, $file) || die "$file: $!\n";
+ while (<IN>) {
+ chomp;
+ $sub->();
+ }
+ close IN;
+}
+
+sub hashify {
+ my $file=shift;
+
+ my %seen;
+ filemap $file, sub { $seen{$_}++ };
+ return \%seen;
+}
+
+sub compare_or {
+ my ($file1, $file2) = @_;
+
+ filemap $file1, sub { print "$_\n" };
+ filemap $file2, sub { print "$_\n" };
+}
+
+sub compare_xor {
+ my ($file1, $file2) = @_;
+
+ my (@lines2, %seen2);
+ filemap $file2,
+ sub {
+ push @lines2, $_;
+ $seen2{$_} = 1;
+ };
+
+ # Print all lines in file1 that are not in file2,
+ # and mark lines that are in both files by setting
+ # their value in %seen2 to 0.
+ filemap $file1,
+ sub {
+ if (exists $seen2{$_}) {
+ $seen2{$_} = 0;
+ }
+ else {
+ print "$_\n";
+ }
+ };
+
+ # Print all lines that are in file2 but not in file1.
+ # The value of these lines in seen2 is set to 1.
+ foreach (@lines2) {
+ print "$_\n" if $seen2{$_};
+ }
+}
+
+sub compare_not {
+ my ($file1, $file2) = @_;
+
+ my $seen=hashify($file2);
+ filemap $file1, sub { print "$_\n" unless $seen->{$_} };
+}
+
+sub compare_and {
+ my ($file1, $file2) = @_;
+
+ my $seen=hashify($file2);
+ filemap $file1, sub { print "$_\n" if $seen->{$_} };
+}
+
+if (@ARGV >= 4 && $ARGV[3] eq "_") {
+ delete $ARGV[3];
+}
+
+if (@ARGV != 3) {
+ die "Usage: combine file1 OP file2\n";
+}
+
+my $file1=shift;
+my $op=lc shift;
+my $file2=shift;
+
+if ($::{"compare_$op"}) {
+ no strict 'refs';
+ "compare_$op"->($file1, $file2);
+}
+else {
+ die "unknown operation, $op\n";
+}
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..7559ffb
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,450 @@
+moreutils (0.49) unstable; urgency=low
+
+ * ts: Fix timezone used with -i
+ * errno, isutf8: Fix zero-termination of option list, which could lead
+ to getopt crash. Closes: #715867
+ (Thanks, Mayhem Team)
+
+ -- Joey Hess <joeyh@debian.org> Wed, 10 Jul 2013 16:21:41 -0400
+
+moreutils (0.48) unstable; urgency=low
+
+ * Allow overriding PREFIX and CFLAGS to make the build more flexible
+ (Thanks, Ben Walton)
+ * Typo. Closes: #697113
+ * ts: -i enables incremental timestamping.
+ Thanks, Thomas Vander Stichele
+ * ts: Support single-digit day dates.
+ Thanks, Peter Lunicks
+ * sponge: Check fclose to detect certian short reads. Closes: #704453
+
+ -- Joey Hess <joeyh@debian.org> Sat, 04 May 2013 23:50:03 -0400
+
+moreutils (0.47) unstable; urgency=low
+
+ * errno: New utility that looks up errno numbers or names.
+
+ -- Joey Hess <joeyh@debian.org> Mon, 25 Jun 2012 16:31:17 -0400
+
+moreutils (0.46) unstable; urgency=low
+
+ * Typo. Closes: #649158
+ * combine: Avoid reading files twice, to support data coming from
+ pipes. Closes: #667960 Thanks, Carsten Hey
+
+ -- Joey Hess <joeyh@debian.org> Mon, 09 Apr 2012 16:19:34 -0400
+
+moreutils (0.45) unstable; urgency=low
+
+ * ts: Support %.s for seconds sinch epoch with subsecond resolution.
+ Closes: #619764
+
+ -- Joey Hess <joeyh@debian.org> Sun, 19 Jun 2011 15:44:28 -0400
+
+moreutils (0.44) unstable; urgency=low
+
+ * pee: Propigate exit status of commands run.
+
+ -- Joey Hess <joeyh@debian.org> Thu, 10 Mar 2011 17:38:14 -0400
+
+moreutils (0.43) unstable; urgency=low
+
+ * chronic: New command, runs a command quietly, unless it fails.
+ * Now depends on IPC::Run, used by chronic.
+
+ -- Joey Hess <joeyh@debian.org> Fri, 29 Oct 2010 15:54:37 -0400
+
+moreutils (0.42) unstable; urgency=low
+
+ * sponge: Guarantee that output file is always updated atomically,
+ by renaming a temp file into place. Closes: #592144
+ * sponge: Ensure that output file permissions are always preserved
+ if it already exists.
+ * Typo. Closes: #596032
+
+ -- Joey Hess <joeyh@debian.org> Wed, 06 Oct 2010 20:03:25 -0400
+
+moreutils (0.41) unstable; urgency=low
+
+ * ifdata.docbook: Mark interface as required in synopsis. Closes: #588397
+ * Add missing AUTHOR section to docbook man pages.
+ * sponge: Correct bad use of fread that caused a trailing quantity of
+ soaked data to be silently discarded when a temp file was used
+ and sponge output to stdout. Closes: #595220
+
+ -- Joey Hess <joeyh@debian.org> Thu, 02 Sep 2010 16:47:10 -0400
+
+moreutils (0.40) unstable; urgency=low
+
+ * lckdo: Now deprecated, since util-linux's flock(1) can do the same
+ thing.
+ * parallel: -i will now replace {} inside parameters, before the {} had
+ to be a separate parameter.
+
+ -- Joey Hess <joeyh@debian.org> Tue, 06 Jul 2010 19:38:14 -0400
+
+moreutils (0.39) unstable; urgency=low
+
+ * parallel: Fix exit code handling when commands are specified after --
+ * parallel: Make -j 0 do something reasonable (start all jobs at once).
+ * parallel: Fix to really avoid running new jobs when load is too high.
+ * parallel: Fix logic error in code handling -l that could make parallel
+ return a bogus 255 exit code when all jobs succeeded. Closes: #569617
+ * parallel: Allow a decimal load value to be specified with -l
+ * Caps sillyness. Closes: #570815
+ * zrun: Add support for .xz files.
+
+ -- Joey Hess <joeyh@debian.org> Tue, 23 Feb 2010 15:51:26 -0500
+
+moreutils (0.38) unstable; urgency=low
+
+ * Description improvements. Closes: #549450
+ (Thanks, Justin B Rye)
+ * parallel: Allow running independent commands,
+ like `parallel -j3 -- ls df "echo hi"`
+ * ifdata: Add FreeBSD kernel support, although some of the more esoteric
+ interface options are not currently supported in FreeBSD.
+ * parallel: Define WEXITED to allow building on FreeBSD kernel.
+ * Thanks Enrico Tassi for the FreeBSD kernel support, which should
+ be enough to get moreutils built on Debian kFreeBSD. Closes: #562609
+
+ -- Joey Hess <joeyh@debian.org> Tue, 09 Feb 2010 15:50:42 -0500
+
+moreutils (0.37) unstable; urgency=low
+
+ * parallel: Clarify man page regarding CPUs. Closes: #536597
+ * parallel: Add -n option. Thanks, Pierre Habouzit. Closes: #537992
+ (As a side effect, fixes a segfault if -- was omitted.)
+ * parallel.1: Typo fixes. Closes: #538147
+
+ -- Joey Hess <joeyh@debian.org> Sat, 25 Jul 2009 10:18:40 +0200
+
+moreutils (0.36) unstable; urgency=low
+
+ * parallel: New program, contributed by Tollef Fog Heen,
+ that can run multiple jobs in parallel, optionally checking
+ load average.
+ * mispipe: Fix closing of extra pipe FD before starting command
+ so it is not inherited by daemons. Closes: #533448
+ (Thanks, Jeremie Koenig)
+
+ -- Joey Hess <joeyh@debian.org> Fri, 10 Jul 2009 11:01:41 -0400
+
+moreutils (0.35) unstable; urgency=low
+
+ * ifdata: Don't assume that all interface names are 6 characters or less,
+ for instance "wmaster0" is longer. Increase the limit to 20 characters.
+ Closes: #526654 (Thanks, Alan Pope)
+ * isutf8: Reject UTF-8-encoded UTF-16 surrogates. Closes: #525301
+ (Thanks, Jakub Wilk and liw)
+
+ -- Joey Hess <joeyh@debian.org> Tue, 05 May 2009 15:06:41 -0400
+
+moreutils (0.34) unstable; urgency=low
+
+ * vipe: Avoid dying on empty input. Thanks, Anders Kaseorg
+ Closes: #508491
+
+ -- Joey Hess <joeyh@debian.org> Thu, 11 Dec 2008 15:11:23 -0500
+
+moreutils (0.33) unstable; urgency=low
+
+ * Support installing moreutils into prefixes other than /usr (Evan Broder)
+ * Fix zrun breakage introduced last version. Closes: #504129
+
+ -- Joey Hess <joeyh@debian.org> Fri, 31 Oct 2008 17:01:03 -0400
+
+moreutils (0.32) unstable; urgency=low
+
+ * zrun: Can be linked to zsomeprog to run the equivilant of zrun someprog.
+ Closes: #411623 (Stefan Fritsch)
+ * zrun: Add support for lzma and lzo. (Stefan Fritsch)
+ * Fix pod error in vidir(1).
+
+ -- Joey Hess <joeyh@debian.org> Sun, 26 Oct 2008 23:43:30 -0400
+
+moreutils (0.31) unstable; urgency=low
+
+ * pee.1: Document difference with tee in stdout.
+ * ts: Support displaying fractional seconds via a "%.S" conversion
+ specification. Closes: #482789
+
+ -- Joey Hess <joeyh@debian.org> Sat, 28 Jun 2008 23:19:31 -0400
+
+moreutils (0.30) unstable; urgency=low
+
+ * debhelper v7; rules file minimisation
+ * Use DESTDIR instead of PREFIX.
+ * Add a DOCBOOK2XMAN setting. (Greg KH)
+ * ifne: Add -n which makes it run the command if stdin is empty.
+ * ifne: If no command is specified, print usage information.
+
+ -- Joey Hess <joeyh@debian.org> Wed, 14 May 2008 19:37:42 -0400
+
+moreutils (0.29) unstable; urgency=low
+
+ * Add ifne, contributed by Javier Merino.
+ * sponge, ifne: Ensure that suspending/resuming doesn't
+ result in partial writes of the data, by using fwrite()
+ rather than write().
+ * sponge: Handle large data sizes by using a temp file rather than by
+ consuming arbitrary amounts of memory. Patch by Brock Noland.
+ * ts: Allow both -r and a format to be specified, to parse dates and output
+ in a specified format.
+ * ts: Fix bug in timezone regexp.
+
+ -- Joey Hess <joeyh@debian.org> Tue, 15 Apr 2008 15:30:52 -0400
+
+moreutils (0.28) unstable; urgency=low
+
+ * Moved to a git repository.
+ * vidir: Applied patch from Stefan Fritsch (one part of #412176):
+ - Check for control characters (especially newlines) in filenames
+ and error out, since this can greatly confuse the editor or vidir.
+ - If the source of a rename does not exist (and thus the rename will fail
+ anyway), vidir should not move an existing target file to a tmpfile.
+ - If a directory is renamed, vidir should take that into account when
+ renaming files in this directory.
+ - If a directory name is passed as name/ to vidir, vidir should not
+ add second slash after the name.
+ * vidir: Add support for unlinking directories. To recursivly delete
+ a directory and its contents, pipe find to vidir, and delete the directory
+ and its contents in the editor. Closes: #412176
+ * Add example to man page about recursive modification of directories.
+ Closes: #390099
+
+ -- Joey Hess <joeyh@debian.org> Sat, 02 Feb 2008 17:26:23 -0500
+
+moreutils (0.27) unstable; urgency=low
+
+ * vidir: Check exit codes of close. Closes: #463739
+
+ -- Joey Hess <joeyh@debian.org> Sat, 02 Feb 2008 16:42:18 -0500
+
+moreutils (0.26) unstable; urgency=low
+
+ * isutf8: Correct inverted exit code when passed a file to check.
+ Closes: #453306
+
+ -- Joey Hess <joeyh@debian.org> Wed, 28 Nov 2007 14:19:32 -0500
+
+moreutils (0.25) unstable; urgency=low
+
+ * isutf8: Detect and reject overlong UTF-8 sequences. Closes: #440951
+ Many thanks to liw for the patch.
+
+ -- Joey Hess <joeyh@debian.org> Mon, 12 Nov 2007 11:58:07 -0500
+
+moreutils (0.24) unstable; urgency=low
+
+ * vidir: Force numbers to normalised integers.
+ * vidir: Abort on unknown item numbers rather than deleting them.
+ Closes: #442440
+
+ -- Joey Hess <joeyh@debian.org> Sun, 16 Sep 2007 13:05:54 -0400
+
+moreutils (0.23) unstable; urgency=low
+
+ * Add pointer to join from combine's man page. Closes: #435516
+ * Don't strip binaries for debian package if built with
+ DEB_BUILD_OPTIONS=nostrip. Closes: #437577
+ * Include Michael Tokarev's lckdo program and replace / conflict with the
+ current lckdo package. (Robert Edmonds) Closes: #432906
+ * lckdo: Don't clear other flags when setting close on exec.
+
+ -- Joey Hess <joeyh@debian.org> Wed, 05 Sep 2007 21:45:25 -0400
+
+moreutils (0.22) unstable; urgency=low
+
+ * vidir, zrun: Don't put temp files in the current directory. Closes: #429367
+
+ -- Joey Hess <joeyh@debian.org> Mon, 25 Jun 2007 16:11:27 -0400
+
+moreutils (0.21) unstable; urgency=low
+
+ * Patch from Sergej Pupykin fixing ifdata -pN.
+
+ -- Joey Hess <joeyh@debian.org> Mon, 25 Jun 2007 13:07:21 -0400
+
+moreutils (0.20) unstable; urgency=low
+
+ * Typo fixes from Ralf Wildenhues.
+ * ifdata: Add -bips and -bops options contributed by André Appel,
+ to print the number of bytes of incoming/outgoing traffic per second.
+
+ -- Joey Hess <joeyh@debian.org> Sat, 23 Dec 2006 15:55:45 -0500
+
+moreutils (0.19) unstable; urgency=low
+
+ * vidir: Don't ignore files whose names end in dots. Closes: #398141
+ Thanks, Bram Sanders.
+
+ -- Joey Hess <joeyh@debian.org> Sat, 11 Nov 2006 22:02:17 -0500
+
+moreutils (0.18) unstable; urgency=low
+
+ * mispipe.docbook: Typo. Closes: #386756
+ * spongs: Output to stdout if no file is specified, useful in a pipeline
+ such as: cvs diff | sponge | patch -R -p0
+ Closes: #387501
+
+ -- Joey Hess <joeyh@debian.org> Thu, 14 Sep 2006 15:13:46 -0400
+
+moreutils (0.17) unstable; urgency=low
+
+ * Add missing \n to sponge usage. Closes: #383944
+ * Add mispipe, contributed by Nathanael Nerode. Pipes together two commands,
+ returning the exit status of the first.
+
+ -- Joey Hess <joeyh@debian.org> Thu, 7 Sep 2006 20:49:16 -0400
+
+moreutils (0.16) unstable; urgency=low
+
+ * Change the default ts format to include the month and day, for consistency
+ with syslog format.
+ * Add -r switch to ts, which makes it convert existing timestamps in
+ the input into relative times, such as "15m2s ago". Many common date
+ formats are supported.
+
+ -- Joey Hess <joeyh@debian.org> Sat, 19 Aug 2006 15:27:42 -0400
+
+moreutils (0.15) unstable; urgency=low
+
+ * Remove notes about potential tools from README, moved to wiki.
+ * vidir: Don't abort if it sees an empty or all-whitespace line.
+ * vidir: If just a filename is removed and the number is left,
+ treat this the same as removing the whole line, and delete the file,
+ instead of trying to rename the file to "".
+ * vidir: Remove the periods after the item numbers.
+ * vidir: Man page improvements. Closes: #378122
+ * combine: Man page improvements, to clarify even more that order does
+ matter and that the operations are not commutative. Closes: #361123
+ * combine: The behavior of "or" was fairly strange if lines were repeated in
+ a file. Changed behavior to just print all lines from both files, even
+ if this means printing dups. Not sure I like this behavior either, but
+ it's consistent with the very useful behaviors of "and" and "not".
+ * vidir, vipe: Use /usr/bin/editor if it's present, and EDITOR and VISUAL
+ arn't set, to comply with Debian policy. For portability, fall back to vi
+ if there's no /usr/bin/editor. Closes: #378623
+
+ -- Joey Hess <joeyh@debian.org> Tue, 25 Jul 2006 22:26:56 -0400
+
+moreutils (0.14) unstable; urgency=low
+
+ * vidir: Also support EDITOR or VISUAL that contains spaces and parameters.
+
+ -- Joey Hess <joeyh@debian.org> Wed, 12 Jul 2006 13:35:49 -0400
+
+moreutils (0.13) unstable; urgency=low
+
+ * ifdata: typo
+ * vipe: Support EDITOR or VISUAL that contains spaces and parameters.
+
+ -- Joey Hess <joeyh@debian.org> Wed, 12 Jul 2006 12:53:44 -0400
+
+moreutils (0.12) unstable; urgency=low
+
+ * Really fix typo. Closes: #369485
+ * zrun: Add usage message.
+ * ifdata: Fix bug in argument parsing that could make it segfault.
+ * combine: Allow operators to be written in any case.
+
+ -- Joey Hess <joeyh@debian.org> Wed, 28 Jun 2006 14:04:49 -0400
+
+moreutils (0.11) unstable; urgency=low
+
+ * combine: better synopsis. Closes: #360544
+ * ifdata: basically rewritten by Adam Lackorzynski, code size is 40%
+ smaller, macros removed, return checks added for all lib calls.
+ Closes: #361728
+ * vidir: behave properly if passed a directory name to edit. Closes: #369156
+ * check-isutf8: needs to be a bash script, the way it encodes characters
+ won't work with dash.
+ * check-isutf8: exit nonzero if any tests fail
+
+ -- Joey Hess <joeyh@debian.org> Sat, 27 May 2006 19:12:50 -0400
+
+moreutils (0.10) unstable; urgency=low
+
+ * ifdata: patch from Adam Lackorzynski to translate French error messages
+ to English and to convert some #defines to enums.
+ * ifdata: patch from Adam Lackorzynski to avoid printing info for
+ bogus interface. Closes: #360433
+ * Add zrun, contributed by Chung-chieh Shan (you might prefer to alias it to
+ "z").
+
+ -- Joey Hess <joeyh@debian.org> Sun, 2 Apr 2006 18:34:01 -0400
+
+moreutils (0.9) unstable; urgency=low
+
+ * ifdata: robustness patch from Adam Lackorzynski, in particular deal with
+ /proc not mounted. Closes: #358930
+ * Typo fixes. Closes: #359039, #359040
+ * pee: don't send a copy of every line sent to every command to stdout.
+ Seems best for it not to output anything to stdout on its on at all.
+ Closes: #359041
+
+ -- Joey Hess <joeyh@debian.org> Mon, 27 Mar 2006 13:45:45 -0500
+
+moreutils (0.8) unstable; urgency=low
+
+ * Back to Mithandir's C sponge, now fixed.
+ * tss.1, ifdata.1: typo fix. Closes: #358557, #358556
+ * ifdata: patch from its author to make it behave properly on big endian
+ systems. Closes: #358860
+
+ -- Joey Hess <joeyh@debian.org> Fri, 24 Mar 2006 20:52:21 -0500
+
+moreutils (0.7) unstable; urgency=low
+
+ * Add pee (pipe tee) contributed by Miek Gieben.
+ * ifdata: Patch from KELEMEN Peter to add support for printing hardware
+ interface. Closes: #357646
+
+ -- Joey Hess <joeyh@debian.org> Sat, 18 Mar 2006 14:17:08 -0500
+
+moreutils (0.6) unstable; urgency=low
+
+ * Revert to perl sponge since the C one corrupts larger files.
+ Hopefully temporary..
+
+ -- Joey Hess <joeyh@debian.org> Fri, 10 Mar 2006 15:33:43 -0500
+
+moreutils (0.5) unstable; urgency=low
+
+ * Added ifdata, by Benjamin BAYART (originally called ifcfg).
+ * Made ifdata -Wall clean.
+ * Made ifdata support -h and print usage on unknown options.
+ * Cleaned up ifdata's behavior when asked to print info for nonexistant
+ devices. Still needs improvement.
+ * Indentation improvements.
+ * Replaced and(1) and not(1) by combine, based on an idea by Matt Taggart.
+
+ -- Joey Hess <joeyh@debian.org> Tue, 7 Mar 2006 23:02:14 -0500
+
+moreutils (0.4) unstable; urgency=low
+
+ * Added versions of and(1) and not(1) that support arbitrary numbers of
+ input files.
+
+ -- Joey Hess <joeyh@debian.org> Sun, 5 Mar 2006 22:28:13 -0500
+
+moreutils (0.3) unstable; urgency=low
+
+ * Switch sponge to a C implementation by mithandir.
+ * Added a list of prospective tools that I am considering adding to the
+ README file.
+
+ -- Joey Hess <joeyh@debian.org> Fri, 3 Mar 2006 18:09:46 -0500
+
+moreutils (0.2) unstable; urgency=low
+
+ * Build dep on docbook-xml.
+
+ -- Joey Hess <joeyh@debian.org> Sun, 19 Feb 2006 18:40:56 -0500
+
+moreutils (0.1) unstable; urgency=low
+
+ * First release.
+
+ -- Joey Hess <joeyh@debian.org> Sun, 12 Feb 2006 17:11:21 -0500
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..7f8f011
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+7
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..aa5f025
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,35 @@
+Source: moreutils
+Section: utils
+Priority: optional
+Build-Depends: debhelper (>= 7), dpkg-dev (>= 1.9.0), docbook2x, docbook-xml
+Maintainer: Joey Hess <joeyh@debian.org>
+Standards-Version: 3.9.2
+Vcs-Git: git://git.kitenet.net/moreutils
+Homepage: http://kitenet.net/~joey/code/moreutils/
+
+Package: moreutils
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, libipc-run-perl
+Suggests: libtime-duration-perl, libtimedate-perl
+Conflicts: lckdo
+Replaces: lckdo
+Description: additional Unix utilities
+ This is a growing collection of the Unix tools that nobody thought
+ to write long ago, when Unix was young.
+ .
+ So far, it includes the following utilities:
+ - chronic: runs a command quietly unless it fails
+ - combine: combine the lines in two files using boolean operations
+ - errno: look up errno names and descriptions
+ - ifdata: get network interface info without parsing ifconfig output
+ - ifne: run a program if the standard input is not empty
+ - isutf8: check if a file or standard input is utf-8
+ - lckdo: execute a program with a lock held
+ - mispipe: pipe two commands, returning the exit status of the first
+ - parallel: run multiple jobs at once
+ - pee: tee standard input to pipes
+ - sponge: soak up standard input and write to a file
+ - ts: timestamp standard input
+ - vidir: edit a directory in your text editor
+ - vipe: insert a text editor into a pipe
+ - zrun: automatically uncompress arguments to command
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..56e9c1f
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,78 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Source: Contributed by many folks.
+
+Files: *
+Copyright: 2006-2011 Joey Hess <joeyh@debian.org>
+License: GPL-2+
+
+Files: isutf8.*
+Copyright: 2005 Lars Wirzenius
+License: GPL-2+
+
+Files: sponge.*
+Copyright: 2006 Tollef Fog Heen
+License: GPL-2
+
+Files: ifdata.c
+Copyright: 2002 Benjamin BAYART
+License: GPL-2+
+
+Files: pee.c
+Copyright: 2006 Miek Gieben
+License: GPL-2+
+
+Files: zrun
+Copyright: 2006 Chung-chieh Shan
+License: GPL-2+
+
+Files: mispipe.c
+Copyright: 2004 Nathanael Nerode
+License: GPL-2+ or Expat
+
+Files: lckdo.c lckdo.docbook
+Copyright: Michael Tokarev
+License: other
+ Public domain
+
+Files: ifne.c ifne.docbook
+Copyright: 2008 Javier Merino
+License: GPL-2+
+
+Files: parallel.c
+Copyright: 2008 Tollef Fog Heen <tfheen@err.no>
+License: GPL-2
+
+Files: errno.c errno.docbook
+Copyright: 2012 Lars Wirzenius
+License: GPL-2+
+
+Files: physmem.c
+Copyright: 2000, 2001, 2003, 2005, 2006 Free Software Foundation, Inc.
+License: GPL-2+
+
+License: GPL-2
+ The full text of the GPL is distributed as COPYING in moreutils's source,
+ and is distributed in /usr/share/common-licenses/GPL-2 on Debian systems.
+
+License: GPL-2+
+ The full text of the GPL is distributed as COPYING in moreutils's source,
+ and is distributed in /usr/share/common-licenses/GPL-2 on Debian systems.
+
+License: Expat
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..e845566
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1 @@
+README
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..cc50779
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,12 @@
+#!/usr/bin/make -f
+
+# Prevent the makefile from stripping, in case it's being built in
+# unstripped mode.
+export INSTALL_BIN=install
+
+%:
+ dh $@
+
+# Not intended for use by anyone except the author.
+announcedir:
+ @echo ${HOME}/src/joeywiki/code/moreutils/news
diff --git a/errno.c b/errno.c
new file mode 100644
index 0000000..d2f68a1
--- /dev/null
+++ b/errno.c
@@ -0,0 +1,228 @@
+/*
+ * errno.c -- look up errno names and descriptions
+ * Copyright 2012 Lars Wirzenius (liw@iki.fi)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You 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
+ */
+
+#define _GNU_SOURCE
+
+#include <ctype.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+
+
+static struct {
+ const char *name;
+ int code;
+} errnos[] = {
+ #include "errnos.h"
+};
+static const int num_errnos = sizeof(errnos) / sizeof(errnos[0]);
+
+
+static void
+report(const char *name, int code)
+{
+ printf("%s %d %s\n", name, code, strerror(code));
+}
+
+
+static bool
+report_from_name(const char *name)
+{
+ int i;
+ for (i = 0; i < num_errnos; ++i) {
+ if (strcasecmp(errnos[i].name, name) == 0) {
+ report(errnos[i].name, errnos[i].code);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+static bool
+report_from_code(int code)
+{
+ int i;
+ for (i = 0; i < num_errnos; ++i) {
+ if (errnos[i].code == code) {
+ report(errnos[i].name, code);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+static bool
+matches(int code, int num_words, char **words)
+{
+ const char *text = strerror(code);
+ int i;
+
+ for (i = 0; i < num_words; ++i) {
+ if (strcasestr(text, words[i]) == NULL)
+ return false;
+ }
+ return true;
+}
+
+
+static void
+search(int num_words, char **words)
+{
+ int i;
+
+ for (i = 0; i < num_errnos; ++i) {
+ if (matches(errnos[i].code, num_words, words))
+ report(errnos[i].name, errnos[i].code);
+ }
+}
+
+
+static void
+search_all(int num_words, char **words)
+{
+ FILE *f;
+
+ /* Static buffers are ugly, but they're simple. If anyone has a
+ locale name longer than a kilobyte, they will suffer, and they
+ will complain, and then I will fix this. */
+ char line[1024];
+
+ f = popen("locale -a", "r");
+ if (f == NULL) {
+ fprintf(stderr, "ERROR: Can't execute locale -a: %d: %s\n",
+ errno, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ while (fgets(line, sizeof line, f) != NULL) {
+ line[strcspn(line, "\n")] = '\0';
+ setlocale(LC_ALL, line);
+ search(num_words, words);
+ }
+
+ fclose(f);
+}
+
+
+static struct option
+options[] = {
+ { "help", 0, NULL, 'h' },
+ { "list", 0, NULL, 'l' },
+ { "search", 0, NULL, 's' },
+ { "search-all-locales", 0, NULL, 'S' },
+ { 0, 0, 0, 0 }
+};
+
+
+static void
+usage(void)
+{
+ printf("Usage: errno [-lsS] [--list] [--search] [--search-all-locales] "
+ "[keyword]\n");
+}
+
+
+int
+main(int argc, char **argv)
+{
+ int i;
+ int exit_code;
+ int index = 0;
+ enum {
+ lookup_mode,
+ list_mode,
+ search_mode,
+ search_all_mode
+ } mode = lookup_mode;
+
+ setlocale(LC_ALL, "");
+
+ for (;;) {
+ int c = getopt_long(argc, argv, "hlsS", options, &index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ usage();
+ return EXIT_SUCCESS;
+
+ case 'l':
+ mode = list_mode;
+ break;
+
+ case 's':
+ mode = search_mode;
+ break;
+
+ case 'S':
+ mode = search_all_mode;
+ break;
+
+ case '?':
+ break;
+
+ default:
+ fprintf(stderr, "getopt returned 0x%02x\n", c);
+ return EXIT_FAILURE;
+ }
+ }
+
+ exit_code = EXIT_SUCCESS;
+
+ switch (mode) {
+ case lookup_mode:
+ for (i = optind; i < argc; ++i) {
+ const char *arg = argv[i];
+ if (toupper(arg[0]) == 'E') {
+ if (!report_from_name(arg))
+ exit_code = EXIT_FAILURE;
+ } else if (isdigit(arg[0])) {
+ if (!report_from_code(atoi(arg)))
+ exit_code = EXIT_FAILURE;
+ } else {
+ fprintf(stderr, "ERROR: Not understood: %s\n", arg);
+ exit_code = EXIT_FAILURE;
+ }
+ }
+ break;
+
+ case list_mode:
+ for (i = 0; i < num_errnos; ++i)
+ report(errnos[i].name, errnos[i].code);
+ break;
+
+ case search_mode:
+ search(argc - optind, argv + optind);
+ break;
+
+ case search_all_mode:
+ search_all(argc - optind, argv + optind);
+ break;
+ }
+
+ return exit_code;
+}
diff --git a/errno.docbook b/errno.docbook
new file mode 100644
index 0000000..63ae492
--- /dev/null
+++ b/errno.docbook
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+
+Copyright 2012 Lars Wirzenius (liw@iki.fi)
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+Public License for more details.
+
+You 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
+
+-->
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"file:///usr/share/xml/docbook/schema/dtd/4.4/docbookx.dtd"
+[]>
+
+<refentry>
+ <refentryinfo>
+ <address>
+ <email>liw@liw.fi</email>
+ </address>
+ <author>
+ <firstname>Lars</firstname>
+ <surname>Wirzenius</surname>
+ </author>
+ <date>2012-06-05</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>errno</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>errno</refname>
+ <refpurpose>look up errno names and descriptions</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>errno</command>
+ <arg choice="req"><replaceable>name-or-code</replaceable></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>errno</command>
+ <arg>-ls</arg>
+ <arg>--list</arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>errno</command>
+ <arg>-s</arg>
+ <arg>--search</arg>
+ <arg choice="req"><replaceable>word</replaceable></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>errno</command>
+ <arg>-S</arg>
+ <arg>--search-all-locales</arg>
+ <arg choice="req"><replaceable>word</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para><command>errno</command> looks up errno macro names,
+ errno codes, and the corresponding descriptions. For example,
+ if given <literal>ENOENT</literal> on a Linux system, it
+ prints out the code 2 and the description "No such file or directory".
+ If given the code 2, it printes <literal>ENOENT</literal>
+ and the same description.</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>-l</option></term>
+ <term><option>--list</option></term>
+ <listitem>
+ <para>List all errno values.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-s</option></term>
+ <term><option>--search</option></term>
+ <listitem>
+ <para>Search for errors whose description contains
+ all the given words (case-insensitive).</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-S</option></term>
+ <term><option>--search-all-locales</option></term>
+ <listitem>
+ <para>Like <option>--search</option>, but searches all
+ installed locales.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>AUTHOR</title>
+ Lars Wirzenius
+ <para>
+
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>
+ <citerefentry>
+ <refentrytitle>errno</refentrytitle><manvolnum>3</manvolnum>
+ </citerefentry>
+ </para>
+
+ </refsect1>
+</refentry>
diff --git a/ifdata.c b/ifdata.c
new file mode 100644
index 0000000..2de98a0
--- /dev/null
+++ b/ifdata.c
@@ -0,0 +1,546 @@
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <sys/ioctl.h>
+
+#if defined(__linux__)
+ #include <linux/sockios.h>
+ #include <linux/if.h>
+#endif
+
+#if defined(__FreeBSD_kernel__)
+ #include <net/if.h>
+#endif
+
+#include <netinet/in.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+enum {
+ DO_EXISTS = 1,
+ DO_PEXISTS,
+ DO_PADDRESS,
+ DO_PMASK,
+ DO_PMTU,
+ DO_PCAST,
+ DO_PALL,
+ DO_PFLAGS,
+ DO_SINPACKETS,
+ DO_SINBYTES,
+ DO_SINERRORS,
+ DO_SINDROPS,
+ DO_SINALL,
+ DO_SINFIFO,
+ DO_SINFRAME,
+ DO_SINCOMPRESSES,
+ DO_SINMULTICAST,
+ DO_SOUTALL,
+ DO_SOUTBYTES,
+ DO_SOUTPACKETS,
+ DO_SOUTERRORS,
+ DO_SOUTDROPS,
+ DO_SOUTFIFO,
+ DO_SOUTCOLLS,
+ DO_SOUTCARRIER,
+ DO_SOUTMULTICAST,
+ DO_PNETWORK,
+ DO_PHWADDRESS,
+ DO_BIPS,
+ DO_BOPS
+};
+
+struct if_stat {
+ unsigned long long in_packets, in_bytes, in_errors, in_drops;
+ unsigned long long in_fifo, in_frame, in_compress, in_multicast;
+ unsigned long long out_bytes, out_packets, out_errors, out_drops;
+ unsigned long long out_fifo, out_colls, out_carrier, out_multicast;
+};
+
+
+void print_quad_ipv4(in_addr_t i) {
+ i = ntohl(i);
+ printf("%d.%d.%d.%d",
+ (i & 0xff000000) >> 24,
+ (i & 0x00ff0000) >> 16,
+ (i & 0x0000ff00) >> 8,
+ (i & 0x000000ff));
+}
+
+void print_quad_ipv6(uint16_t *a) {
+ printf("%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
+ a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]);
+}
+
+void print_quad(struct sockaddr *adr) {
+ switch (adr->sa_family) {
+ case AF_INET:
+ print_quad_ipv4(((struct sockaddr_in*)adr)->sin_addr.s_addr);
+ break;
+ case AF_INET6:
+ print_quad_ipv6(((struct sockaddr_in6*)adr)->sin6_addr.s6_addr16);
+ break;
+ default:
+ printf("NON-IP");
+ break;
+ }
+}
+
+enum print_error_enum {
+ PRINT_ERROR,
+ PRINT_NO_ERROR,
+};
+
+/**
+ * return 0 success
+ * 1 error
+ */
+static int do_socket_ioctl(const char *ifname, const unsigned long int request,
+ struct ifreq *req, int *ioctl_errno,
+ const enum print_error_enum print_error) {
+ int sock, res;
+
+ if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)) == -1)
+ return 1;
+ strncpy(req->ifr_name, ifname, IFNAMSIZ);
+ req->ifr_name[IFNAMSIZ - 1] = 0;
+
+ if ((res = ioctl(sock, request, req)) == -1) {
+ if (ioctl_errno)
+ *ioctl_errno = errno;
+ if (print_error == PRINT_ERROR)
+ fprintf(stderr, "ioctl on %s: %s\n", ifname, strerror(errno));
+ close(sock);
+ return 1;
+ }
+
+ close(sock);
+
+ return 0;
+}
+
+int if_exists(const char *iface) {
+ struct ifreq r;
+ return !do_socket_ioctl(iface, SIOCGIFFLAGS, &r, NULL, PRINT_NO_ERROR);
+}
+
+#if defined(__linux__)
+
+void if_flags(const char *iface) {
+ struct ifreq r;
+ unsigned int i;
+ const struct {
+ unsigned int flag;
+ char *name;
+ } flags[] = {
+ { IFF_UP, "Up" },
+ { IFF_BROADCAST, "Broadcast" },
+ { IFF_DEBUG, "Debugging" },
+ { IFF_LOOPBACK, "Loopback" },
+ { IFF_POINTOPOINT, "Ppp" },
+ { IFF_NOTRAILERS, "No-trailers" },
+ { IFF_RUNNING, "Running" },
+ { IFF_NOARP, "No-arp" },
+ { IFF_PROMISC, "Promiscuous" },
+ { IFF_ALLMULTI, "All-multicast" },
+ { IFF_MASTER, "Load-master" },
+ { IFF_SLAVE, "Load-slave" },
+ { IFF_MULTICAST, "Multicast" },
+ { IFF_PORTSEL, "Port-select" },
+ { IFF_AUTOMEDIA, "Auto-detect" },
+ { IFF_DYNAMIC, "Dynaddr" },
+ { 0xffff0000, "Unknown-flags" },
+ };
+
+ if (do_socket_ioctl(iface, SIOCGIFFLAGS, &r, NULL, PRINT_ERROR))
+ return;
+
+ for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++)
+ printf("%s%s%s", (r.ifr_flags & flags[i].flag) ? "On " : "Off ",
+ flags[i].name,
+ sizeof(flags) / sizeof(flags[0]) - 1 == i ? "" : "\n");
+}
+
+void if_hwaddr(const char *iface) {
+ struct ifreq r;
+ unsigned char *hwaddr;
+
+ if (do_socket_ioctl(iface, SIOCGIFHWADDR, &r, NULL, PRINT_ERROR))
+ return;
+
+ hwaddr = (unsigned char *)r.ifr_hwaddr.sa_data;
+ printf("%02X:%02X:%02X:%02X:%02X:%02X",
+ hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]);
+}
+
+#endif
+
+static struct sockaddr *if_addr_value(const char *iface, struct ifreq *r,
+ unsigned long int request) {
+ int e;
+
+ if (do_socket_ioctl(iface, request, r, &e, PRINT_NO_ERROR)) {
+ if (e == EADDRNOTAVAIL)
+ return &r->ifr_addr;
+ return NULL;
+ }
+ return &r->ifr_addr;
+}
+
+struct sockaddr *if_addr(const char *iface, struct ifreq *r) {
+ return if_addr_value(iface, r, SIOCGIFADDR);
+}
+
+struct sockaddr *if_mask(const char *iface, struct ifreq *r) {
+ return if_addr_value(iface, r, SIOCGIFNETMASK);
+}
+
+struct sockaddr *if_bcast(const char *iface, struct ifreq *r) {
+ return if_addr_value(iface, r, SIOCGIFBRDADDR);
+}
+
+struct sockaddr *if_network(const char *iface) {
+ struct sockaddr *saddr;
+ static struct ifreq req;
+ unsigned int mask;
+
+ if (!(saddr = if_mask(iface, &req)))
+ return NULL;
+
+ mask = ((struct sockaddr_in*)saddr)->sin_addr.s_addr;
+
+ if (!(saddr = if_addr(iface, &req)))
+ return NULL;
+
+ ((struct sockaddr_in*)saddr)->sin_addr.s_addr &= mask;
+ return saddr;
+}
+
+int if_mtu(const char *iface) {
+ static struct ifreq req;
+
+ if (do_socket_ioctl(iface, SIOCGIFMTU, &req, NULL, PRINT_ERROR))
+ return 0;
+
+ return req.ifr_mtu;
+}
+
+#if defined(__linux__)
+
+static void skipline(FILE *fd) {
+ int ch;
+ do {
+ ch = getc(fd);
+ } while (ch != '\n' && ch != EOF);
+}
+
+struct if_stat *get_stats(const char *iface) {
+ FILE *fd;
+ struct if_stat *ifstat;
+ char name[10];
+
+ if (!(ifstat = malloc(sizeof(struct if_stat)))) {
+ perror("malloc");
+ return NULL;
+ }
+
+ if ((fd = fopen("/proc/net/dev", "r")) == NULL) {
+ perror("fopen(\"/proc/net/dev\")");
+ free(ifstat);
+ return NULL;
+ }
+
+ /* Skip header */
+ skipline(fd);
+ skipline(fd);
+
+ do {
+ int items = fscanf(fd,
+ " %20[^:]:%llu %llu %llu %llu %llu %llu %llu %llu "
+ "%llu %llu %llu %llu %llu %llu %llu %llu",
+ name,
+ &ifstat->in_bytes, &ifstat->in_packets,
+ &ifstat->in_errors, &ifstat->in_drops,
+ &ifstat->in_fifo, &ifstat->in_frame,
+ &ifstat->in_compress, &ifstat->in_multicast,
+ &ifstat->out_bytes, &ifstat->out_packets,
+ &ifstat->out_errors, &ifstat->out_drops,
+ &ifstat->out_fifo, &ifstat->out_colls,
+ &ifstat->out_carrier, &ifstat->out_carrier
+ );
+
+ if (items == -1)
+ break;
+ if (items != 17) {
+ fprintf(stderr, "Invalid data read, check!\n");
+ break;
+ }
+
+ if (!strncmp(name, iface, sizeof(name))) {
+ fclose(fd);
+ return ifstat;
+ }
+ } while (!feof(fd));
+
+ fclose(fd);
+ free(ifstat);
+ return NULL;
+}
+
+#endif
+
+const struct {
+ char *option;
+ unsigned int flag;
+ unsigned int is_stat;
+ char *description;
+} options[] = {
+ { "-e", DO_EXISTS, 0, "Reports interface existence via return code" },
+ { "-p", DO_PALL, 0, "Print out the whole config of iface" },
+ { "-pe", DO_PEXISTS, 0, "Print out yes or no according to existence" },
+ { "-pa", DO_PADDRESS, 0, "Print out the address" },
+ { "-pn", DO_PMASK, 0, "Print netmask" },
+ { "-pN", DO_PNETWORK, 0, "Print network address" },
+ { "-pb", DO_PCAST, 0, "Print broadcast" },
+ { "-pm", DO_PMTU, 0, "Print mtu" },
+#if defined(__linux__)
+ { "-ph", DO_PHWADDRESS, 0, "Print out the hardware address" },
+ { "-pf", DO_PFLAGS, 0, "Print flags" },
+ { "-si", DO_SINALL, 1, "Print all statistics on input" },
+ { "-sip", DO_SINPACKETS, 1, "Print # of in packets" },
+ { "-sib", DO_SINBYTES, 1, "Print # of in bytes" },
+ { "-sie", DO_SINERRORS, 1, "Print # of in errors" },
+ { "-sid", DO_SINDROPS, 1, "Print # of in drops" },
+ { "-sif", DO_SINFIFO, 1, "Print # of in fifo overruns" },
+ { "-sic", DO_SINCOMPRESSES, 1, "Print # of in compress" },
+ { "-sim", DO_SINMULTICAST, 1, "Print # of in multicast" },
+ { "-so", DO_SOUTALL, 1, "Print all statistics on output" },
+ { "-sop", DO_SOUTPACKETS, 1, "Print # of out packets" },
+ { "-sob", DO_SOUTBYTES, 1, "Print # of out bytes" },
+ { "-soe", DO_SOUTERRORS, 1, "Print # of out errors" },
+ { "-sod", DO_SOUTDROPS, 1, "Print # of out drops" },
+ { "-sof", DO_SOUTFIFO, 1, "Print # of out fifo overruns" },
+ { "-sox", DO_SOUTCOLLS, 1, "Print # of out collisions" },
+ { "-soc", DO_SOUTCARRIER, 1, "Print # of out carrier loss" },
+ { "-som", DO_SOUTMULTICAST, 1, "Print # of out multicast" },
+ { "-bips",DO_BIPS, 1, "Print # of incoming bytes per second" },
+ { "-bops",DO_BOPS, 1, "Print # of outgoing bytes per second" },
+#endif
+};
+
+void usage(const char *name) {
+ unsigned int i;
+
+ fprintf(stderr, "Usage: %s [options] iface\n", name);
+ for (i = 0; i < sizeof(options) / sizeof(options[0]); i++) {
+ fprintf(stderr, " %5s %s\n",
+ options[i].option, options[i].description);
+ }
+}
+
+void add_do(int *ndo, int **todo, int act) {
+ *todo = realloc(*todo, (*ndo+1) * sizeof(int));
+ (*todo)[*ndo] = act;
+ *ndo += 1;
+}
+
+static void print_addr(struct sockaddr *sadr) {
+ if (!sadr) {
+ fprintf(stderr, "Error\n");
+ exit(1);
+ }
+ print_quad(sadr);
+}
+
+struct if_stat *ifstats, *ifstats2 = NULL;
+
+void please_do(int ndo, int *todo, const char *ifname) {
+ int i;
+ static struct ifreq req;
+ if (!ndo) return;
+ // printf("I have %d items in my queue.\n",ndo);
+ for (i=0; i<ndo; i++) {
+ switch (todo[i]) {
+ case DO_EXISTS:
+ exit(!if_exists(ifname));
+ case DO_PEXISTS:
+ printf("%s", if_exists(ifname) ? "yes" : "no");
+ break;
+ case DO_PADDRESS:
+ print_addr(if_addr(ifname, &req));
+ break;
+#if defined(__linux__)
+ case DO_PHWADDRESS:
+ if_hwaddr(ifname);
+ break;
+ case DO_PFLAGS:
+ if_flags(ifname);
+ break;
+#endif
+ case DO_PMASK:
+ print_addr(if_mask(ifname, &req));
+ break;
+ case DO_PCAST:
+ print_addr(if_bcast(ifname, &req));
+ break;
+ case DO_PMTU:
+ printf("%d", if_mtu(ifname));
+ break;
+ case DO_PNETWORK:
+ print_addr(if_network(ifname));
+ break;
+ case DO_PALL:
+ print_addr(if_addr(ifname, &req));
+ printf(" ");
+ print_addr(if_mask(ifname, &req));
+ printf(" ");
+ print_addr(if_bcast(ifname, &req));
+ printf(" ");
+ printf("%d", if_mtu(ifname));
+ break;
+#if defined(__linux__)
+ case DO_SINPACKETS:
+ printf("%llu",ifstats->in_packets);
+ break;
+ case DO_SINBYTES:
+ printf("%llu",ifstats->in_bytes);
+ break;
+ case DO_SINERRORS:
+ printf("%llu",ifstats->in_errors);
+ break;
+ case DO_SINDROPS:
+ printf("%llu",ifstats->in_drops);
+ break;
+ case DO_SINFIFO:
+ printf("%llu",ifstats->in_fifo);
+ break;
+ case DO_SINFRAME:
+ printf("%llu",ifstats->in_frame);
+ break;
+ case DO_SINCOMPRESSES:
+ printf("%llu",ifstats->in_compress);
+ break;
+ case DO_SINMULTICAST:
+ printf("%llu",ifstats->in_multicast);
+ break;
+ case DO_SINALL:
+ printf("%llu %llu %llu %llu %llu %llu %llu %llu",
+ ifstats->in_bytes, ifstats->in_packets,
+ ifstats->in_errors, ifstats->in_drops,
+ ifstats->in_fifo, ifstats->in_frame,
+ ifstats->in_compress, ifstats->in_multicast);
+ break;
+ case DO_SOUTBYTES:
+ printf("%llu",ifstats->out_bytes);
+ break;
+ case DO_SOUTPACKETS:
+ printf("%llu",ifstats->out_packets);
+ break;
+ case DO_SOUTERRORS:
+ printf("%llu",ifstats->out_errors);
+ break;
+ case DO_SOUTDROPS:
+ printf("%llu",ifstats->out_drops);
+ break;
+ case DO_SOUTFIFO:
+ printf("%llu",ifstats->out_fifo);
+ break;
+ case DO_SOUTCOLLS:
+ printf("%llu",ifstats->out_colls);
+ break;
+ case DO_SOUTCARRIER:
+ printf("%llu",ifstats->out_carrier);
+ break;
+ case DO_SOUTMULTICAST:
+ printf("%llu",ifstats->out_multicast);
+ break;
+ case DO_BIPS:
+ if (ifstats2 == NULL) {
+ sleep(1);
+ ifstats2 = get_stats(ifname);
+ }
+ printf("%llu", ifstats2->in_bytes-ifstats->in_bytes);
+ break;
+ case DO_BOPS:
+ if (ifstats2 == NULL) {
+ sleep(1);
+ ifstats2 = get_stats(ifname);
+ }
+ printf("%llu", ifstats2->out_bytes-ifstats->out_bytes);
+ break;
+ case DO_SOUTALL:
+ printf("%llu %llu %llu %llu %llu %llu %llu %llu",
+ ifstats->out_bytes, ifstats->out_packets,
+ ifstats->out_errors, ifstats->out_drops,
+ ifstats->out_fifo, ifstats->out_colls,
+ ifstats->out_carrier, ifstats->out_multicast);
+ break;
+#endif
+ default:
+ printf("Unknown command: %d", todo[i]);
+ break;
+ }
+ printf("\n");
+ }
+}
+
+int main(int argc, char *argv[]) {
+ int ndo=0;
+ int *todo=NULL;
+ char *ifname=NULL;
+ int narg = 0;
+ int do_stats = 0;
+ unsigned int i, found;
+
+ if (argc == 1) {
+ usage(*argv);
+ return 1;
+ }
+
+ while (narg < argc - 1) {
+ narg++;
+
+ found = 0;
+
+ for (i = 0; i < sizeof(options) / sizeof(options[0]); i++) {
+ if (!strcmp(argv[narg], options[i].option)) {
+ add_do(&ndo, &todo, options[i].flag);
+ do_stats |= options[i].is_stat;
+ found = 1;
+ break;
+ }
+ }
+
+ if (found)
+ continue;
+
+ if (argv[narg][0] == '-') {
+ usage(*argv);
+ return 1;
+ }
+ else {
+ ifname = argv[narg];
+ break;
+ }
+ }
+
+ if (narg + 1 < argc || !ifname) {
+ usage(*argv);
+ return 1;
+ }
+
+#if defined(__linux__)
+ if (do_stats && (ifstats = get_stats(ifname)) == NULL) {
+ fprintf(stderr, "Error getting statistics for %s\n", ifname);
+ return 1;
+ }
+#endif
+
+ please_do(ndo, todo, ifname);
+
+ return 0;
+}
diff --git a/ifdata.docbook b/ifdata.docbook
new file mode 100644
index 0000000..963943e
--- /dev/null
+++ b/ifdata.docbook
@@ -0,0 +1,321 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+
+Copyright 2006 Joey Hess
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+Public License for more details.
+
+You 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
+
+-->
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"file:///usr/share/xml/docbook/schema/dtd/4.4/docbookx.dtd"
+[]>
+
+<refentry>
+
+ <refentryinfo>
+ <author>
+ <firstname>Joey</firstname>
+ <surname>Hess</surname>
+ </author>
+ <date>2006-03-07</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>ifdata</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>ifdata</refname>
+ <refpurpose>get network interface info without
+ parsing ifconfig output</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>ifdata</command>
+ <arg>options</arg>
+ <arg choice="req"><replaceable>iface</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para>
+ <command>ifdata</command> can be used to check for
+ the existence of a network interface, or to get
+ information abut the interface, such as its IP
+ address. Unlike <command>ifconfig</command> or
+ <command>ip</command>, <command>ifdata</command>
+ has simple to parse output that is designed to be
+ easily used by a shell script.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><option>-h</option></term>
+ <listitem>
+ <para>Print out a help summary.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-e</option></term>
+ <listitem>
+ <para>Test to see if the interface exists,
+ exit nonzero if it does not.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-p</option></term>
+ <listitem>
+ <para>Prints out the whole configuration of
+ the interface.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-pe</option></term>
+ <listitem>
+ <para>Prints "yes" or "no" if the interface
+ exists or not.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-pa</option></term>
+ <listitem>
+ <para>Prints the IPv4 address of the
+ interface.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-pn</option></term>
+ <listitem>
+ <para>Prints the netmask of the
+ interface.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-pN</option></term>
+ <listitem>
+ <para>Prints the network address of the
+ interface.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-pb</option></term>
+ <listitem>
+ <para>Prints the broadcast address of the
+ interface.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-pm</option></term>
+ <listitem>
+ <para>Prints the MTU of the interface.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ <para>Following options are Linux only.</para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><option>-ph</option></term>
+ <listitem>
+ <para>Prints the hardware address of the
+ interface.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-pf</option></term>
+ <listitem>
+ <para>Prints the flags of the
+ interface.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-si</option></term>
+ <listitem>
+ <para>Prints out all the input statistics
+ of the interface.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-sip</option></term>
+ <listitem>
+ <para>Prints the number of input packets.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-sib</option></term>
+ <listitem>
+ <para>Prints the number of input bytes.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-sie</option></term>
+ <listitem>
+ <para>Prints the number of input errors.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-sid</option></term>
+ <listitem>
+ <para>Prints the number of dropped input
+ packets.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-sif</option></term>
+ <listitem>
+ <para>Prints the number of input fifo overruns.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-sic</option></term>
+ <listitem>
+ <para>Print the number of compressed input
+ packets.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-sim</option></term>
+ <listitem>
+ <para>Prints the number of input
+ multicast packets.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-so</option></term>
+ <listitem>
+ <para>Prints out all the output statistics
+ of the interface.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-sop</option></term>
+ <listitem>
+ <para>Prints the number of output packets.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-sob</option></term>
+ <listitem>
+ <para>Prints the number of output bytes.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-soe</option></term>
+ <listitem>
+ <para>Prints the number of output errors.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-sod</option></term>
+ <listitem>
+ <para>Prints the number of dropped
+ output packets.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-sof</option></term>
+ <listitem>
+ <para>Prints the number of output fifo overruns.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-sox</option></term>
+ <listitem>
+ <para>Print the number of output collisions.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-soc</option></term>
+ <listitem>
+ <para>Prints the number of output carrier
+ losses.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-som</option></term>
+ <listitem>
+ <para>Prints the number of output multicast
+ packets.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-bips</option></term>
+ <listitem>
+ <para>Prints the number of bytes of
+ incoming traffic measured in one second.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-bops</option></term>
+ <listitem>
+ <para>Prints the number of bytes of
+ outgoing traffic measured in one second.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>
+ Benjamin BAYART
+ </para>
+ </refsect1>
+</refentry>
diff --git a/ifne.c b/ifne.c
new file mode 100644
index 0000000..d8ecea9
--- /dev/null
+++ b/ifne.c
@@ -0,0 +1,127 @@
+/*
+ *
+ * Copyright 2008 Javier Merino <cibervicho@gmail.com>
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You 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
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <string.h>
+#define streq(a, b) (!strcmp((a), (b)))
+
+static void stdin_to_stream(char *buf, ssize_t r, FILE *outf) {
+ while (r > 0) {
+ if (fwrite(buf, r*sizeof(char), 1, outf) < 1) {
+ fprintf(stderr, "Write error\n");
+ exit(EXIT_FAILURE);
+ }
+ r = read(0, buf, BUFSIZ*sizeof(char));
+ }
+ if (r == -1) {
+ perror("read");
+ exit(EXIT_FAILURE);
+ }
+}
+
+int main(int argc, char **argv) {
+ ssize_t r;
+ int run_if_empty;
+ char **argv_exec;
+ int fds[2];
+ int child_status;
+ pid_t child_pid;
+ char buf[BUFSIZ];
+ FILE *outf;
+
+ if ((argc < 2) || ((argc == 2) && streq(argv[1], "-n"))) {
+ fprintf(stderr, "Usage: ifne [-n] command [args]\n");
+ return EXIT_FAILURE;
+ }
+
+ if (streq(argv[1], "-n")) {
+ run_if_empty = 1;
+ argv_exec = &argv[2];
+ } else {
+ run_if_empty = 0;
+ argv_exec = &argv[1];
+ }
+
+ r = read(0, buf, BUFSIZ*sizeof(char));
+
+ if ((r == 0) && !run_if_empty)
+ return EXIT_SUCCESS;
+ else if (r == -1) {
+ perror("read");
+ return EXIT_FAILURE;
+ }
+
+ if (pipe(fds)) {
+ perror("pipe");
+ return EXIT_FAILURE;
+ }
+
+ if (r && run_if_empty) {
+ /* don't run the subcommand if we read something from stdin and -n was set */
+ /* But write stdin to stdout so ifne -n can be piped without sucking the stream */
+ stdin_to_stream(buf, r, stdout);
+ return EXIT_SUCCESS;
+ }
+
+ child_pid = fork();
+ if (!child_pid) {
+ /* child process: rebind stdin and exec the subcommand */
+ close(fds[1]);
+ if (dup2(fds[0], 0)) {
+ perror("dup2");
+ return EXIT_FAILURE;
+ }
+
+ execvp(*argv_exec, argv_exec);
+ perror(*argv_exec);
+ close(fds[0]);
+ return EXIT_FAILURE;
+ } else if (child_pid == -1) {
+ perror("fork");
+ return EXIT_FAILURE;
+ }
+
+ /* Parent: write stdin to fds[1] */
+ close(fds[0]);
+ outf = fdopen(fds[1], "w");
+ if (! outf) {
+ perror("fdopen");
+ exit(1);
+ }
+
+ stdin_to_stream(buf, r, outf);
+ fclose(outf);
+
+ if (waitpid(child_pid, &child_status, 0) != child_pid) {
+ perror("waitpid");
+ return EXIT_FAILURE;
+ }
+ if (WIFEXITED(child_status)) {
+ return (WEXITSTATUS(child_status));
+ } else if (WIFSIGNALED(child_status)) {
+ raise(WTERMSIG(child_status));
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_FAILURE;
+}
diff --git a/ifne.docbook b/ifne.docbook
new file mode 100644
index 0000000..41fa9ab
--- /dev/null
+++ b/ifne.docbook
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+
+Copyright 2008 Javier Merino <cibervicho@gmail.com>
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+Public License for more details.
+
+You 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
+
+-->
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"file:///usr/share/xml/docbook/schema/dtd/4.4/docbookx.dtd"
+[]>
+
+<refentry>
+ <refentryinfo>
+ <address>
+ <email>cibervicho@gmail.com</email>
+ </address>
+ <author>
+ <firstname>Javier</firstname>
+ <surname>Merino</surname>
+ </author>
+ <date>2008-05-01</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>ifne</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>ifne</refname>
+ <refpurpose>Run command if the standard input is not empty</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>ifne [-n] command</command>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para><command>ifne</command> runs the following command if and only if
+ the standard input is not empty.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>-n</option></term>
+ <listitem>
+ <para>Reverse operation. Run the command if the standard input is empty.</para>
+ <para>Note that if the standard input is not empty, it is passed through ifne
+ in this case.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>EXAMPLE</title>
+ <cmdsynopsis>
+ <command>find . -name core | ifne mail -s "Core files found" root</command>
+ </cmdsynopsis>
+ </refsect1>
+
+ <refsect1>
+ <title>AUTHOR</title>
+
+ <para>Copyright 2008 by Javier Merino &lt;cibervicho@gmail.com&gt;</para>
+ <para>Licensed under the GNU GPL</para>
+ </refsect1>
+
+</refentry>
diff --git a/isutf8.c b/isutf8.c
new file mode 100644
index 0000000..9711124
--- /dev/null
+++ b/isutf8.c
@@ -0,0 +1,280 @@
+/*
+ * isutf8.c - do the input files look like valid utf-8 byte streams?
+ *
+ * Copyright (C) 2005 Lars Wirzenius
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+
+
+#define VERSION "1.1"
+
+
+/*
+ * Code to indicate an invalid UTF8 character.
+ */
+enum { INVALID_CHAR = 0xffffffff };
+
+
+/*
+ * Produce shortest UTF8 encoding of a 31-bit value in 'u', returning it
+ * in the array 'buf'. Return the number of bytes in the encoded value.
+ * If the value is too large (more than 32 bits or would take more than
+ * 'maxbytes' bytes), return -1.
+ */
+static int encodeutf8(unsigned long u, unsigned char *buf, size_t maxbytes)
+{
+ static const struct {
+ int nbytes;
+ unsigned long max;
+ } tab[] = {
+ { 1, 0x0000007F },
+ { 2, 0x000007FF },
+ { 3, 0x0000FFFF },
+ { 4, 0x001FFFFF },
+ { 5, 0x03FFFFFF },
+ { 6, 0x7FFFFFFF },
+ };
+ static const int ntab = sizeof(tab) / sizeof(tab[0]);
+ int i, j;
+
+ if (u > tab[ntab-1].max)
+ return -1;
+
+ for (i = 0; i < ntab; ++i) {
+ if (u <= tab[i].max)
+ break;
+ }
+ assert(i < ntab);
+
+ if (tab[i].nbytes > maxbytes)
+ return -1;
+
+ if (tab[i].nbytes == 1) { /* Special case */
+ buf[0] = u;
+ } else {
+ for (j = tab[i].nbytes-1; j > 0; --j) {
+ buf[j] = 0x80 | (u & 0x3f);
+ u >>= 6;
+ }
+
+ unsigned char mask = ~(0xFF >> tab[i].nbytes);
+ buf[0] = mask | u;
+ }
+
+ return tab[i].nbytes;
+}
+
+
+/*
+ * Return number of ones at the top of a byte.
+ *
+ * I'm pretty sure there is a fancy trick to do this without a loop,
+ * but I'm too tired to figure it out now. --liw
+ */
+static int high_ones(int c) {
+ int n;
+
+ for (n = 0; (c & 0x80) == 0x80; c <<= 1)
+ ++n;
+ return n;
+}
+
+
+/*
+ * Decode a UTF8 character from an array of bytes. Return character code.
+ * Upon error, return INVALID_CHAR.
+ */
+static unsigned long decodeutf8(unsigned char *buf, int nbytes)
+{
+ unsigned long u;
+ int i, j;
+
+ if (nbytes <= 0)
+ return INVALID_CHAR;
+
+ if (nbytes == 1) {
+ if (buf[0] >= 0x80)
+ return INVALID_CHAR;
+ return buf[0];
+ }
+
+ i = high_ones(buf[0]);
+ if (i != nbytes)
+ return INVALID_CHAR;
+ u = buf[0] & (0xff >> i);
+ for (j = 1; j < nbytes; ++j) {
+ if ((buf[j] & 0xC0) != 0x80)
+ return INVALID_CHAR;
+ u = (u << 6) | (buf[j] & 0x3f);
+ }
+
+ /* Conforming UTF-8 cannot contain codes 0xd800–0xdfff (UTF-16
+ surrogates) as well as 0xfffe and 0xffff. */
+ if (u >= 0xD800 && u <= 0xDFFF)
+ return INVALID_CHAR;
+ if (u == 0xFFFE || u == 0xFFFF)
+ return INVALID_CHAR;
+
+ return u;
+}
+
+
+/*
+ * Determine if the contents of an open file form a valid UTF8 byte stream.
+ * Do this by collecting bytes for a character into a buffer and then
+ * decode the bytes and re-encode them and compare that they are identical
+ * to the original bytes. If any step fails, return 0 for error. If EOF
+ * is reached, return 1 for OK.
+ */
+static int is_utf8_byte_stream(FILE *file, char *filename, int quiet) {
+ enum { MAX_UTF8_BYTES = 6 };
+ unsigned char buf[MAX_UTF8_BYTES];
+ unsigned char buf2[MAX_UTF8_BYTES];
+ int nbytes, nbytes2;
+ int c;
+ unsigned long code;
+ unsigned long line, col, byteoff;
+
+ nbytes = 0;
+ line = 1;
+ col = 1;
+ byteoff = 0;
+
+ for (;;) {
+ c = getc(file);
+
+ if (c == EOF || c < 0x80 || (c & 0xC0) != 0x80) {
+ /* New char starts, deal with previous one. */
+ if (nbytes > 0) {
+ code = decodeutf8(buf, nbytes);
+ if (code == INVALID_CHAR)
+ goto error;
+ nbytes2 = encodeutf8(code, buf2,
+ MAX_UTF8_BYTES);
+ if (nbytes != nbytes2 ||
+ memcmp(buf, buf2, nbytes) != 0)
+ goto error;
+ ++col;
+ }
+ nbytes = 0;
+ /* If it's UTF8, start collecting again. */
+ if (c != EOF && c >= 0x80)
+ buf[nbytes++] = c;
+ } else {
+ /* This is a continuation byte, append to buffer. */
+ if (nbytes == MAX_UTF8_BYTES)
+ goto error;
+ buf[nbytes++] = c;
+ }
+
+ if (c == EOF)
+ break;
+ else if (c == '\n') {
+ ++line;
+ byteoff = 0;
+ col = 1;
+ } else
+ ++byteoff;
+ }
+
+ if (nbytes != 0)
+ goto error;
+
+ return 1;
+
+error:
+ if (!quiet) {
+ printf("%s: line %lu, char %lu, byte offset %lu: "
+ "invalid UTF-8 code\n", filename, line, col, byteoff);
+ }
+ return 0;
+}
+
+
+static void usage(const char *program_name) {
+ printf("Usage: %s [-hq] [--help] [--quiet] [file ...]\n",
+ program_name);
+ printf("Check whether input files are valid UTF-8.\n");
+ printf("This is version %s.\n", VERSION);
+}
+
+
+int main(int argc, char **argv) {
+ int i, ok;
+ FILE *file;
+
+ int quiet;
+ struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "quiet", no_argument, &quiet, 1 },
+ { 0, 0, 0, 0 }
+ };
+ int opt;
+
+ quiet = 0;
+
+ while ((opt = getopt_long(argc, argv, "hq", options, NULL)) != -1) {
+ switch (opt) {
+ case 0:
+ break;
+
+ case 'h':
+ usage(argv[0]);
+ exit(0);
+ break;
+
+ case 'q':
+ quiet = 1;
+ break;
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+
+ if (optind == argc)
+ ok = is_utf8_byte_stream(stdin, "stdin", quiet);
+ else {
+ ok = 1;
+ for (i = optind; i < argc; ++i) {
+ file = fopen(argv[i], "r");
+ if (file == NULL) {
+ fprintf(stderr, "isutf8: %s: error %d: %s\n",
+ argv[i], errno,
+ strerror(errno));
+ ok = 0;
+ } else {
+ if (! is_utf8_byte_stream(file, argv[i], quiet))
+ ok = 0;
+ (void) fclose(file);
+ }
+ }
+ }
+
+ if (ok)
+ exit(0);
+ exit(EXIT_FAILURE);
+}
diff --git a/isutf8.docbook b/isutf8.docbook
new file mode 100644
index 0000000..58355a2
--- /dev/null
+++ b/isutf8.docbook
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+
+Copyright 2006 Lars Wirzenius (liw@iki.fi)
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+Public License for more details.
+
+You 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
+
+-->
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"file:///usr/share/xml/docbook/schema/dtd/4.4/docbookx.dtd"
+[]>
+
+<refentry>
+ <refentryinfo>
+ <address>
+ <email>liw@iki.fi</email>
+ </address>
+ <author>
+ <firstname>Lars</firstname>
+ <surname>Wirzenius</surname>
+ </author>
+ <date>2006-02-19</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>isutf8</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>isutf8</refname>
+ <refpurpose>check whether files are valid UTF-8</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>isutf8</command>
+ <arg><option>-hq</option></arg>
+ <arg><option>--help</option></arg>
+ <arg><option>--quiet</option></arg>
+ <group choice="opt">
+ <arg rep="repeat"><replaceable>file</replaceable></arg>
+ </group>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para><command>isutf8</command> checks whether files are
+ syntactically valid UTF-8. Input is either files named on the
+ command line, or the standard input. Notices about files with
+ invalid UTF-8 are printed to standard output.</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print out a help summary.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-q</option></term>
+ <term><option>--quiet</option></term>
+ <listitem>
+ <para>Don't print messages telling which files are
+ invalid UTF-8, merely indicate it with the exit
+ status.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>EXIT STATUS</title>
+
+ <para>If the file is valid UTF-8, the exit status is zero.
+ If the file is not valid UTF-8, or there is some
+ error, the exit status is non-zero.</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>AUTHOR</title>
+ Lars Wirzenius
+ <para>
+
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>
+ <citerefentry>
+ <refentrytitle>utf8</refentrytitle><manvolnum>7</manvolnum>
+ </citerefentry>
+ </para>
+
+ </refsect1>
+</refentry>
diff --git a/lckdo.c b/lckdo.c
new file mode 100644
index 0000000..4925409
--- /dev/null
+++ b/lckdo.c
@@ -0,0 +1,233 @@
+/* lckdo.c: run a program with a lock held,
+ * to prevent multiple processes running in parallel.
+ * Use just like `nice' or `nohup'.
+ * Written by Michael Tokarev <mjt@tls.msk.ru>
+ * Public domain.
+ */
+
+#define _GNU_SOURCE
+#define _BSD_SOURCE
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <sysexits.h>
+#include <sys/file.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+/* compile with -DUSE_FLOCK to use flock() instead of fcntl() */
+
+#if !defined(USE_FLOCK) && !defined(F_SETLKW)
+# define USE_FLOCK
+#endif
+
+#ifndef __GNUC__
+# ifndef __attribute__
+# define __attribute__(x)
+# endif
+#endif
+
+static char *progname;
+static void
+__attribute__((format(printf,3,4)))
+__attribute__((noreturn))
+error(int errnum, int exitcode, const char *fmt, ...) {
+ va_list ap;
+ fprintf(stderr, "%s: ", progname);
+ va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap);
+ if (errnum)
+ fprintf(stderr, ": %s\n", strerror(errnum));
+ else
+ fputs("\n", stderr);
+ exit(exitcode);
+}
+
+static const char *lckfile;
+static int quiet;
+
+static void sigalarm(int sig) {
+ if (quiet)
+ _exit(EX_TEMPFAIL);
+ error(0, EX_TEMPFAIL,
+ "lock file `%s' is already locked (timeout waiting)", lckfile);
+}
+
+int main(int argc, char **argv) {
+ int fd;
+ int c;
+ int create = O_CREAT;
+ int dofork = 1;
+ int waittime = 0;
+ int shared = 0;
+ int test = 0;
+ int fdn = -1;
+#ifndef USE_FLOCK
+ struct flock fl;
+#endif
+
+ if ((progname = strrchr(argv[0], '/')) == NULL)
+ progname = argv[0];
+ else
+ argv[0] = ++progname;
+
+ if (argc == 1) {
+ printf(
+ "%s: execute a program with a lock set.\n"
+ "Usage: %s [options] lockfile program [arguments]\n"
+ "where options are:\n"
+ " -w - if the lock is already held by another process,\n"
+ " wait for it to complete instead of failing immediately\n"
+ " -W sec - the same as -w but wait not more than sec seconds\n"
+ " -e - execute the program directly, no fork/wait\n"
+ " (keeps extra open file descriptor)\n"
+ " -E nnn - set the fd# to keep open in -e case (implies -e)\n"
+ " -n - do not create the lock file if it does not exist\n"
+ " -q - produce no output if lock is already held\n"
+ " -s - lock in shared (read) mode\n"
+ " -x - lock in exclusive (write) mode (default)\n"
+ " -t - test for lock existence"
+#ifndef USE_FLOCK
+ " (just prints pid if any with -q)\n"
+#endif
+ " (implies -n)\n"
+ , progname, progname);
+ return 0;
+ }
+
+ while ((c = getopt(argc, argv, "+wW:neE:sxtq")) != EOF) {
+ switch(c) {
+ case 'w':
+ if (!waittime)
+ waittime = -1;
+ break;
+ case 'W':
+ if ((waittime = atoi(optarg)) < 1)
+ error(0, EX_USAGE, "invalid wait time `%s'", optarg);
+ break;
+ case 't':
+ test = 1;
+ /* fall thru */
+ case 'n':
+ create = 0;
+ break;
+ case 'E':
+ if ((fdn = atoi(optarg)) < 0 || fdn == 2)
+ error(0, EX_USAGE, "invalid fd# `%s'", optarg);
+ /* fall thru */
+ case 'e':
+ dofork = 0;
+ break;
+ case 's':
+ shared = 1;
+ break;
+ case 'x':
+ shared = 0;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ default:
+ return EX_USAGE;
+ }
+ }
+
+ argc -= optind; argv += optind;
+ if (!argc || (!test && argc < 2))
+ error(0, EX_USAGE, "too few arguments given");
+
+ lckfile = *argv++;
+
+#ifdef USE_FLOCK
+ create |= O_RDONLY;
+#else
+ if (!test)
+ create |= shared ? O_RDONLY : O_WRONLY;
+#endif
+ fd = open(lckfile, create, 0666);
+ if (fd < 0) {
+ if (test && errno == ENOENT) {
+ if (!quiet)
+ printf("lockfile `%s' is not locked\n", lckfile);
+ return 0;
+ }
+ error(errno, EX_CANTCREAT, "unable to open `%s'", lckfile);
+ }
+
+ if (!test && fdn >= 0) {
+ /* dup it early to comply with stupid POSIX fcntl locking
+ * semantics */
+ if (dup2(fd, fdn) < 0)
+ error(errno, EX_OSERR, "dup2(%d,%d) failed", fd, fdn);
+ close(fd);
+ fd = fdn;
+ }
+
+ if (test)
+ waittime = 0;
+ else if (waittime > 0) {
+ alarm(waittime);
+ signal(SIGALRM, sigalarm);
+ }
+#ifdef USE_FLOCK
+ c = flock(fd, (shared ? LOCK_SH : LOCK_EX) | (waittime ? 0 : LOCK_NB));
+ if (test && c < 0 &&
+ (errno == EWOULDBLOCK || errno == EAGAIN || errno == EACCES)) {
+ if (!quiet)
+ printf("lockfile `%s' is locked\n", lckfile);
+ else
+ printf("locked\n");
+ return EX_TEMPFAIL;
+ }
+#else
+ memset(&fl, 0, sizeof(fl));
+ fl.l_type = shared ? F_RDLCK : F_WRLCK;
+ c = fcntl(fd, test ? F_GETLK : waittime ? F_SETLKW : F_SETLK, &fl);
+ if (test && c == 0) {
+ if (fl.l_type == F_UNLCK) {
+ if (!quiet)
+ printf("lockfile `%s' is not locked\n", lckfile);
+ return 0;
+ }
+ if (!quiet)
+ printf("lockfile `%s' is locked by process %d\n", lckfile, fl.l_pid);
+ else
+ printf("%d\n", fl.l_pid);
+ return EX_TEMPFAIL;
+ }
+#endif
+ if (waittime > 0)
+ alarm(0);
+ if (c < 0) {
+ if (errno != EWOULDBLOCK && errno != EAGAIN && errno != EACCES)
+ error(errno, EX_OSERR, "unable to lock `%s'", lckfile);
+ else if (quiet)
+ return EX_TEMPFAIL;
+ else
+ error(0, EX_TEMPFAIL, "lockfile `%s' is already locked", lckfile);
+ }
+
+ if (dofork) {
+ pid_t pid;
+ int flags = fcntl(fd, F_GETFD, 0);
+ if (flags < 0)
+ error(errno, EX_OSERR, "fcntl() failed");
+ fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
+ pid = fork();
+ if (pid < 0)
+ error(errno, EX_OSERR, "unable to fork");
+ else if (pid) {
+ if (wait(&c) < 0)
+ error(errno, EX_OSERR, "wait() failed");
+ if (WIFSIGNALED(c))
+ error(0, EX_SOFTWARE, "%s: %s", *argv,
+ strsignal(WTERMSIG(c)));
+ return WEXITSTATUS(c);
+ }
+ }
+ execvp(*argv, argv);
+ error(errno, EX_OSERR, "unable to execute %s", *argv);
+}
diff --git a/lckdo.docbook b/lckdo.docbook
new file mode 100644
index 0000000..effe84d
--- /dev/null
+++ b/lckdo.docbook
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+
+Written by Michael Tokarev <mjt@tls.msk.ru>
+Public domain.
+
+-->
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"file:///usr/share/xml/docbook/schema/dtd/4.4/docbookx.dtd"
+[]>
+
+<refentry>
+ <refentryinfo>
+ <address>
+ <email>mjt@tls.msk.ru</email>
+ </address>
+ <author>
+ <firstname>Michael</firstname>
+ <surname>Tokarev</surname>
+ </author>
+ <date>2007-08-15</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>lckdo</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>lckdo</refname>
+ <refpurpose>run a program with a lock held</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>lckdo</command>
+ <arg>options</arg>
+ <arg choice="req">lockfile</arg>
+ <arg choice="req">program</arg>
+ <arg>arguments</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para><command>lckdo</command> runs a program with a lock
+ held, in order to prevent multiple processes from running in
+ parallel. Use just like <command>nice</command> or
+ <command>nohup</command>.</para>
+
+ <para>Now that util-linux contains a similar command
+ named <command>flock</command>, lckdo is deprecated,
+ and will be removed from some future version of moreutils.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><option>-w</option></term>
+ <listitem>
+ <para>If the lock is already held by another process,
+ wait for it to complete instead of failing
+ immediately.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-W {sec}</option></term>
+ <listitem>
+ <para>The same as -w but wait not more than sec
+ seconds.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-e</option></term>
+ <listitem>
+ <para>Execute the program directly without forking and
+ waiting (keeps an extra file descriptor open).</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-E {nnn}</option></term>
+ <listitem>
+ <para>Set the file descriptor number to keep open when
+ exec()ing (implies -e).</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-n</option></term>
+ <listitem>
+ <para>Do not create the lock file if it does not
+ exist.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-q</option></term>
+ <listitem>
+ <para>Produce no output if lock is already held.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-s</option></term>
+ <listitem>
+ <para>Lock in shared (read) mode.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-x</option></term>
+ <listitem>
+ <para>Lock in exclusive (write) mode (default).</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-t</option></term>
+ <listitem>
+ <para>Test for lock existence.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>EXIT STATUS</title>
+
+ <para>If the lock was successfully acquired, the return value is that
+ of the program invoked by <command>lckdo</command>. If the lock
+ couldn't be acquired, EX_TEMPFAIL is returned. If there was a problem
+ opening/creating or locking the lock file, EX_CANTCREAT or EX_OSERR
+ will be returned.</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ Michael Tokarev
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/mispipe.c b/mispipe.c
new file mode 100644
index 0000000..d183d04
--- /dev/null
+++ b/mispipe.c
@@ -0,0 +1,179 @@
+/*
+ * mispipe: written by Nathanael Nerode.
+ *
+ * Copyright 2004 Nathanael Nerode.
+ *
+ * Licensed under the GPL version 2 or above, and dual-licensed under the
+ * following license:
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/*
+ * Usage: mispipe <command1> <command2>
+ * Run <command1> | <command2>, but return with the exit status of <command1>.
+ *
+ * This is designed for a very specific purpose: logging.
+ * "foo | logger -t foo"
+ * will return with the exit status of logger, not that of foo.
+ * "mispipe foo 'logger -t foo'"
+ * will return with the exit status of foo.
+ */
+
+/*
+ * To do:
+ * Make this into a fancy, internationalized, option-handling hellbeast.
+ * (But why bother? It does its job quite well.)
+ */
+
+#include <errno.h> /* errno */
+#include <sys/types.h>
+#include <unistd.h> /* pipe(), fork(),... */
+#include <stdlib.h> /* system() */
+#include <sys/wait.h> /* waitpid(), etc. */
+#include <stdio.h>
+#include <stdarg.h> /* va_list, for error() */
+
+static const char* progname; /* Stores argv[0] */
+static int filedes[2]; /* Stores pipe file descriptors */
+
+/* Subroutine for 'warning' and 'error' which prefixes progname */
+static void warning_prefix(void) {
+ fputs(progname, stderr);
+ fputs(": ", stderr);
+}
+
+/* Issue a warning, then die */
+__attribute__(( noreturn, format (printf, 1, 2) ))
+static void error(const char* formatstr, ...) {
+ va_list ap;
+ va_start(ap, formatstr);
+ warning_prefix();
+ vfprintf(stderr, formatstr, ap);
+ va_end(ap);
+ exit(1);
+}
+
+/* Issue a warning, then die, with errno */
+__attribute__(( noreturn ))
+static void error_with_errno(const char* message) {
+ int saved_errno;
+ saved_errno=errno;
+ warning_prefix();
+ fputs(message, stderr);
+ fputs(": error number ", stderr);
+ fprintf(stderr, "%i\n", saved_errno);
+ exit(1);
+}
+
+/* Convert 'wait'/'system'-style exit status to 'exit'-style exit status */
+__attribute__(( const ))
+static int shorten_status(int status_big) {
+ if (WIFEXITED(status_big))
+ return WEXITSTATUS(status_big);
+ if (WIFSIGNALED(status_big))
+ return 128+WTERMSIG(status_big);
+ if (WIFSTOPPED(status_big))
+ return 128+WSTOPSIG(status_big);
+#ifdef WCOREDUMP
+ if (WCOREDUMP(status_big))
+ error("Ow, somebody dumped core!");
+#endif
+ error("shorten_status got an invalid status (?!)");
+}
+
+/* All the work for command 2. */
+__attribute__(( noreturn ))
+static void subprocess2(const char* cmd) {
+ /* Close the old standard input. */
+ if (close(0))
+ error_with_errno("Failed (in child) closing standard input");
+ /* Make the reading end of the pipe the new standard input. */
+ if (dup2(filedes[0], 0) == -1)
+ error_with_errno("Failed (in child) redefining standard input");
+ /* Close the original file descriptor for it */
+ if (close(filedes[0]))
+ error_with_errno("Failed (in child) closing filedes[0]");
+ /* Close the other end of the pipe. */
+ if (close(filedes[1]))
+ error_with_errno("Failed (in child) closing filedes[1]");
+ /* Do the second command, and throw away the exit status. */
+ system(cmd);
+ /* Close the standard input. */
+ if (close(0))
+ error_with_errno("Failed (in child) closing standard output "
+ " (while cleaning up)");
+ exit(0);
+}
+
+int main (int argc, const char ** argv) {
+ int status_big; /* Exit status, 'wait' and 'system' style */
+ pid_t child2_pid;
+ pid_t dead_pid;
+
+ /* Set progname */
+ progname = argv[0];
+
+ /* Verify arguments */
+ if (argc != 3)
+ error("Wrong number of args, aborting\n");
+ /* Open a new pipe */
+ if (pipe(filedes))
+ error_with_errno("pipe() failed");
+
+ /* Fork to run second command */
+ child2_pid = fork();
+ if (child2_pid == 0)
+ subprocess2(argv[2]);
+ if (child2_pid == -1)
+ error_with_errno("fork() failed");
+
+ /* Run first command inline (seriously!) */
+ /* Close standard output. */
+ if (close(1))
+ error_with_errno("Failed closing standard output");
+ /* Make the writing end of the pipe the new standard output. */
+ if (dup2(filedes[1], 1) == -1)
+ error_with_errno("Failed redefining standard output");
+ /* Close the original file descriptor for it. */
+ if (close(filedes[1]))
+ error_with_errno("Failed closing filedes[1]");
+ /* Close the other end of the pipe. */
+ if (close(filedes[0]))
+ error_with_errno("Failed closing filedes[0]");
+ /* Do the first command, and (crucially) get the status. */
+ status_big = system(argv[1]);
+
+ /* Close standard output. */
+ if (close(1))
+ error_with_errno("Failed closing standard output (while cleaning up)");
+
+ /* Wait for the other process to exit. */
+ /* We don't care about the status. */
+ dead_pid = waitpid(child2_pid, NULL, WUNTRACED);
+ if (dead_pid == -1) {
+ error_with_errno("waitpid() failed");
+ }
+ else if (dead_pid != child2_pid) {
+ error("waitpid(): Who died? %i\n", dead_pid);
+ }
+
+ /* Return the desired exit status. */
+ return shorten_status(status_big);
+}
diff --git a/mispipe.docbook b/mispipe.docbook
new file mode 100644
index 0000000..bd8faa8
--- /dev/null
+++ b/mispipe.docbook
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+
+Copyright 2006 Joey Hess <joey@kitenet.net>
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+Public License for more details.
+
+You 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
+
+-->
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"file:///usr/share/xml/docbook/schema/dtd/4.4/docbookx.dtd"
+[]>
+
+<refentry>
+ <refentryinfo>
+ <address>
+ <email>neroden@fastmail.fm</email>
+ </address>
+ <author>
+ <firstname>Nathanael</firstname>
+ <surname>Nerode</surname>
+ </author>
+ <date>2006-09-07</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>mispipe</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>mispipe</refname>
+ <refpurpose>pipe two commands, returning the exit status of
+ the first</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>mispipe</command>
+ <arg><replaceable>"command1"</replaceable></arg>
+ <arg><replaceable>"command2"</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para><command>mispipe</command> pipes two commands
+ together like the shell does, but unlike piping in the
+ shell, which returns the exit status of the last command;
+ when using mispipe, the exit status of the first command
+ is returned.
+ </para>
+
+ <para>
+ Note that some shells, notably <command>bash</command>,
+ do offer a pipefail option, however, that option does not
+ behave the same since it makes a failure of any command in
+ the pipeline be returned, not just the exit status of the
+ first.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>EXIT STATUS</title>
+
+ <para>The exit status of the first command. If the process
+ terminated abnormally (due to a signal), 128 will be added
+ to its exit status.</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ Nathanael Nerode
+ </para>
+ </refsect1>
+</refentry>
diff --git a/parallel.c b/parallel.c
new file mode 100644
index 0000000..d283b96
--- /dev/null
+++ b/parallel.c
@@ -0,0 +1,243 @@
+/*
+ * parallel.c - run commands in parallel until you run out of commands
+ *
+ * Copyright © 2008 Tollef Fog Heen <tfheen@err.no>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You 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
+ *
+ */
+
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#if defined(__FreeBSD_kernel__)
+#define WEXITED 0
+#endif
+
+void usage() {
+ printf("parallel [OPTIONS] command -- arguments\n\tfor each argument, "
+ "run command with argument, in parallel\n");
+ printf("parallel [OPTIONS] -- commands\n\trun specified commands in parallel\n");
+ exit(1);
+}
+
+void exec_child(char **command, char **arguments, int replace_cb, int nargs) {
+ if (fork() != 0) {
+ return;
+ }
+
+ if (command[0]) {
+ char **argv;
+ int argc = 0;
+ int i;
+ char *s;
+
+ while (command[argc] != 0) {
+ argc++;
+ }
+ if (! replace_cb)
+ argc++;
+ argv = calloc(sizeof(char*), argc + nargs);
+
+ for (i = 0; i < argc; i++) {
+ while (replace_cb && (s=strstr(command[i], "{}"))) {
+ char *buf=malloc(strlen(command[i]) + strlen(arguments[0]));
+ s[0]='\0';
+ sprintf(buf, "%s%s%s", command[i], arguments[0], s+2);
+ command[i]=buf;
+ }
+ argv[i] = command[i];
+ }
+ if (! replace_cb)
+ memcpy(argv + i - 1, arguments, nargs * sizeof(char *));
+ execvp(argv[0], argv);
+ exit(1);
+ }
+ else {
+ int ret=system(arguments[0]);
+ if (WIFEXITED(ret)) {
+ exit(WEXITSTATUS(ret));
+ }
+ else {
+ exit(1);
+ }
+ }
+ return;
+}
+
+int wait_for_child(int options) {
+ id_t id_ignored = 0;
+ siginfo_t infop;
+
+ infop.si_pid = 0;
+ waitid(P_ALL, id_ignored, &infop, WEXITED | options);
+ if (infop.si_pid == 0) {
+ return -1; /* Nothing to wait for */
+ }
+ if (infop.si_code == CLD_EXITED) {
+ return infop.si_status;
+ }
+ return 1;
+}
+
+int main(int argc, char **argv) {
+ int maxjobs = -1;
+ int curjobs = 0;
+ double maxload = -1;
+ int argsatonce = 1;
+ int opt;
+ char **command = calloc(sizeof(char*), argc);
+ char **arguments = NULL;
+ int argidx = 0;
+ int arglen = 0;
+ int cidx = 0;
+ int returncode = 0;
+ int replace_cb = 0;
+ char *t;
+
+ while ((argv[optind] && strcmp(argv[optind], "--") != 0) &&
+ (opt = getopt(argc, argv, "+hij:l:n:")) != -1) {
+ switch (opt) {
+ case 'h':
+ usage();
+ break;
+ case 'i':
+ replace_cb = 1;
+ break;
+ case 'j':
+ errno = 0;
+ maxjobs = strtoul(optarg, &t, 0);
+ if (errno != 0 || (t-optarg) != strlen(optarg)) {
+ fprintf(stderr, "option '%s' is not a number\n",
+ optarg);
+ exit(2);
+ }
+ break;
+ case 'l':
+ errno = 0;
+ maxload = strtod(optarg, &t);
+ if (errno != 0 || (t-optarg) != strlen(optarg)) {
+ fprintf(stderr, "option '%s' is not a number\n",
+ optarg);
+ exit(2);
+ }
+ break;
+ case 'n':
+ errno = 0;
+ argsatonce = strtoul(optarg, &t, 0);
+ if (errno != 0 || argsatonce < 1 || (t-optarg) != strlen(optarg)) {
+ fprintf(stderr, "option '%s' is not a positive number\n",
+ optarg);
+ exit(2);
+ }
+ break;
+ default: /* ’?’ */
+ usage();
+ break;
+ }
+ }
+
+ if (replace_cb && argsatonce > 1) {
+ fprintf(stderr, "options -i and -n are incomaptible\n");
+ exit(2);
+ }
+
+ if (maxjobs < 0) {
+#ifdef _SC_NPROCESSORS_ONLN
+ maxjobs = sysconf(_SC_NPROCESSORS_ONLN);
+#else
+#warning Cannot autodetect number of CPUS on this system: _SC_NPROCESSORS_ONLN not defined.
+ maxjobs = 1;
+#endif
+ }
+
+ while (optind < argc) {
+ if (strcmp(argv[optind], "--") == 0) {
+ int i;
+
+ optind++;
+ arglen = argc - optind;
+ arguments = calloc(sizeof(char *), arglen);
+ if (! arguments) {
+ exit(1);
+ }
+
+ for (i = 0; i < arglen; i++) {
+ arguments[i] = strdup(argv[optind + i]);
+ }
+ optind += i;
+ }
+ else {
+ command[cidx] = strdup(argv[optind]);
+ cidx++;
+ }
+ optind++;
+ }
+
+ if (argsatonce > 1 && ! command[0]) {
+ fprintf(stderr, "option -n cannot be used without a command\n");
+ exit(2);
+ }
+
+ while (argidx < arglen) {
+ double load;
+
+ getloadavg(&load, 1);
+
+ if ((maxjobs == 0 || curjobs < maxjobs) &&
+ (maxload < 0 || load < maxload)) {
+
+ if (argsatonce > arglen - argidx)
+ argsatonce = arglen - argidx;
+ exec_child(command, arguments + argidx,
+ replace_cb, argsatonce);
+ argidx += argsatonce;
+ curjobs++;
+ }
+
+ if (maxjobs == 0 || curjobs == maxjobs) {
+ returncode |= wait_for_child(0);
+ curjobs--;
+ }
+
+ if (maxload > 0 && load >= maxload) {
+ int r;
+ sleep(1); /* XXX We should have a better
+ * heurestic than this */
+ r = wait_for_child(WNOHANG);
+ if (r > 0)
+ returncode |= r;
+ if (r != -1)
+ curjobs--;
+ }
+ }
+ while (curjobs > 0) {
+ returncode |= wait_for_child(0);
+ curjobs--;
+ }
+
+ return returncode;
+}
+
diff --git a/parallel.docbook b/parallel.docbook
new file mode 100644
index 0000000..d3ffcce
--- /dev/null
+++ b/parallel.docbook
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+
+Written by Joey Hess
+
+-->
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"file:///usr/share/xml/docbook/schema/dtd/4.4/docbookx.dtd"
+[]>
+
+<refentry>
+ <refentryinfo>
+ <address>
+ <email>joey@kitenet.net</email>
+ </address>
+ <author>
+ <firstname>Joey</firstname>
+ <surname>Hess</surname>
+ </author>
+ <date>2009-07-02</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>parallel</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>parallel</refname>
+ <refpurpose>run programs in parallel</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>parallel</command>
+ <arg>options</arg>
+ <arg>command</arg>
+ <command>--</command>
+ <arg>argument ...</arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>parallel</command>
+ <arg>options</arg>
+ <command>--</command>
+ <arg>command ...</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para><command>parallel</command> runs the specified command,
+ passing it a single one of the specified arguments. This is
+ repeated for each argument. Jobs may be run in
+ parallel. The default is to run one job per CPU.</para>
+
+ <para>If no command is specified before the --,
+ the commands after it are instead run in parallel.</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>OPTIONS</title>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><option>-j maxjobs</option></term>
+ <listitem>
+ <para>Use to limit the number of jobs
+ that are run at the same time.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-l maxload</option></term>
+ <listitem>
+ <para>Wait as needed to avoid starting
+ new jobs when the system's load average
+ is not below the specified limit.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-i</option></term>
+ <listitem>
+ <para>Normally the command is passed the
+ argument at the end of its command line. With
+ this option, any instances of "{}" in
+ the command are replaced with the argument.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-n</option></term>
+ <listitem>
+ <para>Number of arguments to pass to a
+ command at a time. Default is 1.
+ Incompatible with -i</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>EXAMPLE</title>
+
+ <para>
+ <cmdsynopsis>
+ <command>parallel sh -c "echo hi; sleep 2; echo bye" -- 1 2 3</command>
+ </cmdsynopsis>
+ </para>
+
+ <para>This runs three subshells that each print a message, delay,
+ and print another message. If your system has multiple
+ CPUs, parallel will run some of the jobs in parallel,
+ which should be clear from the order the messages are
+ output.
+ </para>
+
+ <para>
+ <cmdsynopsis>
+ <command>parallel -j 3 ufraw -o processed -- *.NEF</command>
+ </cmdsynopsis>
+ </para>
+
+ <para>This runs three ufraw processes at the same time until
+ all of the NEF files have been processed.
+ </para>
+
+ <para>
+ <cmdsynopsis>
+ <command>parallel -j 3 -- ls df "echo hi"</command>
+ </cmdsynopsis>
+ </para>
+
+ <para>This runs three independent commands in parallel.</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>EXIT STATUS</title>
+
+
+ <para>Its exit status is the combination of the exit statuses of each
+ command ran, ORed together. (Thus, if any one command
+ exits nonzero, <command>parallel</command> as a whole will exit nonzero.)</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ Tollef Fog Heen
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/pee.c b/pee.c
new file mode 100644
index 0000000..a8565c0
--- /dev/null
+++ b/pee.c
@@ -0,0 +1,61 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+/* Licensed under the GPL
+ * Copyright (c) Miek Gieben, 2006
+ */
+
+/* like tee(1), but then connect to other programs using
+ * pipes _and_ output to standard output
+ */
+
+int
+close_pipes(FILE **p, size_t i)
+{
+ int ret=EXIT_SUCCESS;
+ size_t j;
+ for (j = 0; j < i; j++) {
+ int r = pclose(p[j]);
+ if (WIFEXITED(r))
+ ret |= WEXITSTATUS(r);
+ else
+ ret |= 1;
+ }
+ return ret;
+}
+
+int
+main(int argc, char **argv) {
+ size_t i, r;
+ FILE **pipes;
+ char buf[BUFSIZ];
+
+ pipes = malloc(((argc - 1) * sizeof *pipes));
+ if (!pipes)
+ exit(EXIT_FAILURE);
+
+ for (i = 1; i < argc; i++) {
+ pipes[i - 1] = popen(argv[i], "w");
+ if (!pipes[i - 1]) {
+ fprintf(stderr, "Can not open pipe to '%s\'\n", argv[i]);
+ close_pipes(pipes, argc);
+
+ exit(EXIT_FAILURE);
+ }
+ }
+ argc--;
+
+ while(!feof(stdin) && (!ferror(stdin))) {
+ r = fread(buf, sizeof(char), BUFSIZ, stdin);
+ for(i = 0; i < argc; i++) {
+ if (fwrite(buf, sizeof(char), r, pipes[i]) != r) {
+ fprintf(stderr, "Write error to `%s\'\n", argv[i + 1]);
+ close_pipes(pipes, argc);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+ exit(close_pipes(pipes, argc));
+}
diff --git a/pee.docbook b/pee.docbook
new file mode 100644
index 0000000..f6a8441
--- /dev/null
+++ b/pee.docbook
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+
+Copyright 2006 Joey Hess <joey@kitenet.net>
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+Public License for more details.
+
+You 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
+
+-->
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"file:///usr/share/xml/docbook/schema/dtd/4.4/docbookx.dtd"
+[]>
+
+<refentry>
+ <refentryinfo>
+ <address>
+ <email>joey@kitenet.net</email>
+ </address>
+ <author>
+ <firstname>Joey</firstname>
+ <surname>Hess</surname>
+ </author>
+ <date>2006-03-14</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>pee</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>pee</refname>
+ <refpurpose>tee standard input to pipes</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>pee</command>
+ <group choice="opt">
+ <arg rep="repeat"><replaceable>"command"</replaceable></arg>
+ </group>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para><command>pee</command> is like <command>tee</command>
+ but for pipes. Each command is run and fed a copy of the
+ standard input. The output of all commands is sent to
+ stdout.</para>
+
+ <para>Note that while this is similar to
+ <command>tee</command>, a copy of the input is not sent
+ to stdout, like tee does. If that is desired, use
+ <command>pee cat ...</command></para>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+
+ <para>
+ <citerefentry>
+ <refentrytitle>tee</refentrytitle><manvolnum>1</manvolnum>
+ </citerefentry>
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ Miek Gieben
+ </para>
+ </refsect1>
+</refentry>
diff --git a/physmem.c b/physmem.c
new file mode 100644
index 0000000..a53a2cf
--- /dev/null
+++ b/physmem.c
@@ -0,0 +1,301 @@
+/* Calculate the size of physical memory.
+
+ Copyright (C) 2000, 2001, 2003, 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 Paul Eggert. */
+
+#include <unistd.h>
+
+#if HAVE_SYS_PSTAT_H
+# include <sys/pstat.h>
+#endif
+
+#if HAVE_SYS_SYSMP_H
+# include <sys/sysmp.h>
+#endif
+
+#if HAVE_SYS_SYSINFO_H && HAVE_MACHINE_HAL_SYSINFO_H
+# include <sys/sysinfo.h>
+# include <machine/hal_sysinfo.h>
+#endif
+
+#if HAVE_SYS_TABLE_H
+# include <sys/table.h>
+#endif
+
+#include <sys/types.h>
+
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+
+#if HAVE_SYS_SYSCTL_H
+# include <sys/sysctl.h>
+#endif
+
+#if HAVE_SYS_SYSTEMCFG_H
+# include <sys/systemcfg.h>
+#endif
+
+#ifdef _WIN32
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+/* MEMORYSTATUSEX is missing from older windows headers, so define
+ a local replacement. */
+typedef struct
+{
+ DWORD dwLength;
+ DWORD dwMemoryLoad;
+ DWORDLONG ullTotalPhys;
+ DWORDLONG ullAvailPhys;
+ DWORDLONG ullTotalPageFile;
+ DWORDLONG ullAvailPageFile;
+ DWORDLONG ullTotalVirtual;
+ DWORDLONG ullAvailVirtual;
+ DWORDLONG ullAvailExtendedVirtual;
+} lMEMORYSTATUSEX;
+typedef WINBOOL (WINAPI *PFN_MS_EX) (lMEMORYSTATUSEX*);
+#endif
+
+#define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0]))
+
+/* Return the total amount of physical memory. */
+double
+physmem_total (void)
+{
+#if defined _SC_PHYS_PAGES && defined _SC_PAGESIZE
+ { /* This works on linux-gnu, solaris2 and cygwin. */
+ double pages = sysconf (_SC_PHYS_PAGES);
+ double pagesize = sysconf (_SC_PAGESIZE);
+ if (0 <= pages && 0 <= pagesize)
+ return pages * pagesize;
+ }
+#endif
+
+#if HAVE_PSTAT_GETSTATIC
+ { /* This works on hpux11. */
+ struct pst_static pss;
+ if (0 <= pstat_getstatic (&pss, sizeof pss, 1, 0))
+ {
+ double pages = pss.physical_memory;
+ double pagesize = pss.page_size;
+ if (0 <= pages && 0 <= pagesize)
+ return pages * pagesize;
+ }
+ }
+#endif
+
+#if HAVE_SYSMP && defined MP_SAGET && defined MPSA_RMINFO && defined _SC_PAGESIZE
+ { /* This works on irix6. */
+ struct rminfo realmem;
+ if (sysmp (MP_SAGET, MPSA_RMINFO, &realmem, sizeof realmem) == 0)
+ {
+ double pagesize = sysconf (_SC_PAGESIZE);
+ double pages = realmem.physmem;
+ if (0 <= pages && 0 <= pagesize)
+ return pages * pagesize;
+ }
+ }
+#endif
+
+#if HAVE_GETSYSINFO && defined GSI_PHYSMEM
+ { /* This works on Tru64 UNIX V4/5. */
+ int physmem;
+
+ if (getsysinfo (GSI_PHYSMEM, (caddr_t) &physmem, sizeof (physmem),
+ NULL, NULL, NULL) == 1)
+ {
+ double kbytes = physmem;
+
+ if (0 <= kbytes)
+ return kbytes * 1024.0;
+ }
+ }
+#endif
+
+#if HAVE_SYSCTL && defined HW_PHYSMEM
+ { /* This works on *bsd and darwin. */
+ unsigned int physmem;
+ size_t len = sizeof physmem;
+ static int mib[2] = { CTL_HW, HW_PHYSMEM };
+
+ if (sysctl (mib, ARRAY_SIZE (mib), &physmem, &len, NULL, 0) == 0
+ && len == sizeof (physmem))
+ return (double) physmem;
+ }
+#endif
+
+#if HAVE__SYSTEM_CONFIGURATION
+ /* This works on AIX. */
+ return _system_configuration.physmem;
+#endif
+
+#if defined _WIN32
+ { /* this works on windows */
+ PFN_MS_EX pfnex;
+ HMODULE h = GetModuleHandle ("kernel32.dll");
+
+ if (!h)
+ return 0.0;
+
+ /* Use GlobalMemoryStatusEx if available. */
+ if ((pfnex = (PFN_MS_EX) GetProcAddress (h, "GlobalMemoryStatusEx")))
+ {
+ lMEMORYSTATUSEX lms_ex;
+ lms_ex.dwLength = sizeof lms_ex;
+ if (!pfnex (&lms_ex))
+ return 0.0;
+ return (double) lms_ex.ullTotalPhys;
+ }
+
+ /* Fall back to GlobalMemoryStatus which is always available.
+ but returns wrong results for physical memory > 4GB. */
+ else
+ {
+ MEMORYSTATUS ms;
+ GlobalMemoryStatus (&ms);
+ return (double) ms.dwTotalPhys;
+ }
+ }
+#endif
+
+ /* Guess 64 MB. It's probably an older host, so guess small. */
+ return 64 * 1024 * 1024;
+}
+
+/* Return the amount of physical memory available. */
+double
+physmem_available (void)
+{
+#if defined _SC_AVPHYS_PAGES && defined _SC_PAGESIZE
+ { /* This works on linux-gnu, solaris2 and cygwin. */
+ double pages = sysconf (_SC_AVPHYS_PAGES);
+ double pagesize = sysconf (_SC_PAGESIZE);
+ if (0 <= pages && 0 <= pagesize)
+ return pages * pagesize;
+ }
+#endif
+
+#if HAVE_PSTAT_GETSTATIC && HAVE_PSTAT_GETDYNAMIC
+ { /* This works on hpux11. */
+ struct pst_static pss;
+ struct pst_dynamic psd;
+ if (0 <= pstat_getstatic (&pss, sizeof pss, 1, 0)
+ && 0 <= pstat_getdynamic (&psd, sizeof psd, 1, 0))
+ {
+ double pages = psd.psd_free;
+ double pagesize = pss.page_size;
+ if (0 <= pages && 0 <= pagesize)
+ return pages * pagesize;
+ }
+ }
+#endif
+
+#if HAVE_SYSMP && defined MP_SAGET && defined MPSA_RMINFO && defined _SC_PAGESIZE
+ { /* This works on irix6. */
+ struct rminfo realmem;
+ if (sysmp (MP_SAGET, MPSA_RMINFO, &realmem, sizeof realmem) == 0)
+ {
+ double pagesize = sysconf (_SC_PAGESIZE);
+ double pages = realmem.availrmem;
+ if (0 <= pages && 0 <= pagesize)
+ return pages * pagesize;
+ }
+ }
+#endif
+
+#if HAVE_TABLE && defined TBL_VMSTATS
+ { /* This works on Tru64 UNIX V4/5. */
+ struct tbl_vmstats vmstats;
+
+ if (table (TBL_VMSTATS, 0, &vmstats, 1, sizeof (vmstats)) == 1)
+ {
+ double pages = vmstats.free_count;
+ double pagesize = vmstats.pagesize;
+
+ if (0 <= pages && 0 <= pagesize)
+ return pages * pagesize;
+ }
+ }
+#endif
+
+#if HAVE_SYSCTL && defined HW_USERMEM
+ { /* This works on *bsd and darwin. */
+ unsigned int usermem;
+ size_t len = sizeof usermem;
+ static int mib[2] = { CTL_HW, HW_USERMEM };
+
+ if (sysctl (mib, ARRAY_SIZE (mib), &usermem, &len, NULL, 0) == 0
+ && len == sizeof (usermem))
+ return (double) usermem;
+ }
+#endif
+
+#if defined _WIN32
+ { /* this works on windows */
+ PFN_MS_EX pfnex;
+ HMODULE h = GetModuleHandle ("kernel32.dll");
+
+ if (!h)
+ return 0.0;
+
+ /* Use GlobalMemoryStatusEx if available. */
+ if ((pfnex = (PFN_MS_EX) GetProcAddress (h, "GlobalMemoryStatusEx")))
+ {
+ lMEMORYSTATUSEX lms_ex;
+ lms_ex.dwLength = sizeof lms_ex;
+ if (!pfnex (&lms_ex))
+ return 0.0;
+ return (double) lms_ex.ullAvailPhys;
+ }
+
+ /* Fall back to GlobalMemoryStatus which is always available.
+ but returns wrong results for physical memory > 4GB */
+ else
+ {
+ MEMORYSTATUS ms;
+ GlobalMemoryStatus (&ms);
+ return (double) ms.dwAvailPhys;
+ }
+ }
+#endif
+
+ /* Guess 25% of physical memory. */
+ return physmem_total () / 4;
+}
+
+
+#if DEBUG
+
+# include <stdio.h>
+# include <stdlib.h>
+
+int
+main (void)
+{
+ printf ("%12.f %12.f\n", physmem_total (), physmem_available ());
+ exit (0);
+}
+
+#endif /* DEBUG */
+
+/*
+Local Variables:
+compile-command: "gcc -DDEBUG -g -O -Wall -W physmem.c"
+End:
+*/
diff --git a/sponge.c b/sponge.c
new file mode 100644
index 0000000..969703f
--- /dev/null
+++ b/sponge.c
@@ -0,0 +1,373 @@
+/*
+ * sponge.c - read in all available info from stdin, then output it to
+ * file named on the command line
+ *
+ * Copyright © 2006 Tollef Fog Heen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You 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
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+/* MAX() */
+#include <sys/param.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/resource.h>
+/* SIZE_MAX */
+#include <stdint.h>
+#include <signal.h>
+
+#include "physmem.c"
+
+#define BUFF_SIZE 8192
+#define MIN_SPONGE_SIZE BUFF_SIZE
+char *tmpname = NULL;
+
+void usage() {
+ printf("sponge <file>: soak up all input from stdin and write it to <file>\n");
+ exit(0);
+}
+
+/* all the signal stuff copied from gnu sort */
+
+/* The set of signals that are caught. */
+static sigset_t caught_signals;
+
+/* Critical section status. */
+struct cs_status {
+ int valid; // was bool
+ 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);
+ }
+}
+
+static void cleanup () {
+ if (tmpname) {
+ unlink(tmpname);
+ }
+}
+
+static void onexit_cleanup (void) {
+ struct cs_status cs = cs_enter();
+ cleanup();
+ cs_leave(cs);
+}
+
+static void sighandler (int sig) {
+ if (! SA_NOCLDSTOP)
+ signal(sig, SIG_IGN);
+
+ cleanup();
+
+ signal(sig, SIG_DFL);
+ raise(sig);
+}
+
+/* taken from coreutils sort */
+static size_t default_sponge_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_SPONGE_SIZE);
+}
+
+void trapsignals (void) {
+ ssize_t i = 0;
+ 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
+ };
+ int 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
+}
+
+static void write_buff_tmp(char* buff, size_t length, FILE *fd) {
+ if (fwrite(buff, length, 1, fd) < 1) {
+ perror("error writing buffer to temporary file");
+ fclose(fd);
+ exit(1);
+ }
+}
+
+static void write_buff_tmp_finish (char* buff, size_t length, FILE *fd) {
+ if (length)
+ write_buff_tmp(buff, length, fd);
+ if (fflush(fd) != 0) {
+ perror("fflush");
+ exit(1);
+ }
+}
+
+static void write_buff_out (char* buff, size_t length, FILE *fd) {
+ if (fwrite(buff, length, 1, fd) < 1) {
+ perror("error writing buffer to output file");
+ fclose(fd);
+ exit(1);
+ }
+}
+
+static void copy_tmpfile (FILE *tmpfile, FILE *outfile, char *buf, size_t size) {
+ ssize_t i;
+ if (lseek(fileno(tmpfile), 0, SEEK_SET)) {
+ perror("could to seek to start of temporary file");
+ fclose(tmpfile);
+ exit(1);
+ }
+ while ((i = read(fileno(tmpfile), buf, size)) > 0) {
+ write_buff_out(buf, i, outfile);
+ }
+ if (i == -1) {
+ perror("read temporary file");
+ fclose(tmpfile);
+ exit(1);
+ }
+ if (fclose(tmpfile) != 0) {
+ perror("read temporary file");
+ exit(1);
+ }
+ if (fclose(outfile) != 0) {
+ perror("error writing buffer to output file");
+ exit(1);
+ }
+}
+
+FILE *open_tmpfile (void) {
+ struct cs_status cs;
+ int tmpfd;
+ FILE *tmpfile;
+ mode_t mask;
+ char *tmpdir;
+ char const * const template="%s/sponge.XXXXXX";
+
+ trapsignals();
+ cs = cs_enter();
+ tmpdir = getenv("TMPDIR");
+ if (tmpdir == NULL)
+ tmpdir = "/tmp";
+ /* Subtract 2 for `%s' and add 1 for the trailing NULL. */
+ tmpname=malloc(strlen(tmpdir) + strlen(template) - 2 + 1);
+ if (! tmpname) {
+ perror("failed to allocate memory");
+ exit(1);
+ }
+ sprintf(tmpname, template, tmpdir);
+ mask=umask(077);
+ tmpfd = mkstemp(tmpname);
+ umask(mask);
+ atexit(onexit_cleanup); // solaris on_exit(onexit_cleanup, 0);
+ cs_leave(cs);
+
+ if (tmpfd < 0) {
+ perror("mkstemp failed");
+ exit(1);
+ }
+ tmpfile = fdopen(tmpfd, "w+");
+ if (! tmpfile) {
+ perror("fdopen");
+ exit(1);
+ }
+ return tmpfile;
+}
+
+int main (int argc, char **argv) {
+ char *buf, *bufstart, *outname = NULL;
+ size_t bufsize = BUFF_SIZE;
+ size_t bufused = 0;
+ FILE *outfile, *tmpfile = 0;
+ ssize_t i = 0;
+ size_t mem_available = default_sponge_size();
+ int tmpfile_used=0;
+
+ if (argc > 2 || (argc == 2 && strcmp(argv[1], "-h") == 0)) {
+ usage();
+ }
+ if (argc == 2) {
+ outname = argv[1];
+ }
+
+ tmpfile = open_tmpfile();
+ bufstart = buf = malloc(bufsize);
+ if (!buf) {
+ perror("failed to allocate memory");
+ exit(1);
+ }
+ while ((i = read(0, buf, bufsize - bufused)) > 0) {
+ bufused = bufused+i;
+ if (bufused == bufsize) {
+ if ((bufsize*2) >= mem_available) {
+ write_buff_tmp(bufstart, bufused, tmpfile);
+ bufused = 0;
+ tmpfile_used = 1;
+ }
+ else {
+ bufsize *= 2;
+ bufstart = realloc(bufstart, bufsize);
+ if (!bufstart) {
+ perror("failed to realloc memory");
+ exit(1);
+ }
+ }
+ }
+ buf = bufstart + bufused;
+ }
+ if (i < 0) {
+ perror("failed to read from stdin");
+ exit(1);
+ }
+
+ if (outname) {
+ mode_t mode;
+ struct stat statbuf;
+ int exists = (lstat(outname, &statbuf) == 0);
+
+ write_buff_tmp_finish(bufstart, bufused, tmpfile);
+
+ /* Set temp file mode to match either
+ * the old file mode, or the default file
+ * mode for a newly created file. */
+ if (exists) {
+ mode = statbuf.st_mode;
+ }
+ else {
+ mode_t mask = umask(0);
+ umask(mask);
+ mode = 0666 & ~mask;
+ }
+ if (chmod(tmpname, mode) != 0) {
+ perror("chmod");
+ exit(1);
+ }
+
+ /* If it's a regular file, or does not yet exist,
+ * attempt a fast rename of the temp file. */
+ if (((exists &&
+ S_ISREG(statbuf.st_mode) &&
+ ! S_ISLNK(statbuf.st_mode)
+ ) || ! exists) &&
+ rename(tmpname, outname) == 0) {
+ tmpname=NULL; /* don't try to cleanup tmpname */
+ }
+ else {
+ /* Fall back to slow copy. */
+ outfile = fopen(outname, "w");
+ if (!outfile) {
+ perror("error opening output file");
+ exit(1);
+ }
+ copy_tmpfile(tmpfile, outfile, bufstart, bufsize);
+ }
+ }
+ else {
+ if (tmpfile_used) {
+ write_buff_tmp_finish(bufstart, bufused, tmpfile);
+ copy_tmpfile(tmpfile, stdout, bufstart, bufsize);
+ }
+ else if (bufused) {
+ /* buffer direct to stdout, no tmpfile */
+ write_buff_out(bufstart, bufused, stdout);
+ }
+ }
+
+ return 0;
+}
diff --git a/sponge.docbook b/sponge.docbook
new file mode 100644
index 0000000..daab8eb
--- /dev/null
+++ b/sponge.docbook
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+
+Copyright © 2006 Joey Hess
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+version 2 as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You 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
+
+-->
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"file:///usr/share/xml/docbook/schema/dtd/4.4/docbookx.dtd"
+[]>
+
+<refentry>
+
+ <refentryinfo>
+ <author>
+ <firstname>Joey</firstname>
+ <surname>Hess</surname>
+ </author>
+ <date>2006-02-19</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>sponge</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>sponge</refname>
+ <refpurpose>soak up standard input and write to a file</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>sed '...' file | grep '...' | sponge file</command>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+
+ <para><command>sponge</command> reads standard input and
+ writes it out to the specified file. Unlike a shell
+ redirect, sponge soaks up all its input before
+ opening the output file. This allows constructing
+ pipelines that read from and write to
+ the same file.</para>
+ <para>
+ It also creates the output file
+ atomically by renaming a temp file into place,
+ and preserves the permissions of the output file
+ if it already exists.
+ If the output file is a special file or symlink,
+ the data will be written to it.</para>
+ <para>If no output file is specified, sponge outputs to
+ stdout.</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>AUTHOR</title>
+ <para>
+ Colin Watson and Tollef Fog Heen
+ </para>
+ </refsect1>
+</refentry>
diff --git a/ts b/ts
new file mode 100755
index 0000000..2a04392
--- /dev/null
+++ b/ts
@@ -0,0 +1,154 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+ts - timestamp input
+
+=head1 SYNOPSIS
+
+ts [-r] [-i] [format]
+
+=head1 DESCRIPTION
+
+ts adds a timestamp to the beginning of each line of input.
+
+The optional format parameter controls how the timestamp is formatted,
+as used by L<strftime(3)>. The default format is "%b %d %H:%M:%S". In
+addition to the regular strftime conversion specifications, "%.S" and "%.s"
+are like "%S" and "%s", but provide subsecond resolution
+(ie, "30.00001" and "1301682593.00001").
+
+If the -r switch is passed, it instead converts existing timestamps in
+the input to relative times, such as "15m5s ago". Many common timestamp
+formats are supported. Note that the Time::Duration and Date::Parse perl
+modules are required for this mode to work. Currently, converting localized
+dates is not supported.
+
+If both -r and a format is passed, the existing timestamps are
+converted to the specified format.
+
+If the -i switch is passed, ts timestamps incrementally instead. Every
+timestamp will be the time elapsed since the last timestamp.
+The default format changes to "%H:%M:%S", and "%.S" and "%.s" can be used
+as well.
+
+=head1 ENVIRONMENT
+
+The standard TZ environment variable controls what time zone dates
+are assumed to be in, if a timezone is not specified as part of the date.
+
+=head1 AUTHOR
+
+Copyright 2006 by Joey Hess <joey@kitenet.net>
+
+Licensed under the GNU GPL.
+
+=cut
+
+use warnings;
+use strict;
+use POSIX q{strftime};
+
+$|=1;
+
+my $rel=0;
+my $inc=0;
+use Getopt::Long;
+GetOptions("r" => \$rel, "i" => \$inc) || die "usage: ts [-r] [-i] [format]\n";
+
+if ($rel) {
+ eval q{
+ use Date::Parse;
+ use Time::Duration;
+ };
+ die $@ if $@;
+}
+
+my $use_format=@ARGV;
+my $format="%b %d %H:%M:%S";
+if ($inc) {
+ $format="%H:%M:%S";
+ $ENV{TZ}='GMT';
+}
+$format=shift if @ARGV;
+
+# For subsecond resolution, Time::HiRes is needed.
+my $hires=0;
+if ($format=~/\%\.[Ss]/) {
+ require Time::HiRes;
+ $hires=1;
+}
+
+my $lastseconds = 0;
+my $lastmicroseconds = 0;
+
+if ($hires) {
+ ($lastseconds, $lastmicroseconds) = Time::HiRes::gettimeofday();
+} else {
+ $lastseconds = time;
+}
+
+
+while (<>) {
+ if (! $rel) {
+ if ($hires) {
+ my $f=$format;
+ my ($seconds, $microseconds) = Time::HiRes::gettimeofday();
+ if ($inc) {
+ my $deltaseconds = $seconds - $lastseconds;
+ my $deltamicroseconds = $microseconds - $lastmicroseconds;
+ if ($deltamicroseconds < 0) {
+ $deltaseconds -= 1;
+ $deltamicroseconds += 1000000;
+ }
+ $lastseconds = $seconds;
+ $lastmicroseconds = $microseconds;
+ $seconds = $deltaseconds;
+ $microseconds = $deltamicroseconds;
+ }
+ my $s=sprintf("%06i", $microseconds);
+ $f=~s/\%\.([Ss])/%$1.$s/g;
+ print strftime($f, localtime($seconds));
+ }
+ else {
+ if ($inc) {
+ my $seconds = time;
+ my $deltaseconds = $seconds - $lastseconds;
+ $lastseconds = $seconds;
+ print strftime($format, localtime($deltaseconds));
+ } else {
+ print strftime($format, localtime);
+ }
+ }
+ print " ".$_;
+ }
+ else {
+ s{\b(
+ \d\d[-\s\/]\w\w\w # 21 dec 17:05
+ (?:\/\d\d+)? # 21 dec/93 17:05
+ [\s:]\d\d:\d\d # (time part of above)
+ (?::\d\d)? # (optional seconds)
+ (?:\s+[+-]\d\d\d\d)? # (optional timezone)
+ |
+ \w{3}\s+\d{1,2}\s+\d\d:\d\d:\d\d # syslog form
+ |
+ \d\d\d[-:]\d\d[-:]\d\dT\d\d:\d\d:\d\d.\d+ # ISO-8601
+ |
+ (?:\w\w\w,?\s+)? # (optional Day)
+ \d+\s+\w\w\w\s+\d\d+\s+\d\d:\d\d:\d\d
+ # 16 Jun 94 07:29:35
+ (?:\s+\w\w\w|\s[+-]\d\d\d\d)?
+ # (optional timezone)
+ |
+ \w\w\w\s+\w\w\w\s+\d\d\s+\d\d:\d\d
+ # lastlog format
+ )\b
+ }{
+ $use_format
+ ? strftime($format, localtime(str2time($1)))
+ : concise(ago(time - str2time($1), 2))
+ }exg;
+
+ print $_;
+ }
+}
diff --git a/vidir b/vidir
new file mode 100755
index 0000000..a77739f
--- /dev/null
+++ b/vidir
@@ -0,0 +1,232 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+vidir - edit directory
+
+=head1 SYNOPSIS
+
+B<vidir> [--verbose] [directory|file|-] ...
+
+=head1 DESCRIPTION
+
+vidir allows editing of the contents of a directory in a text editor. If no
+directory is specified, the current directory is edited.
+
+When editing a directory, each item in the directory will appear on its own
+numbered line. These numbers are how vidir keeps track of what items are
+changed. Delete lines to remove files from the directory, or
+edit filenames to rename files. You can also switch pairs of numbers to
+swap filenames.
+
+Note that if "-" is specified as the directory to edit, it reads a list of
+filenames from stdin and displays those for editing. Alternatively, a list
+of files can be specified on the command line.
+
+=head1 OPTIONS
+
+=over 4
+
+=item -v, --verbose
+
+Verbosely display the actions taken by the program.
+
+=back
+
+=head1 EXAMPLES
+
+=over 4
+
+=item vidir
+
+=item vidir *.jpeg
+
+Typical uses.
+
+=item find | vidir -
+
+Edit subdirectory contents too. To delete subdirectories,
+delete all their contents and the subdirectory itself in the editor.
+
+=item find -type f | vidir -
+
+Edit all files under the current directory and subdirectories.
+
+=back
+
+=head1 ENVIRONMENT VARIABLES
+
+=over 4
+
+=item EDITOR
+
+Editor to use.
+
+=item VISUAL
+
+Also supported to determine what editor to use.
+
+=back
+
+=head1 AUTHOR
+
+Copyright 2006 by Joey Hess <joey@kitenet.net>
+
+Licensed under the GNU GPL.
+
+=cut
+
+use File::Spec;
+use File::Temp;
+use Getopt::Long;
+
+my $error=0;
+
+my $verbose=0;
+if (! GetOptions("verbose|v" => \$verbose)) {
+ die "Usage: $0 [--verbose] [directory|file|-]\n";
+}
+
+my @dir;
+if (! @ARGV) {
+ push @ARGV, "."
+}
+foreach my $item (@ARGV) {
+ if ($item eq "-") {
+ push @dir, map { chomp; $_ } <STDIN>;
+ close STDIN;
+ open(STDIN, "/dev/tty") || die "reopen: $!\n";
+ }
+ elsif (-d $item) {
+ $item =~ s{/?$}{/};
+ opendir(DIR, $item) || die "$0: cannot read $item: $!\n";
+ push @dir, map { "$item$_" } sort readdir(DIR);
+ closedir DIR;
+ }
+ else {
+ push @dir, $item;
+ }
+}
+
+if (grep(/[[:cntrl:]]/, @dir)) {
+ die "$0: control characters in filenames are not supported\n";
+}
+
+my $tmp=File::Temp->new(TEMPLATE => "dirXXXXX", DIR => File::Spec->tmpdir);
+open (OUT, ">".$tmp->filename) || die "$0: cannot create ".$tmp->filename.": $!\n";
+
+my %item;
+my $c=0;
+foreach (@dir) {
+ next if /^(.*\/)?\.$/ || /^(.*\/)?\.\.$/;
+ $item{++$c}=$_;
+ print OUT "$c\t$_\n";
+}
+@dir=();
+close OUT || die "$0: cannot write ".$tmp->filename.": $!\n";
+
+my @editor="vi";
+if (-x "/usr/bin/editor") {
+ @editor="/usr/bin/editor";
+}
+if (exists $ENV{EDITOR}) {
+ @editor=split(' ', $ENV{EDITOR});
+}
+if (exists $ENV{VISUAL}) {
+ @editor=split(' ', $ENV{VISUAL});
+}
+$ret=system(@editor, $tmp);
+if ($ret != 0) {
+ die "@editor exited nonzero, aborting\n";
+}
+
+open (IN, $tmp->filename) || die "$0: cannot read ".$tmp->filename.": $!\n";
+while (<IN>) {
+ chomp;
+ if (/^(\d+)\t{0,1}(.*)/) {
+ my $num=int($1);
+ my $name=$2;
+ if (! exists $item{$num}) {
+ die "$0: unknown item number $num\n";
+ }
+ elsif ($name ne $item{$num}) {
+ next unless length $name;
+ my $src=$item{$num};
+
+ if (! (-e $src || -l $src) ) {
+ print STDERR "$0: $src does not exist\n";
+ delete $item{$num};
+ next;
+ }
+
+ # deal with swaps
+ if (-e $name || -l $name) {
+ my $tmp=$name."~";
+ my $c=0;
+ while (-e $tmp || -l $tmp) {
+ $c++;
+ $tmp=$name."~$c";
+ }
+ if (! rename($name, $tmp)) {
+ print STDERR "$0: failed to rename $name to $tmp: $!\n";
+ $error=1;
+ }
+ elsif ($verbose) {
+ print "'$name' -> '$tmp'\n";
+ }
+ foreach my $item (keys %item) {
+ if ($item{$item} eq $name) {
+ $item{$item}=$tmp;
+ }
+ }
+ }
+
+ if (! rename($src, $name)) {
+ print STDERR "$0: failed to rename $src to $name: $!\n";
+ $error=1;
+ }
+ else {
+ if (-d $name) {
+ foreach (values %item) {
+ s/^\Q$src\E/$name/;
+ }
+ }
+ if ($verbose) {
+ print "'$src' => '$name'\n";
+ }
+ }
+ }
+ delete $item{$num};
+ }
+ elsif (/^\s*$/) {
+ # skip empty line
+ }
+ else {
+ die "$0: unable to parse line \"$_\", aborting\n";
+ }
+}
+close IN || die "$0: cannot read ".$tmp->filename.": $!\n";
+unlink($tmp.'~') if -e $tmp.'~';
+
+sub rm {
+ my $file = shift;
+
+ if (-d $file && ! -l $file) {
+ return rmdir $file;
+ }
+ else {
+ return unlink $file;
+ }
+}
+
+foreach my $item (reverse sort values %item) {
+ if (! rm($item)) {
+ print STDERR "$0: failed to remove $item: $!\n";
+ $error=1;
+ }
+ if ($verbose) {
+ print "removed '$item'\n";
+ }
+}
+
+exit $error;
diff --git a/vipe b/vipe
new file mode 100755
index 0000000..fd61049
--- /dev/null
+++ b/vipe
@@ -0,0 +1,74 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+vipe - edit pipe
+
+=head1 SYNOPSIS
+
+command1 | vipe | command2
+
+=head1 DESCRIPTION
+
+vipe allows you to run your editor in the middle of a unix pipeline and
+edit the data that is being piped between programs. Your editor will
+have the full data being piped from command1 loaded into it, and when you
+save, that data will be piped into command2.
+
+=head1 ENVIRONMENT VARIABLES
+
+=over 4
+
+=item EDITOR
+
+Editor to use.
+
+=item VISUAL
+
+Also supported to determine what editor to use.
+
+=back
+
+=head1 AUTHOR
+
+Copyright 2006 by Joey Hess <joey@kitenet.net>
+
+Licensed under the GNU GPL.
+
+=cut
+
+use warnings;
+use strict;
+use File::Temp q{tempfile};
+
+$/=undef;
+
+my ($fh, $tmp)=tempfile();
+die "cannot create tempfile" unless $fh;
+print ($fh <STDIN>) || die "write temp: $!";
+close $fh;
+close STDIN;
+open(STDIN, "</dev/tty") || die "reopen stdin: $!";
+open(OUT, ">&STDOUT") || die "save stdout: $!";
+close STDOUT;
+open(STDOUT, ">/dev/tty") || die "reopen stdout: $!";
+
+my @editor="vi";
+if (-x "/usr/bin/editor") {
+ @editor="/usr/bin/editor";
+}
+if (exists $ENV{EDITOR}) {
+ @editor=split(' ', $ENV{EDITOR});
+}
+if (exists $ENV{VISUAL}) {
+ @editor=split(' ', $ENV{VISUAL});
+}
+my $ret=system(@editor, $tmp);
+if ($ret != 0) {
+ die "@editor exited nonzero, aborting\n";
+}
+
+open (IN, $tmp) || die "$0: cannot read $tmp: $!\n";
+print (OUT <IN>) || die "write failure: $!";
+close IN;
+unlink($tmp);
diff --git a/zrun b/zrun
new file mode 100755
index 0000000..98d1445
--- /dev/null
+++ b/zrun
@@ -0,0 +1,105 @@
+#!/usr/bin/perl
+
+=head1 NAME
+
+zrun - automatically uncompress arguments to command
+
+=head1 SYNOPSIS
+
+zrun command file.gz [...]
+
+=head1 DESCRIPTION
+
+Prefixing a shell command with "zrun" causes any compressed files that are
+arguments of the command to be transparently uncompressed to temp files
+(not pipes) and the uncompressed files fed to the command.
+
+This is a quick way to run a command that does not itself support
+compressed files, without manually uncompressing the files.
+
+The following compression types are supported: gz bz2 Z xz lzma lzo
+
+If zrun is linked to some name beginning with z, like zprog, and the link is
+executed, this is equivalent to executing "zrun prog".
+
+=head1 BUGS
+
+Modifications to the uncompressed temporary file are not fed back into the
+input file, so using this as a quick way to make an editor support
+compressed files won't work.
+
+=head1 AUTHOR
+
+Copyright 2006 by Chung-chieh Shan <ccshan@post.harvard.edu>
+
+=cut
+
+use warnings;
+use strict;
+use IO::Handle;
+use File::Spec;
+use File::Temp qw{tempfile};
+
+my $program;
+
+if ($0 =~ m{(?:^|/)z([^/]+)$} && $1 ne 'run') {
+ $program = $1;
+ if (! @ARGV) {
+ die "Usage: z$1 <args>\nEquivalent to: zrun $1 <args>\n";
+ }
+}
+else {
+ $program = shift;
+ if (! @ARGV) {
+ die "Usage: zrun <command> <args>\n";
+ }
+}
+
+my @argument;
+my %child;
+foreach my $argument (@ARGV) {
+ if ($argument =~ m{^(.*/)?([^/]*)\.(gz|Z|bz2|xz|lzo|lzma)$}s) {
+ my $suffix = "-$2";
+ my @preprocess = $3 eq "bz2" ? qw(bzip2 -d -c) :
+ $3 eq "xz" ? qw(xz -d -c) :
+ $3 eq "lzo" ? qw(lzop -d -c) :
+ $3 eq "lzma" ? qw(lzma -d -c) : qw(gzip -d -c);
+
+ my ($fh, $tmpname) = tempfile(SUFFIX => $suffix,
+ DIR => File::Spec->tmpdir, UNLINK => 1)
+ or die "zrun: cannot create temporary file: $!\n";
+
+ if (my $child = fork) {
+ $child{$child} = $argument;
+ $argument = $tmpname;
+ }
+ elsif (defined $child) {
+ STDOUT->fdopen($fh, "w");
+ exec @preprocess, $argument;
+ }
+ else {
+ die "zrun: cannot fork to handle $argument: $!\n";
+ }
+ }
+ push @argument, $argument;
+}
+
+while (%child and (my $pid = wait) != -1) {
+ if (defined(my $argument = delete $child{$pid})) {
+ if ($? & 255) {
+ die "zrun: preprocessing for $argument terminated abnormally: $?\n";
+ }
+ elsif (my $code = $? >> 8) {
+ die "zrun: preprocessing for $argument terminated with code $code\n";
+ }
+ }
+}
+
+my $status = system $program ($program, @argument);
+if ($status & 255) {
+ die "zrun: $program terminated abnormally: $?\n";
+}
+else {
+ my $code = $? >> 8;
+ exit $code;
+}