[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
New utility pb (patch included)
From: |
Miika Pekkarinen |
Subject: |
New utility pb (patch included) |
Date: |
Fri, 13 Feb 2004 17:24:08 +0200 (EET) |
Hi
I have created a new utility pb (progress bar) for gathering progress
information from different utilities. Currently cp and mv are supported
with a patch included. In future I could add support for other utilities
such dd too.
Please try it at tell me what you think about it. :)
Most likely there are lot of bugs at the moment but I could fix them and
improve the utility in the future.
While testing the following line could be useful:
./pb -- ./cp -vr <some dir with big files> /tmp/
I have patched cp and mv to support USR1-signal. This way there was
minimal changes to existing programs and mostly everything new are in pb
(my earlier progress bar patch was made directly into the utilities and I
think also that was a wrong way). The patch was made for coreutils version
5.1.3 because I couldn't get automake working with the latest cvs :(
In case that the included patch was corrupted by mail software, it's
available from web also:
http://ihme.org/~miipekk/patches/coreutils_pb-0.1.diff
--
Miika Pekkarinen <miika at ihme.org>
Before the patch I have included what pb --help prints:
Usage: ./pb [OPTION]... COMMAND [PARAMETERS]
Execute a program COMMAND with arguments defined in PARAMETERS.
A progress bar is displayed to provide progress information.
-f, --format=FORMAT Display a user specified progress bar
-d, --delay=SECONDS Time to wait before progress info is shown
-v, --verbose Don't clean progress info with a newline
FORMAT controls the output. Allowed interpreted control sequences are:
%% a literal %
%f current file name
%i bytes copied/done
%I bytes copied using SI-units
%p percents done
%s total file size in bytes
%S total file size in bytes using SI-units
Mandatory arguments to long options are mandatory for short options too.
--help display this help and exit
--version output version information and exit
Report bugs to <address@hidden>.
diff -Nur coreutils-5.1.3_orig/src/Makefile.am
coreutils-5.1.3_new/src/Makefile.am
--- coreutils-5.1.3_orig/src/Makefile.am 2004-02-02 10:12:57.000000000
+0200
+++ coreutils-5.1.3_new/src/Makefile.am 2004-02-13 11:12:02.000000000 +0200
@@ -10,7 +10,7 @@
nl od paste pr ptx sha1sum sort split sum tac tail tr tsort unexpand uniq wc
\
basename date dirname echo env expr factor false \
hostname id kill logname pathchk printenv printf pwd seq sleep tee \
- test true tty whoami yes \
+ test true tty whoami yes pb \
$(OPTIONAL_BIN_PROGS) $(DF_PROG)
noinst_PROGRAMS = setuidgid
diff -Nur coreutils-5.1.3_orig/src/Makefile.in
coreutils-5.1.3_new/src/Makefile.in
--- coreutils-5.1.3_orig/src/Makefile.in 2004-02-05 15:42:52.000000000
+0200
+++ coreutils-5.1.3_new/src/Makefile.in 2004-02-13 11:12:15.000000000 +0200
@@ -16,7 +16,7 @@
-SOURCES = $(__SOURCES) basename.c cat.c $(chgrp_SOURCES) chmod.c
$(chown_SOURCES) chroot.c cksum.c comm.c $(cp_SOURCES) csplit.c cut.c date.c
dd.c df.c $(dir_SOURCES) dircolors.c dirname.c du.c echo.c env.c expand.c
expr.c factor.c false.c fmt.c fold.c $(ginstall_SOURCES) head.c hostid.c
hostname.c id.c join.c kill.c link.c ln.c logname.c $(ls_SOURCES)
$(md5sum_SOURCES) mkdir.c mkfifo.c mknod.c $(mv_SOURCES) nice.c nl.c nohup.c
od.c paste.c pathchk.c pinky.c pr.c printenv.c printf.c ptx.c pwd.c readlink.c
$(rm_SOURCES) rmdir.c seq.c setuidgid.c $(sha1sum_SOURCES) shred.c sleep.c
sort.c split.c stat.c stty.c su.c sum.c sync.c tac.c tail.c tee.c test.c
touch.c tr.c true.c tsort.c tty.c uname.c unexpand.c uniq.c unlink.c uptime.c
users.c $(vdir_SOURCES) wc.c who.c whoami.c yes.c
+SOURCES = $(__SOURCES) basename.c cat.c $(chgrp_SOURCES) chmod.c
$(chown_SOURCES) chroot.c cksum.c comm.c $(cp_SOURCES) csplit.c cut.c date.c
dd.c df.c $(dir_SOURCES) dircolors.c dirname.c du.c echo.c env.c expand.c
expr.c factor.c false.c fmt.c fold.c $(ginstall_SOURCES) head.c hostid.c
hostname.c id.c join.c kill.c link.c ln.c logname.c $(ls_SOURCES)
$(md5sum_SOURCES) mkdir.c mkfifo.c mknod.c $(mv_SOURCES) nice.c nl.c nohup.c
od.c paste.c pathchk.c pb.c pinky.c pr.c printenv.c printf.c ptx.c pwd.c
readlink.c $(rm_SOURCES) rmdir.c seq.c setuidgid.c $(sha1sum_SOURCES) shred.c
sleep.c sort.c split.c stat.c stty.c su.c sum.c sync.c tac.c tail.c tee.c
test.c touch.c tr.c true.c tsort.c tty.c uname.c unexpand.c uniq.c unlink.c
uptime.c users.c $(vdir_SOURCES) wc.c who.c whoami.c yes.c
srcdir = @srcdir@
top_srcdir = @top_srcdir@
@@ -60,8 +60,8 @@
kill$(EXEEXT) logname$(EXEEXT) pathchk$(EXEEXT) \
printenv$(EXEEXT) printf$(EXEEXT) pwd$(EXEEXT) seq$(EXEEXT) \
sleep$(EXEEXT) tee$(EXEEXT) test$(EXEEXT) true$(EXEEXT) \
- tty$(EXEEXT) whoami$(EXEEXT) yes$(EXEEXT) $(am__EXEEXT_1) \
- $(am__EXEEXT_2)
+ tty$(EXEEXT) whoami$(EXEEXT) yes$(EXEEXT) pb$(EXEEXT) \
+ $(am__EXEEXT_1) $(am__EXEEXT_2)
noinst_PROGRAMS = setuidgid$(EXEEXT)
subdir = src
DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \
@@ -414,6 +414,11 @@
pathchk_LDADD = $(LDADD)
pathchk_DEPENDENCIES = ../lib/libfetish.a $(am__DEPENDENCIES_1) \
../lib/libfetish.a
+pb_SOURCES = pb.c
+pb_OBJECTS = pb.$(OBJEXT)
+pb_LDADD = $(LDADD)
+pb_DEPENDENCIES = ../lib/libfetish.a $(am__DEPENDENCIES_1) \
+ ../lib/libfetish.a
pinky_SOURCES = pinky.c
pinky_OBJECTS = pinky.$(OBJEXT)
pinky_LDADD = $(LDADD)
@@ -637,27 +642,28 @@
@AMDEP_TRUE@ ./$(DEPDIR)/mv.Po ./$(DEPDIR)/nice.Po \
@AMDEP_TRUE@ ./$(DEPDIR)/nl.Po ./$(DEPDIR)/nohup.Po \
@AMDEP_TRUE@ ./$(DEPDIR)/od.Po ./$(DEPDIR)/paste.Po \
address@hidden@ ./$(DEPDIR)/pathchk.Po ./$(DEPDIR)/pinky.Po \
address@hidden@ ./$(DEPDIR)/pr.Po ./$(DEPDIR)/printenv.Po \
address@hidden@ ./$(DEPDIR)/printf.Po ./$(DEPDIR)/ptx.Po \
address@hidden@ ./$(DEPDIR)/pwd.Po ./$(DEPDIR)/readlink.Po \
address@hidden@ ./$(DEPDIR)/remove.Po ./$(DEPDIR)/rm.Po \
address@hidden@ ./$(DEPDIR)/rmdir.Po ./$(DEPDIR)/seq.Po \
address@hidden@ ./$(DEPDIR)/setuidgid.Po ./$(DEPDIR)/sha1sum.Po \
address@hidden@ ./$(DEPDIR)/shred.Po ./$(DEPDIR)/sleep.Po \
address@hidden@ ./$(DEPDIR)/sort.Po ./$(DEPDIR)/split.Po \
address@hidden@ ./$(DEPDIR)/stat.Po ./$(DEPDIR)/stty.Po \
address@hidden@ ./$(DEPDIR)/su.Po ./$(DEPDIR)/sum.Po \
address@hidden@ ./$(DEPDIR)/sync.Po ./$(DEPDIR)/tac.Po \
address@hidden@ ./$(DEPDIR)/tail.Po ./$(DEPDIR)/tee.Po \
address@hidden@ ./$(DEPDIR)/test.Po ./$(DEPDIR)/touch.Po \
address@hidden@ ./$(DEPDIR)/tr.Po ./$(DEPDIR)/true.Po \
address@hidden@ ./$(DEPDIR)/tsort.Po ./$(DEPDIR)/tty.Po \
address@hidden@ ./$(DEPDIR)/uname.Po ./$(DEPDIR)/unexpand.Po \
address@hidden@ ./$(DEPDIR)/uniq.Po ./$(DEPDIR)/unlink.Po \
address@hidden@ ./$(DEPDIR)/uptime.Po ./$(DEPDIR)/users.Po \
address@hidden@ ./$(DEPDIR)/wc.Po ./$(DEPDIR)/who.Po \
address@hidden@ ./$(DEPDIR)/whoami.Po ./$(DEPDIR)/yes.Po
address@hidden@ ./$(DEPDIR)/pathchk.Po ./$(DEPDIR)/pb.Po \
address@hidden@ ./$(DEPDIR)/pinky.Po ./$(DEPDIR)/pr.Po \
address@hidden@ ./$(DEPDIR)/printenv.Po ./$(DEPDIR)/printf.Po \
address@hidden@ ./$(DEPDIR)/ptx.Po ./$(DEPDIR)/pwd.Po \
address@hidden@ ./$(DEPDIR)/readlink.Po ./$(DEPDIR)/remove.Po \
address@hidden@ ./$(DEPDIR)/rm.Po ./$(DEPDIR)/rmdir.Po \
address@hidden@ ./$(DEPDIR)/seq.Po ./$(DEPDIR)/setuidgid.Po \
address@hidden@ ./$(DEPDIR)/sha1sum.Po ./$(DEPDIR)/shred.Po \
address@hidden@ ./$(DEPDIR)/sleep.Po ./$(DEPDIR)/sort.Po \
address@hidden@ ./$(DEPDIR)/split.Po ./$(DEPDIR)/stat.Po \
address@hidden@ ./$(DEPDIR)/stty.Po ./$(DEPDIR)/su.Po \
address@hidden@ ./$(DEPDIR)/sum.Po ./$(DEPDIR)/sync.Po \
address@hidden@ ./$(DEPDIR)/tac.Po ./$(DEPDIR)/tail.Po \
address@hidden@ ./$(DEPDIR)/tee.Po ./$(DEPDIR)/test.Po \
address@hidden@ ./$(DEPDIR)/touch.Po ./$(DEPDIR)/tr.Po \
address@hidden@ ./$(DEPDIR)/true.Po ./$(DEPDIR)/tsort.Po \
address@hidden@ ./$(DEPDIR)/tty.Po ./$(DEPDIR)/uname.Po \
address@hidden@ ./$(DEPDIR)/unexpand.Po ./$(DEPDIR)/uniq.Po \
address@hidden@ ./$(DEPDIR)/unlink.Po ./$(DEPDIR)/uptime.Po \
address@hidden@ ./$(DEPDIR)/users.Po ./$(DEPDIR)/wc.Po \
address@hidden@ ./$(DEPDIR)/who.Po ./$(DEPDIR)/whoami.Po \
address@hidden@ ./$(DEPDIR)/yes.Po
COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
CCLD = $(CC)
@@ -669,7 +675,7 @@
fmt.c fold.c $(ginstall_SOURCES) head.c hostid.c hostname.c \
id.c join.c kill.c link.c ln.c logname.c $(ls_SOURCES) \
$(md5sum_SOURCES) mkdir.c mkfifo.c mknod.c $(mv_SOURCES) \
- nice.c nl.c nohup.c od.c paste.c pathchk.c pinky.c pr.c \
+ nice.c nl.c nohup.c od.c paste.c pathchk.c pb.c pinky.c pr.c \
printenv.c printf.c ptx.c pwd.c readlink.c $(rm_SOURCES) \
rmdir.c seq.c setuidgid.c $(sha1sum_SOURCES) shred.c sleep.c \
sort.c split.c stat.c stty.c su.c sum.c sync.c tac.c tail.c \
@@ -683,7 +689,7 @@
fmt.c fold.c $(ginstall_SOURCES) head.c hostid.c hostname.c \
id.c join.c kill.c link.c ln.c logname.c $(ls_SOURCES) \
$(md5sum_SOURCES) mkdir.c mkfifo.c mknod.c $(mv_SOURCES) \
- nice.c nl.c nohup.c od.c paste.c pathchk.c pinky.c pr.c \
+ nice.c nl.c nohup.c od.c paste.c pathchk.c pb.c pinky.c pr.c \
printenv.c printf.c ptx.c pwd.c readlink.c $(rm_SOURCES) \
rmdir.c seq.c setuidgid.c $(sha1sum_SOURCES) shred.c sleep.c \
sort.c split.c stat.c stty.c su.c sum.c sync.c tac.c tail.c \
@@ -974,7 +980,7 @@
done
clean-binPROGRAMS:
- -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) > /dev/null 2>&1 ||
/bin/rm -f $(bin_PROGRAMS)
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
installcheck-binPROGRAMS: $(bin_PROGRAMS)
bad=0; pid=$$$$; list="$(bin_PROGRAMS)"; for p in $$list; do \
@@ -1140,6 +1146,9 @@
pathchk$(EXEEXT): $(pathchk_OBJECTS) $(pathchk_DEPENDENCIES)
@rm -f pathchk$(EXEEXT)
$(LINK) $(pathchk_LDFLAGS) $(pathchk_OBJECTS) $(pathchk_LDADD) $(LIBS)
+pb$(EXEEXT): $(pb_OBJECTS) $(pb_DEPENDENCIES)
+ @rm -f pb$(EXEEXT)
+ $(LINK) $(pb_LDFLAGS) $(pb_OBJECTS) $(pb_LDADD) $(LIBS)
pinky$(EXEEXT): $(pinky_OBJECTS) $(pinky_DEPENDENCIES)
@rm -f pinky$(EXEEXT)
$(LINK) $(pinky_LDFLAGS) $(pinky_OBJECTS) $(pinky_LDADD) $(LIBS)
@@ -1358,6 +1367,7 @@
@AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
@AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
@AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
address@hidden@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
@AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
@AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
@AMDEP_TRUE@@am__include@ @address@hidden/$(DEPDIR)/address@hidden@
diff -Nur coreutils-5.1.3_orig/src/copy.c coreutils-5.1.3_new/src/copy.c
--- coreutils-5.1.3_orig/src/copy.c 2004-02-07 17:42:04.000000000 +0200
+++ coreutils-5.1.3_new/src/copy.c 2004-02-13 11:28:12.000000000 +0200
@@ -21,6 +21,7 @@
#include <stdio.h>
#include <assert.h>
#include <sys/types.h>
+#include <signal.h>
#if HAVE_HURD_H
# include <hurd.h>
@@ -73,6 +74,16 @@
dev_t st_dev;
};
+/* Tell something about current file being processed. */
+struct status_info {
+ char program_name[10];
+ const char *name;
+ off_t size;
+ off_t index;
+};
+
+static struct status_info statinfo;
+
/* Initial size of the above hash table. */
#define DEST_INFO_INITIAL_CAPACITY 61
@@ -230,6 +241,14 @@
goto close_src_desc;
}
+ statinfo.name = (char *) rindex(src_path, '/');
+ if (statinfo.name != NULL)
+ statinfo.name = &statinfo.name[1];
+
+ statinfo.index = 0;
+ statinfo.size = 0;
+ statinfo.size = src_open_sb.st_size;
+
/* Compare the source dev/ino from the open file to the incoming,
saved ones obtained via a previous call to stat. */
if (! SAME_INODE (*src_sb, src_open_sb))
@@ -376,6 +395,9 @@
}
last_write_made_hole = 0;
}
+
+ /* Update file status information */
+ statinfo.index = n_read_total;
}
/* If the file ends with a `hole', something needs to be written at
@@ -740,6 +762,40 @@
return !!hash_lookup (ht, &new_ent);
}
+/* When a signal (typically SIGUSR1) is sent to the program, this
+ handler will send back status information. */
+static void
+status_handler (int signum)
+{
+ /* Make sure we don't use buffering for stdout at all. */
+ setbuf (stdout, NULL);
+
+ if (statinfo.name == NULL) {
+ fprintf (stderr, "%s: N/A\n",
+ statinfo.program_name);
+ return ;
+ }
+
+ fprintf (stderr, "%s: %llu/%llu %s\n",
+ statinfo.program_name,
+ statinfo.index,
+ statinfo.size,
+ statinfo.name);
+ fflush(stderr);
+}
+
+/* Initialize a signal handler for a signal (typically SIGUSR1)
+ which can be used to get some status information from
+ program. */
+sighandler_t
+status_signal_init (int signum, const char *progname)
+{
+ strncpy(statinfo.program_name, progname,
+ sizeof(statinfo.program_name) + 1);
+ statinfo.name = NULL;
+ return signal(signum, status_handler);
+}
+
/* Record destination filename, FILENAME, and dev/ino from *STATS,
in the hash table, HT. If HT is NULL, return immediately.
If STATS is NULL, call lstat on FILENAME to get the device
diff -Nur coreutils-5.1.3_orig/src/copy.h coreutils-5.1.3_new/src/copy.h
--- coreutils-5.1.3_orig/src/copy.h 2004-02-07 18:00:59.000000000 +0200
+++ coreutils-5.1.3_new/src/copy.h 2004-02-13 11:10:16.000000000 +0200
@@ -1,6 +1,8 @@
#ifndef COPY_H
# define COPY_H
+# include <signal.h>
+
# include "hash.h"
/* Control creation of sparse files (files with holes). */
@@ -209,4 +211,7 @@
void
src_info_init (struct cp_options *);
+sighandler_t
+status_signal_init (int signum, const char *progname);
+
#endif
diff -Nur coreutils-5.1.3_orig/src/cp.c coreutils-5.1.3_new/src/cp.c
--- coreutils-5.1.3_orig/src/cp.c 2004-02-07 17:55:09.000000000 +0200
+++ coreutils-5.1.3_new/src/cp.c 2004-02-13 11:10:16.000000000 +0200
@@ -22,6 +22,7 @@
#include <sys/types.h>
#include <assert.h>
#include <getopt.h>
+#include <signal.h>
#include "system.h"
#include "argmatch.h"
@@ -839,6 +840,10 @@
atexit (close_stdout);
cp_option_init (&x);
+ if (status_signal_init(SIGUSR1, "cp") == SIG_ERR)
+ {
+ error (0, errno, "Failed to initialize signal SIGUSR1.\n");
+ }
/* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless
we'll actually use backup_suffix_string. */
diff -Nur coreutils-5.1.3_orig/src/mv.c coreutils-5.1.3_new/src/mv.c
--- coreutils-5.1.3_orig/src/mv.c 2004-02-07 17:41:02.000000000 +0200
+++ coreutils-5.1.3_new/src/mv.c 2004-02-13 11:10:16.000000000 +0200
@@ -22,6 +22,7 @@
#include <getopt.h>
#include <sys/types.h>
#include <assert.h>
+#include <signal.h>
#include "system.h"
#include "argmatch.h"
@@ -381,6 +382,10 @@
atexit (close_stdout);
cp_option_init (&x);
+ if (status_signal_init(SIGUSR1, PROGRAM_NAME) == SIG_ERR)
+ {
+ error (0, errno, "Failed to initialize signal SIGUSR1.\n");
+ }
/* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless
we'll actually use backup_suffix_string. */
diff -Nur coreutils-5.1.3_orig/src/pb.c coreutils-5.1.3_new/src/pb.c
--- coreutils-5.1.3_orig/src/pb.c 1970-01-01 02:00:00.000000000 +0200
+++ coreutils-5.1.3_new/src/pb.c 2004-02-13 16:20:47.000000000 +0200
@@ -0,0 +1,698 @@
+/* pb.c -- progress bar utility
+ Copyright (C) 2004 Free Software Foundation.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ Written by Miika Pekkarinen. */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include "system.h"
+#include "error.h"
+#include "quote.h"
+#include "xstrtol.h"
+
+/* The official name of this program (e.g., no `g' prefix). */
+#define PROGRAM_NAME "pb"
+
+#define AUTHORS "Miika Pekkarinen"
+
+#define SPRINT(type, msg) snprintf (fmtbuf, sizeof fmtbuf, "%%%d%c",\
+ spacing, type);\
+ snprintf (msgbuf, sizeof msgbuf, fmtbuf, msg);\
+ strncat (message, msgbuf, sizeof(message) - 1);
+
+/* How many percents should be left to show up a progress bar
+ after an initial delay. */
+#define TRIGGER_PERCENT 75
+
+/* Format definition:
+ %f File name
+ %i Current index (or bytes done)
+ %I Print current index using SI-units
+ %p Percents done
+ %s Total bytes
+ %S Print total bytes using SI-units
+*/
+static char *format = "%f: %I/%S (%3p%%)";
+
+struct status_info
+{
+ char progname[20];
+ off_t n_read;
+ off_t size;
+ char filename[110];
+};
+
+static struct status_info statinfo;
+
+struct input_buffer
+{
+ char data[200];
+ int pos;
+};
+
+enum
+{
+ BUF_STDOUT = 0,
+ BUF_STDERR
+};
+
+char *program_name;
+
+int verbose_mode = 0;
+
+int statusline_length;
+
+/*
+ enum
+ {
+ FORMAT_OPTION = CHAR_MAX + 1,
+ UPDATE_INTERVAL_OPTION,
+ WIDTH_OPTION
+ };
+*/
+
+static int child_pid;
+static volatile int running = 1;
+
+static int fd_child_stderr;
+static int fd_child_stdout;
+
+static int pbar_active = 0;
+
+/* Command line arguments. */
+static struct option const long_options[] =
+{
+ {"format", required_argument, NULL, 'f'},
+/* {"update-interval", required_argument, NULL, 'i'}, */
+/* {"width", required_argument, NULL, 'w'}, */
+ {"verbose", no_argument, NULL, 'v'},
+ {"delay", required_argument, NULL, 'd'},
+ {GETOPT_HELP_OPTION_DECL},
+ {GETOPT_VERSION_OPTION_DECL},
+ {NULL, 0, NULL, 0}
+};
+
+/* Signal handlers. */
+void
+signal_handler (int signum)
+{
+ switch (signum)
+ {
+ case SIGCHLD: /* Child has finished */
+ running = 0;
+ break;
+
+ default:
+ printf ("Other signal got!\n");
+ }
+}
+
+/* Usage information. */
+void
+usage (int status)
+{
+ if (status != 0)
+ fprintf (stderr, _("Try `%s --help' for more information.\n"),
+ program_name);
+ else
+ {
+ printf (_("Usage: %s [OPTION]... COMMAND [PARAMETERS]\n"),
+ program_name);
+ fputs (_("\
+Execute a program COMMAND with arguments defined in PARAMETERS.\n\
+A progress bar is displayed to provide progress information.\n\
+\n\
+ -f, --format=FORMAT Display a user specified progress bar\n\
+ -d, --delay=SECONDS Time to wait before progress info is shown\n\
+ -v, --verbose Don't clean progress info with a newline\n\
+\n\
+FORMAT controls the output. Allowed interpreted control sequences are:\n\
+ %% a literal %\n\
+ %f current file name\n\
+ %i bytes copied/done\n\
+ %I bytes copied using SI-units\n\
+ %p percents done\n\
+ %s total file size in bytes\n\
+ %S total file size in bytes using SI-units\n\
+\n\
+Mandatory arguments to long options are mandatory for short options too.\n\
+\n\
+"), stdout);
+
+ fputs (HELP_OPTION_DESCRIPTION, stdout);
+ fputs (VERSION_OPTION_DESCRIPTION, stdout);
+
+ printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ }
+ exit (status);
+}
+
+/* Convert an integer to SI-units and return a pointer to the string. */
+const char *si_units(off_t size)
+{
+ static char buf[50];
+ static char *unit_array[] = { "B", "KiB", "MiB", "GiB", ""};
+ int i;
+
+ for (i = 0; size > 10000; i++)
+ {
+
+ if (unit_array[i][0] == '\0')
+ {
+
+ i--;
+ break;
+ }
+
+ size /= 1024;
+ }
+
+
+ snprintf (buf, sizeof(buf), "%lu %s", (unsigned long)size, unit_array[i]);
+
+ return buf;
+}
+
+/* Print some message only once to avoid flooding user's display on
+ error situations (when progress bar can't displayed). */
+static void
+print_once (const char *msg)
+{
+ static int printed = 0;
+
+ if (printed)
+ return ;
+
+ fprintf (stderr, "%s\n", msg);
+ printed = 1;
+}
+
+/* Parse format string and generate the progress bar.
+ Returns -1 on success,
+ 0 on error,
+ >0 when finished. */
+int
+parse_fmtchar (char fmtchar)
+{
+ static int spacing = 0;
+
+ /* The following buffers are used by macro SPRINT */
+ char fmtbuf[20];
+ char msgbuf[20];
+ char message[256] = "";
+
+ if (fmtchar == '\0')
+ return 0;
+
+ if (isdigit(fmtchar))
+ {
+ spacing *= 10;
+ spacing += (int)(fmtchar - '0');
+ if (spacing > 50)
+ {
+ print_once (_("invalid format"));
+ return 0;
+ }
+ return -1;
+ }
+
+
+ switch (fmtchar)
+ {
+ case '%':
+ strncat (message, "%", sizeof(message) - 1);
+ break;
+
+ case 'f':
+ SPRINT ('s', statinfo.filename);
+ break;
+
+ case 'i':
+ SPRINT ('d', statinfo.n_read);
+ break;
+
+ case 'I':
+ SPRINT ('s', si_units(statinfo.n_read));
+ break;
+
+ case 's':
+ SPRINT ('d', statinfo.size);
+ break;
+
+ case 'S':
+ SPRINT ('s', si_units(statinfo.size));
+ break;
+
+ case 'p':
+ if (statinfo.size == 0)
+ {
+ break;
+ }
+
+ SPRINT ('d', (int)(100 * statinfo.n_read / statinfo.size));
+ break;
+
+ default:
+ error (0, 0, _("unknown format character: %c"), fmtchar);
+ }
+
+ fputs (message, stderr);
+ spacing = 0;
+
+ return strlen(message);
+}
+
+/* Clean the statusline where progress bar is printed to. */
+void
+erase_statusline (void)
+{
+ while (statusline_length-- > 0)
+ {
+ fputc (' ', stderr);
+ }
+
+ fputc ('\r', stderr);
+ statusline_length = 0;
+}
+
+/* Generate the progress bar. */
+void
+pbar_print (void)
+{
+ int i;
+ int length;
+
+ erase_statusline ();
+
+ if (statinfo.filename[0] == '\0')
+ return ;
+
+ length = strlen (format);
+
+ for (i = 0; i < length; i++)
+ {
+ if (format[i] == '%')
+ {
+ int length;
+
+ while ( (length = parse_fmtchar(format[++i]) ) )
+ {
+ if (length > 0)
+ {
+ statusline_length += length;
+ break ;
+ }
+ }
+
+ continue ;
+ }
+
+ fputc (format[i], stderr);
+ statusline_length++;
+ }
+
+ fputc ('\r', stderr);
+
+ fflush (stderr);
+ fflush (stdout);
+ pbar_active = 1;
+}
+
+/* Send a SIGUSR1 signal to child requesting new status information. */
+void
+pbar_update (void)
+{
+ if (kill(child_pid, SIGUSR1) < 0)
+ return ;
+}
+
+/* Non-blocking function to get \n terminated lines from child.
+ Pointer to the received string is returned. */
+const char *
+read_child (int fd, struct input_buffer *buffer)
+{
+ char tempbuf[2];
+ int n_read;
+
+ /* Read bytes until got \n or no more data available */
+ while (buffer->pos < sizeof(buffer->data) - 1)
+ {
+ n_read = read (fd, tempbuf, 1);
+ if (n_read < 0)
+ {
+ if (errno != EAGAIN)
+ {
+ error (0, errno, _("read failed"));
+ exit (EXIT_FAILURE);
+ }
+
+ return NULL;
+ }
+
+ if (n_read == 0)
+ {
+ return NULL;
+ }
+
+ if (tempbuf[0] == '\n')
+ {
+ buffer->data[buffer->pos] = '\0';
+ buffer->pos = 0;
+ return buffer->data;
+ }
+
+ buffer->data[buffer->pos++] = tempbuf[0];
+ }
+
+ buffer->data[buffer->pos] = '\0';
+ buffer->pos = 0;
+
+ return buffer->data;
+}
+
+/* Dump child's stdout to screen. */
+static void
+dump_stdout (const char *msg)
+{
+ if (pbar_active)
+ {
+ erase_statusline ();
+ pbar_active = 0;
+ }
+
+ fputs (msg, stdout);
+ fputc ('\n', stdout);
+ fflush (stdout);
+}
+
+/* Complete progress bar for current file by displaying status 100%. */
+void
+pbar_flush (void)
+{
+ if (statinfo.filename[0] == '\0')
+ return ;
+
+ if (verbose_mode)
+ {
+ statinfo.n_read = statinfo.size;
+ pbar_print ();
+ fputc ('\n', stderr);
+ }
+ else
+ {
+ erase_statusline ();
+ fputc ('\r', stderr);
+ }
+
+ statusline_length = 0;
+ pbar_active = 0;
+}
+
+/* This function tries to detect different output formats from
+ programs and parses information from them.
+ Returns 1 if progress bar needs update.
+ */
+int
+parse_response (const char *msg)
+{
+ struct status_info newinfo;
+ int need_update = 0;
+
+ /* dd gives (the old version, currently not supported :():
+ * 25041930 bytes transferred in 18.458752 seconds (1356643 bytes/sec)
+ * cp/mv gives:
+ * cp: 1000/50000 filename.txt */
+ if (sscanf(msg, "%10s %llu/%llu %100s",
+ newinfo.progname,
+ &newinfo.n_read,
+ &newinfo.size,
+ newinfo.filename) == 4)
+ {
+ /* If filename changed then print 100% for last name */
+ if (strcmp(statinfo.filename, newinfo.filename) != 0)
+ pbar_flush ();
+ }
+
+ else
+ {
+ if (newinfo.progname != NULL)
+ {
+ if (!strcmp("cp:", newinfo.progname) || strcmp("mv:",
newinfo.progname))
+ return 0;
+ }
+
+ print_once (_("Unsupported response detected."));
+ /* Just print the received message to user because we can't parse it. */
+ fprintf (stderr, "%s\n", msg);
+ newinfo.filename[0] = '\0';
+ return 0;
+ }
+
+ if (memcmp(&statinfo, &newinfo, sizeof(struct status_info)) != 0)
+ {
+ need_update = 1;
+ }
+
+ memcpy (&statinfo, &newinfo, sizeof(struct status_info));
+
+ return need_update;
+}
+
+/* Executing a program with given arguments and
+ initialization of I/O redirection. */
+void
+start_command (int n_arguments, char **args)
+{
+ char *cmd;
+ int fd_stderr[2];
+ int fd_stdout[2];
+
+ if (n_arguments < 1) {
+ error (0, 0, _("missing argument"));
+ usage (EXIT_FAILURE);
+ }
+
+ /* Initializing signal handlers */
+ if (signal(SIGCHLD, signal_handler) == SIG_ERR)
+ {
+ error (0, errno, _("failed to initialize a signal handler"));
+ exit (EXIT_FAILURE);
+ }
+
+ if (signal(SIGPIPE, signal_handler) == SIG_ERR)
+ {
+ error (0, errno, _("failed to initialize a signal handler"));
+ exit (EXIT_FAILURE);
+ }
+
+ /* Create pipes */
+ if (pipe(fd_stderr) < 0)
+ {
+ error (0, errno, _("cannot create pipe #1"));
+ exit (EXIT_FAILURE);
+ }
+
+ if (pipe(fd_stdout) < 0)
+ {
+ error (0, errno, _("cannot create pipe #2"));
+ exit (EXIT_FAILURE);
+ }
+
+ fd_child_stderr = fd_stderr[0];
+ fd_child_stdout = fd_stdout[0];
+
+ /* Set non-blocking mode */
+ if (fcntl(fd_child_stderr, F_SETFL, O_NONBLOCK) < 0)
+ {
+ error (0, errno, _("fcntl failed #1"));
+ exit (EXIT_FAILURE);
+ }
+
+ if (fcntl(fd_child_stdout, F_SETFL, O_NONBLOCK) < 0)
+ {
+ error (0, errno, _("fcntl failed #2"));
+ exit (EXIT_FAILURE);
+ }
+
+ cmd = args[0];
+ child_pid = fork();
+ switch (child_pid)
+ {
+ case -1: /* fork failed */
+ error (0, errno, _("fork failed"));
+ exit (EXIT_FAILURE);
+ break;
+
+ case 0: /* child */
+ /* Re-map stdout and stderr */
+ dup2 (fd_stderr[1], fileno(stderr));
+ close (fd_stderr[0]);
+ close (fd_stderr[1]);
+
+ dup2 (fd_stdout[1], fileno(stdout));
+ close (fd_stdout[0]);
+ close (fd_stdout[1]);
+
+ if (execvp(cmd, args) < 0)
+ {
+ error (0, errno, _("execvp failed"));
+ exit (EXIT_FAILURE);
+ }
+ break;
+
+ default: /* parent */
+ close (fd_stderr[1]);
+ close (fd_stdout[1]);
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ struct status_info lastinfo;
+ struct input_buffer buffers[2];
+ struct timespec ts;
+ int counter = 0;
+
+ int c;
+ const char *p;
+ long int delay = 100;
+ long int delay_left
+
+ initialize_main (&argc, &argv);
+ program_name = argv[0];
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ atexit (close_stdout);
+
+ while ((c = getopt_long (argc, argv, "d:f:v", long_options, NULL))
+ != -1)
+ {
+ switch (c)
+ {
+ case 0:
+ break;
+
+ case 'd':
+ if (xstrtol (optarg, NULL, 10, &delay, "") != LONGINT_OK
+ || delay < 0 || delay > INT_MAX)
+ {
+ error (0, errno, _("incorrect delay specified"));
+ exit (EXIT_FAILURE);
+ }
+ delay *= 100;
+
+ break;
+
+ case 'f':
+ format = optarg;
+ break;
+
+ case 'i':
+ break;
+
+ case 'w':
+ break;
+
+ case 'v':
+ verbose_mode = 1;
+ break;
+
+ case_GETOPT_HELP_CHAR;
+
+ case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+
+ default:
+ usage (EXIT_FAILURE);
+ }
+ }
+
+ /* Initialize everything. */
+ memset (&statinfo, 0, sizeof(struct status_info));
+ memset (&lastinfo, 0, sizeof(struct status_info));
+ buffers[BUF_STDOUT].pos = 0;
+ buffers[BUF_STDERR].pos = 0;
+ delay_left = delay;
+ statusline_length = 0;
+
+ start_command(argc - optind, argv + optind);
+
+ /* Wait until the child has finished. */
+ while (running)
+ {
+ /* 10 ms sleep */
+ ts.tv_sec = 0;
+ ts.tv_nsec = 10000000;
+ nanosleep (&ts, NULL);
+
+ /* Update the progress bar every 100 ms. */
+ if (++counter == 10)
+ {
+ pbar_update();
+ counter = 0;
+ }
+
+ /* Print stdout to screen. */
+ while ( (p = read_child(fd_child_stdout, &buffers[BUF_STDOUT])) != NULL)
+ {
+ dump_stdout(p);
+ }
+
+ /* Parse stderr. */
+ while ( (p = read_child(fd_child_stderr, &buffers[BUF_STDERR])) != NULL)
+ {
+ /* Print progress bar only when some changes has happened. */
+ if (parse_response(p) == 0)
+ continue ;
+
+ if (strcmp(lastinfo.filename, statinfo.filename) != 0)
+ delay_left = delay;
+
+ if (delay_left == 0)
+ pbar_print ();
+ memcpy (&lastinfo, &statinfo, sizeof(struct status_info));
+ }
+
+ if (delay_left > 0 && statinfo.size > 0)
+ {
+ /* Check if percents done are above the trigger. In that case
+ we won't print a progress bar. */
+ if ( (100 * statinfo.n_read / statinfo.size) < TRIGGER_PERCENT)
+ delay_left--;
+ }
+ }
+
+ if (delay_left == 0)
+ pbar_flush ();
+
+ /* Close all file descriptors */
+ close (fd_child_stderr);
+ close (fd_child_stdout);
+
+ exit (EXIT_SUCCESS);
+}
+
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- New utility pb (patch included),
Miika Pekkarinen <=