diff --git a/src/split.c b/src/split.c index 3a630a0..8dd1571 100644 --- a/src/split.c +++ b/src/split.c @@ -13,7 +13,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ - + /* By address@hidden, with rms. To do: @@ -26,6 +26,7 @@ #include #include #include +#include #include "system.h" #include "error.h" @@ -84,6 +85,10 @@ enum Split_type type_undef, type_bytes, type_byteslines, type_lines, type_digits, type_chunk_bytes, type_chunk_lines, type_rr }; + +/* gzip/bzip2 the output file. */ +static char *outhook = NULL; +static char *outhooksuf = NULL; /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ @@ -106,6 +111,8 @@ static struct option const longopts[] = {"verbose", no_argument, NULL, VERBOSE_OPTION}, {"-io-blksize", required_argument, NULL, IO_BLKSIZE_OPTION}, /* do not document */ + {"on-output-hook", required_argument, NULL, 'k'}, + {"on-output-hook-suffix", required_argument, NULL, 's'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} @@ -173,6 +180,8 @@ Mandatory arguments to long options are mandatory for short options too.\n\ -l, --lines=NUMBER put NUMBER lines per output file\n\ -n, --number=CHUNKS generate CHUNKS output files. See below\n\ -u, --unbuffered immediately copy input to output with `-n r/...'\n\ + -k, --on-output-hook custom output compression hook command \n\ + -s, --on-output-hook-suffix custom compresion output extension\n\ "), DEFAULT_SUFFIX_LENGTH); fputs (_("\ --verbose print a diagnostic just before each\n\ @@ -262,6 +271,93 @@ create (const char* name) (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); } +/* Opens a new fd based on the file type selection */ + +static int +fdpopen ( const char *command, const char *mode) +{ + int parent_end, child_end; + int pipe_fds[2]; + pid_t child_pid; + + int do_write = 0; + int do_cloexec = 0; + + while (*mode != '\0') + { + switch (*mode++) + { + case 'w': + do_write = 1; + break; + case 'e': + do_cloexec = 1; + break; + default: + errno = EINVAL; + return -1; + } + } + + if (pipe (pipe_fds) < 0) + return -1; + + parent_end = pipe_fds[1]; + child_end = pipe_fds[0]; + child_pid = fork (); + + if (child_pid == 0) + { + int child_std_end = 0; /* Make this as the stdin file descriptor */ + close (parent_end); + + if (child_end != child_std_end) + { + dup2 (child_end, child_std_end); + close (child_end); + } + + /* TODO: get rid of this sh, pipe to the output in C */ + execl ("/bin/sh", "sh", "-c", command, (char *) 0); + _exit (127); + } + + close (child_end); + if (child_pid < 0) + { + close (parent_end); + return -1; + } + + if (do_cloexec) + fcntl (parent_end, F_SETFD, FD_CLOEXEC); + + return parent_end; +} + +static void +new_fd_pipe () +{ + /* 'gzip < /1/2/3/4/5/outputfile.gz' */ + + size_t outzlength = strlen (outhook); + size_t outlength = strlen (outfile); + size_t suflength = strlen (outhooksuf); + size_t tlength = outzlength + outlength + suflength + 3; + + char* outfilez = xmalloc (tlength + 1); + + snprintf (outfilez, tlength + 1, "%s > %s%s", outhook, outfile, outhooksuf); + + if (verbose) + fprintf (stdout, _("opening command %s\n"), quote (outfilez)); + + output_desc = fdpopen ( outfilez, "we"); + + if (output_desc < 0) + error (EXIT_FAILURE, errno, "%s", outfilez); +} + /* Write BYTES bytes at BP to an output file. If NEW_FILE_FLAG is true, open the next output file. Otherwise add to the same output file already in use. */ @@ -275,10 +371,22 @@ cwrite (bool new_file_flag, const char *bp, size_t bytes) return; if (output_desc >= 0 && close (output_desc) < 0) error (EXIT_FAILURE, errno, "%s", outfile); + next_file_name (); - if ((output_desc = create (outfile)) < 0) - error (EXIT_FAILURE, errno, "%s", outfile); + + if (outhook) + { + int status; + waitpid (-1, &status, 0); /* FIXME: check status */ + new_fd_pipe (); + } + else + { + if ((output_desc = create (outfile)) < 0) + error (EXIT_FAILURE, errno, "%s", outfile); + } } + if (full_write (output_desc, bp, bytes) != bytes) error (EXIT_FAILURE, errno, "%s", outfile); } @@ -824,7 +932,7 @@ main (int argc, char **argv) int this_optind = optind ? optind : 1; char *slash; - c = getopt_long (argc, argv, "0123456789C:a:b:del:n:u", longopts, NULL); + c = getopt_long (argc, argv, "0123456789C:a:b:dek:l:n:s:u", longopts, NULL); if (c == -1) break; @@ -965,6 +1073,13 @@ main (int argc, char **argv) else in_blk_size = tmp_blk_size; } + + case 'k': + outhook = optarg; + break; + + case 's': + outhooksuf = optarg; break; case VERBOSE_OPTION: @@ -980,6 +1095,12 @@ main (int argc, char **argv) } } + if (outhook && !outhooksuf) + { + error (0, 0, _("need a suffix for the split output files when used with on-output-hook")); + usage (EXIT_FAILURE); + } + /* Handle default case. */ if (split_type == type_undef) { @@ -1088,6 +1209,9 @@ main (int argc, char **argv) error (EXIT_FAILURE, errno, "%s", infile); if (output_desc >= 0 && close (output_desc) < 0) error (EXIT_FAILURE, errno, "%s", outfile); + if (outhook) + waitpid (-1, NULL, 0); /* FIXME: check status */ + exit (EXIT_SUCCESS); } diff --git a/tests/Makefile.am b/tests/Makefile.am index 2f4a561..6135dd2 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -250,6 +250,7 @@ TESTS = \ misc/split-a \ misc/split-bchunk \ misc/split-fail \ + misc/split-k \ misc/split-l \ misc/split-lchunk \ misc/split-rchunk \ diff --git a/tests/misc/split-k b/tests/misc/split-k new file mode 100755 index 0000000..0c517ad --- /dev/null +++ b/tests/misc/split-k @@ -0,0 +1,43 @@ +#!/bin/sh +# Show that split -k works. + +# Copyright (C) 2002-2010 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 3 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, see . + +. "${srcdir=.}/init.sh"; path_prepend_ ../src +print_ver_ split + +printf '1\n2\n3\n4\n5\n' > in || framework_failure + +split --lines=2 --on-output-hook gzip --on-output-hook-suffix .gz in > out || fail=1 +gzip <<\EOF > exp-1.gz +1 +2 +EOF +gzip <<\EOF > exp-2.gz +3 +4 +EOF +gzip <<\EOF > exp-3.gz +5 +EOF + +compare xaa.gz exp-1.gz || fail=1 +compare xab.gz exp-2.gz || fail=1 +compare xac.gz exp-3.gz || fail=1 +test -f xaa && fail=1 +test -f xad.gz && fail=1 + +Exit $fail