Skip to content

Commit

Permalink
Merge pull request #35 from Projeto-Pindorama/watch-implementation-ch…
Browse files Browse the repository at this point in the history
…anges

Watch implementation changes
  • Loading branch information
takusuman authored Jan 8, 2024
2 parents 55ceb88 + 0348993 commit 4fcfd6a
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 77 deletions.
23 changes: 12 additions & 11 deletions watch/watch.1
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,35 @@ This allows you to watch a program output change over the time.
It accepts the following options:
.TP
.B \-n
Specifies the interval to refresh the output.
Specifies the interval to refresh the output. Per default, the
interval is of 2 seconds.
.TP
.B \-b
Beeps if the program being run returns a non-zero exit status.
.TP
.B \-t
Turns off the information header, letting just the verbatim program
output being refreshed every time on the screen.
output being refreshed every time on the screen \(em equivalent to a \fIksh\fR(1)
\fIfor ((;;))\fR accompanied by \fIsleep\fR(1).
.br
Not recommended, unless you're using \fItmux\fR(1) or another terminal multiplexer
that shows you information that would originally be shown by this header.
that shows you information that would originally be shown by that header.
.SH EXAMPLES
It can be very useful when backuping some disc and needing to see how many files
were already copied.
.br
Let's suppose you're copying a disc from a terminal and, in another, you want to
keep an eye on \fIdu\fR(1) output to see every minute if the file size matches
Lets suppose you're copying a disc from a terminal and, in another, you want to
keep an eye at \fIdu\fR(1) output to see every minute if the file size matches
with what you're expecting.
.IP \& 2
.BI "% watch -n 60 -b du -sh /dsk/1"
.LP
.SH NOTES
This implementation does not support floating point numbers as the time
interval, neither it has support to the "\fIWATCH_INTERVAL\fR" environment
This implementation does not support the "\fIWATCH_INTERVAL\fR" environment
variable.
.SH "SEE ALSO"
execvp(3),
sleep(3),
nanosleep(2),
curses(3X)
.SH HISTORY
The
Expand All @@ -53,16 +54,16 @@ utility has a hazy history.
Nobody seems to be actually sure of where it has appeared
first.
.br
According to procps 010114 watch C source code, where it first
According to procps 010114's watch C source code, where it first
appeared in that package, it was written from scratch by Tony Rems
in 1991, and later recieved many modifications and corrections by
Francois Pinard.
.br
However, according to Internet Initiative Japan Inc. iwatch
However, according to Internet Initiative Japan Inc.'s iwatch
manual page, the watch utility came first from BSDI Inc. BSD/OS 3.1
and their code is slightly derived from it; he/she \(em who wrote the
manual page and comments at the original source code \(em also
speculated that it's existence was earlier than BSD/OS 3.1, from some
speculated that its existence was earlier than BSD/OS 3.1, from some
another free distribution, but there's no further information that
supports this.
.PP
Expand Down
255 changes: 189 additions & 66 deletions watch/watch.c
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
/*
/*
* watch.c - Keep an eye on a command output
*
*/
/*
* Copyright (C) 2023: Luiz Antônio Rangel (takusuman)
* Copyright (C) 2023, 2024: Luiz Antônio Rangel (takusuman)
* Arthur Bacci (arthurbacci)
*
* SPDX-Licence-Identifier: Zlib
*
* Support for calling commands with arguments without having to escape them
* with double-dash thoroughly based of IIJ's iwatch(1).
* As per the copyright header of IIJ's iwatch.c:
* Copyright (c) 2000, 2001 Internet Initiative Japan Inc.
* Copyright (c) 2000, 2001 Internet Initiative Japan Inc.
*
* SPDX-Licence-Identifier: BSD-2-Clause
*/
Expand All @@ -31,120 +33,241 @@ static char *progname;
int main(int argc, char *argv[]);
void usage(void);

struct Flag {
int Beep_on_error, No_title;
}; static struct Flag flag;

int main(int argc, char *argv[]) {
progname = argv[0];
// Initialize every integer, to avoid warnings when compiling
// with -Wconditional-uninitialized.
// Default interval of 2 seconds, as other major implementations
// usually do.
int option = 0,
interval = 2,
c = 0,
ec = 0,
term_x = 0,
term_y = 0;
int option = 0;
int fBeep_on_error = 0,
fNo_title = 0,
c = 0, ec = 0,
term_x = 0;
char **commandv;
pid_t exec_pid;
struct timespec interval;

// Variables for the information header.
// Defining nodename from now, since it shouldn't change while we're
// watching the command.
/*
* Default interval of 2 seconds, as other major
* implementations usually do.
*/
interval.tv_sec = 2;
interval.tv_nsec = 0;

/*
* Variables for the information header.
* Defining nodename from now, since it shouldn't change
* while we're watching the command.
*/
time_t now;
struct tm *timeinfo;
struct utsname u;
if ( uname(&u) == -1 ) {
if (uname(&u) == -1) {
prerror(errno);
exit(-1);
}

while ( (option = getopt(argc, argv, "n:hbt")) != -1 ) {
switch (option) {
case 'n':
interval = atoi(optarg);
break;
case 'b':
flag.Beep_on_error = 1;
break;
case 't':
flag.No_title = 1;
while ((option = getopt(argc, argv, "n:bth")) != -1) {
switch (option) {
case 'n':
if (!optarg) {
break;
}
char arg[128];
char *afterpoint = NULL;
strncpy(arg, optarg, 128);
arg[127] = '\0';

size_t point = 0;
for (; arg[point]; point++) {
if (arg[point] == '.' || arg[point] == ',') {
arg[point] = '\0';
afterpoint = &arg[point + 1];
break;
case 'h':
default:
usage();
}
}

if (strlen(arg) == 0) {
interval.tv_sec = 0;
} else {
interval.tv_sec = atoi(arg);
}

size_t afterpointlen = afterpoint ? strlen(afterpoint) : 0;
if (afterpointlen > 0) {
long integer = atoi(afterpoint);

if (afterpointlen > 9) {
afterpoint[9] = '\0';
afterpointlen = 9;
}

for (size_t i = 0; i < 9 - afterpointlen; i++)
integer *= 10;

interval.tv_nsec = integer;
}

if (interval.tv_sec == 0 && interval.tv_nsec < 100000000) {
interval.tv_nsec = 100000000;
}

break;
case 'b':
fBeep_on_error = 1;
break;
case 't':
fNo_title = 1;
break;
case 'h':
default:
usage();
}
}

/* FIXME: Not a good practice. */
argc -= optind;
argv += optind;

// Missing operand
if ( argc < 1 ){
/* Missing operand. */
if (argc < 1) {
usage();
}

// Now we just have to copy the "rest" of argv[] to a new character
// array allocating some space in memory with calloc(3) and then
// copying using a for loop.

if ( (commandv = calloc((unsigned long)(argc + 1), sizeof(char *))) == NULL ) {
prerror(errno);
/*
* Now we just have to copy the "rest" of argv[] to a new character
* array allocating some space in memory with calloc(3) and then
* copying using a for loop.
*/
if ((commandv = calloc((unsigned long)(argc + 1), sizeof(char *))) == NULL) {
/* Should I use prerror? */
perror("couldn't callocate");
exit(-1);
}

for ( c = 0; c < argc; c++ ) {
for (c = 0; c < argc; c++) {
commandv[c] = argv[c];
}

// Initialize curses terminal with colours to be used.
// Get terminal size too, we're going to need it.
/*
* Initialize curses terminal with colours to be used.
* Get terminal size too, we're going to need it.
*/
newterm(getenv("TERM"), stdout, stdin);
start_color();
init_pair(1, COLOR_BLACK, COLOR_WHITE);
getmaxyx(stdscr, term_y, term_x);

/*
* Declaring the left side of the information header, which contains
* "Every χ second(s): ...", outside the loop because it's immutable
* after we got the program we're going to run and the amount of time.
* Also initialize 'char left[]' with a null character for preventing
* it being considered an "incomplete type" by the compiler.
*/
int left_len = 0;
char left[256];
left[0] = '\0';
if (!fNo_title) {
/*
* FIXME: When one uses "-n 0.1", it actually prints
* "0.100000000" instead of 0.1 or even 0.10.
*/
left_len = snprintf(
left, 256, "Every %d.%d second(s): %s",
(int)interval.tv_sec, (int)interval.tv_nsec,
argv[0]
);
}

for (;;) {
// Get current time to be passed as a string with ctime(3).
/* Clear terminal for the next cycle. */
clear();

/* Get the terminal maximum x-axis size. */
term_x = getmaxx(stdscr);

/* Get current time to be passed as a string with ctime(3). */
time(&now);
if ( ! flag.No_title ){
timeinfo = localtime(&now);

if (!fNo_title) {
int right_len = 0; /* "left_len = 0" declared above. */
char right[256], time[256]; /* "left[256]" declared above. */

attron(COLOR_PAIR(1) | A_BOLD);
// Use a factor of terminal width (term_x) divided by 5,
// since it seems to work the best to keep the header at
// a "confortable" size.
printw("Every %d second(s): %-*s %s: %s\n",
interval, (term_x/5), argv[0], u.nodename, ctime(&now));

/*
* This is done because ctime returns
* a string with '\n'.
*/
strftime(time, 256, "%c", timeinfo);

right_len = snprintf(
right, 256, "%s: %s",
u.nodename, time
);

/*
* I think that a case involving left_len and right_len,
* which contain the "Every (int).(int) second(s): (string)"
* and "hostname: date", respectively, is improbable, so
* this is just a pure formality in case of one's
* C library implementation having a faulty snprintf().
*/
if (left_len <= 0 || right_len <= 0) {
perror("please try to execute it without the information header.\n");
exit(255);
}

if (left_len <= term_x && right_len <= term_x) {
/*
* If the sum of the text on left and right
* sections of the bar is larger than the
* terminal x axis, it shall be justified.
* Else, keep it as normal.
*/
if (left_len + right_len >= term_x) {
printw("%-*s", term_x, left);
printw("%*s", term_x, right);
} else {
printw("%s%*s", left, (term_x - left_len), right);
}
printw("\n");
}

attroff(COLOR_PAIR(1) | A_BOLD);
}

// Not using endwin(3x), since it breaks with multiline
// also-curses programs, such as ls(1) with the "-l" option.

/*
* Not using endwin(3x), since it breaks with multiline
* also-curses programs, such as ls(1) with the "-l" option.
*/
reset_shell_mode();
refresh();

if ( (exec_pid = fork()) == 0 ) {
if ( (execvp(commandv[0], commandv)) == -1 ) {
if ((exec_pid = fork()) == 0) {
if ((execvp(commandv[0], commandv)) == -1) {
prerror(errno);
exit(-1);
}
}
waitpid(exec_pid, &ec, 0);

// execvp'd command hasn't exit with success and we have
// flag.Beep_on_error activated.
if ( ec != 0 && flag.Beep_on_error ) {
/*
* execvp'd command hasn't exit with success and we have
* fBeep_on_error activated.
*/
if (ec != 0 && fBeep_on_error) {
beep();
}

sleep( (uint)(interval) );

// Clear terminal for the next cycle.
clear();
nanosleep(&interval, NULL);
}
}

void usage(void) {
pfmt(stderr, MM_NOSTD,
"usage: %s [-n seconds] [-bt] command [args...]\n", progname);
pfmt(
stderr, MM_NOSTD,
"usage: %s [-n seconds] [-bt] command [args...]\n",
progname
);
exit(1);
}

0 comments on commit 4fcfd6a

Please sign in to comment.