bug-coreutils
[Top][All Lists]
Advanced

[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);
+}
+







reply via email to

[Prev in Thread] Current Thread [Next in Thread]