mirror of
https://github.com/coreutils/coreutils.git
synced 2024-11-24 10:23:31 +08:00
tail: use inotify if it is available
* NEWS: Document the new feature. * m4/jm-macros.m4: Check if inotify is present. * src/tail.c (tail_forever_inotify): New function. (main): Use the inotify-based function, if possible. * tests/Makefile.am: Add new tests for tail. * tests/test-lib.sh (require_proc_pid_status_, get_process_status_): New functions. * tests/tail-2/pid: New file. * tests/tail-2/wait: New file. * tests/tail-2/tail-n0f: Refactor code into the test-lib.sh require_proc_pid_status_ function.
This commit is contained in:
parent
358aca5fb9
commit
ae494d4be8
2
NEWS
2
NEWS
@ -28,6 +28,8 @@ GNU coreutils NEWS -*- outline -*-
|
|||||||
sort accepts a new option, --human-numeric-sort (-h): sort numbers
|
sort accepts a new option, --human-numeric-sort (-h): sort numbers
|
||||||
while honoring human readable suffixes like KiB and MB etc.
|
while honoring human readable suffixes like KiB and MB etc.
|
||||||
|
|
||||||
|
tail uses inotify when possible.
|
||||||
|
|
||||||
|
|
||||||
* Noteworthy changes in release 7.4 (2009-05-07) [stable]
|
* Noteworthy changes in release 7.4 (2009-05-07) [stable]
|
||||||
|
|
||||||
|
@ -52,6 +52,11 @@ AC_DEFUN([coreutils_MACROS],
|
|||||||
# Used by sort.c.
|
# Used by sort.c.
|
||||||
AC_CHECK_FUNCS_ONCE([nl_langinfo])
|
AC_CHECK_FUNCS_ONCE([nl_langinfo])
|
||||||
|
|
||||||
|
# Used by tail.c.
|
||||||
|
AC_CHECK_FUNCS([inotify_init],
|
||||||
|
[AC_DEFINE([HAVE_INOTIFY], [1],
|
||||||
|
[Define to 1 if you have usable inotify support.])])
|
||||||
|
|
||||||
AC_CHECK_FUNCS_ONCE( \
|
AC_CHECK_FUNCS_ONCE( \
|
||||||
endgrent \
|
endgrent \
|
||||||
endpwent \
|
endpwent \
|
||||||
|
275
src/tail.c
275
src/tail.c
@ -21,7 +21,8 @@
|
|||||||
|
|
||||||
Original version by Paul Rubin <phr@ocf.berkeley.edu>.
|
Original version by Paul Rubin <phr@ocf.berkeley.edu>.
|
||||||
Extensions by David MacKenzie <djm@gnu.ai.mit.edu>.
|
Extensions by David MacKenzie <djm@gnu.ai.mit.edu>.
|
||||||
tail -f for multiple files by Ian Lance Taylor <ian@airs.com>. */
|
tail -f for multiple files by Ian Lance Taylor <ian@airs.com>.
|
||||||
|
inotify back-end by Giuseppe Scrivano <gscrivano@gnu.org>. */
|
||||||
|
|
||||||
#include <config.h>
|
#include <config.h>
|
||||||
|
|
||||||
@ -46,6 +47,11 @@
|
|||||||
#include "xstrtol.h"
|
#include "xstrtol.h"
|
||||||
#include "xstrtod.h"
|
#include "xstrtod.h"
|
||||||
|
|
||||||
|
#if HAVE_INOTIFY
|
||||||
|
# include "hash.h"
|
||||||
|
# include <sys/inotify.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/* The official name of this program (e.g., no `g' prefix). */
|
/* The official name of this program (e.g., no `g' prefix). */
|
||||||
#define PROGRAM_NAME "tail"
|
#define PROGRAM_NAME "tail"
|
||||||
|
|
||||||
@ -125,8 +131,26 @@ struct File_spec
|
|||||||
/* The value of errno seen last time we checked this file. */
|
/* The value of errno seen last time we checked this file. */
|
||||||
int errnum;
|
int errnum;
|
||||||
|
|
||||||
|
#if HAVE_INOTIFY
|
||||||
|
/* The watch descriptor used by inotify. */
|
||||||
|
int wd;
|
||||||
|
|
||||||
|
/* The parent directory watch descriptor. It is used only
|
||||||
|
* when Follow_name is used. */
|
||||||
|
int parent_wd;
|
||||||
|
|
||||||
|
/* Offset in NAME of the basename part. */
|
||||||
|
size_t basename_start;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if HAVE_INOTIFY
|
||||||
|
/* The events mask used with inotify on files. This mask is not used on
|
||||||
|
directories. */
|
||||||
|
const uint32_t inotify_wd_mask = (IN_MODIFY | IN_ATTRIB | IN_DELETE_SELF
|
||||||
|
| IN_MOVE_SELF);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Keep trying to open a file even if it is inaccessible when tail starts
|
/* Keep trying to open a file even if it is inaccessible when tail starts
|
||||||
or if it becomes inaccessible later -- useful only with -f. */
|
or if it becomes inaccessible later -- useful only with -f. */
|
||||||
static bool reopen_inaccessible_files;
|
static bool reopen_inaccessible_files;
|
||||||
@ -964,7 +988,7 @@ any_live_files (const struct File_spec *f, int n_files)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tail NFILES files forever, or until killed.
|
/* Tail N_FILES files forever, or until killed.
|
||||||
The pertinent information for each file is stored in an entry of F.
|
The pertinent information for each file is stored in an entry of F.
|
||||||
Loop over each of them, doing an fstat to see if they have changed size,
|
Loop over each of them, doing an fstat to see if they have changed size,
|
||||||
and an occasional open/fstat to see if any dev/ino pair has changed.
|
and an occasional open/fstat to see if any dev/ino pair has changed.
|
||||||
@ -972,22 +996,22 @@ any_live_files (const struct File_spec *f, int n_files)
|
|||||||
while and try again. Continue until the user interrupts us. */
|
while and try again. Continue until the user interrupts us. */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
tail_forever (struct File_spec *f, int nfiles, double sleep_interval)
|
tail_forever (struct File_spec *f, int n_files, double sleep_interval)
|
||||||
{
|
{
|
||||||
/* Use blocking I/O as an optimization, when it's easy. */
|
/* Use blocking I/O as an optimization, when it's easy. */
|
||||||
bool blocking = (pid == 0 && follow_mode == Follow_descriptor
|
bool blocking = (pid == 0 && follow_mode == Follow_descriptor
|
||||||
&& nfiles == 1 && ! S_ISREG (f[0].mode));
|
&& n_files == 1 && ! S_ISREG (f[0].mode));
|
||||||
int last;
|
int last;
|
||||||
bool writer_is_dead = false;
|
bool writer_is_dead = false;
|
||||||
|
|
||||||
last = nfiles - 1;
|
last = n_files - 1;
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
bool any_input = false;
|
bool any_input = false;
|
||||||
|
|
||||||
for (i = 0; i < nfiles; i++)
|
for (i = 0; i < n_files; i++)
|
||||||
{
|
{
|
||||||
int fd;
|
int fd;
|
||||||
char const *name;
|
char const *name;
|
||||||
@ -1087,7 +1111,7 @@ tail_forever (struct File_spec *f, int nfiles, double sleep_interval)
|
|||||||
f[i].size += bytes_read;
|
f[i].size += bytes_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! any_live_files (f, nfiles) && ! reopen_inaccessible_files)
|
if (! any_live_files (f, n_files) && ! reopen_inaccessible_files)
|
||||||
{
|
{
|
||||||
error (0, 0, _("no files remaining"));
|
error (0, 0, _("no files remaining"));
|
||||||
break;
|
break;
|
||||||
@ -1117,6 +1141,224 @@ tail_forever (struct File_spec *f, int nfiles, double sleep_interval)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if HAVE_INOTIFY
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
wd_hasher (const void *entry, size_t tabsize)
|
||||||
|
{
|
||||||
|
const struct File_spec *spec = entry;
|
||||||
|
return spec->wd % tabsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
wd_comparator (const void *e1, const void *e2)
|
||||||
|
{
|
||||||
|
const struct File_spec *spec1 = e1;
|
||||||
|
const struct File_spec *spec2 = e2;
|
||||||
|
return spec1->wd == spec2->wd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tail N_FILES files forever, or until killed.
|
||||||
|
Check modifications using the inotify events system. */
|
||||||
|
|
||||||
|
static void
|
||||||
|
tail_forever_inotify (int wd, struct File_spec *f, int n_files)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
unsigned int max_realloc = 3;
|
||||||
|
Hash_table *wd_table;
|
||||||
|
|
||||||
|
bool found_watchable = false;
|
||||||
|
size_t prev_wd;
|
||||||
|
size_t evlen = 0;
|
||||||
|
char *evbuf;
|
||||||
|
size_t evbuf_off = 0;
|
||||||
|
ssize_t len = 0;
|
||||||
|
|
||||||
|
wd_table = hash_initialize (n_files, NULL, wd_hasher, wd_comparator, NULL);
|
||||||
|
if (! wd_table)
|
||||||
|
xalloc_die ();
|
||||||
|
|
||||||
|
/* Add an inotify watch for each watched file. If -F is specified then watch
|
||||||
|
its parent directory too, in this way when they re-appear we can add them
|
||||||
|
again to the watch list. */
|
||||||
|
for (i = 0; i < n_files; i++)
|
||||||
|
{
|
||||||
|
if (!f[i].ignore)
|
||||||
|
{
|
||||||
|
size_t fnlen = strlen (f[i].name);
|
||||||
|
if (evlen < fnlen)
|
||||||
|
evlen = fnlen;
|
||||||
|
|
||||||
|
f[i].wd = 0;
|
||||||
|
|
||||||
|
if (follow_mode == Follow_name)
|
||||||
|
{
|
||||||
|
size_t dirlen = dir_len (f[i].name);
|
||||||
|
char prev = f[i].name[dirlen];
|
||||||
|
f[i].basename_start = last_component (f[i].name) - f[i].name;
|
||||||
|
|
||||||
|
f[i].name[dirlen] = '\0';
|
||||||
|
|
||||||
|
/* It's fine to add the same directory more than once.
|
||||||
|
In that case the same watch descriptor is returned. */
|
||||||
|
f[i].parent_wd = inotify_add_watch (wd, dirlen ? f[i].name : ".",
|
||||||
|
(IN_CREATE | IN_MOVED_TO
|
||||||
|
| IN_ATTRIB));
|
||||||
|
|
||||||
|
f[i].name[dirlen] = prev;
|
||||||
|
|
||||||
|
if (f[i].parent_wd < 0)
|
||||||
|
{
|
||||||
|
error (0, errno, _("cannot watch parent directory of %s"),
|
||||||
|
quote (f[i].name));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f[i].wd = inotify_add_watch (wd, f[i].name, inotify_wd_mask);
|
||||||
|
|
||||||
|
if (f[i].wd < 0)
|
||||||
|
{
|
||||||
|
if (errno != f[i].errnum)
|
||||||
|
error (0, errno, _("cannot watch %s"), quote (f[i].name));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hash_insert (wd_table, &(f[i])) == NULL)
|
||||||
|
xalloc_die ();
|
||||||
|
|
||||||
|
if (follow_mode == Follow_name || f[i].wd)
|
||||||
|
found_watchable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (follow_mode == Follow_descriptor && !found_watchable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
prev_wd = f[n_files - 1].wd;
|
||||||
|
|
||||||
|
evlen += sizeof (struct inotify_event) + 1;
|
||||||
|
evbuf = xmalloc (evlen);
|
||||||
|
|
||||||
|
/* Wait for inotify events and handle them. Events on directories make sure
|
||||||
|
that watched files can be re-added when -F is used.
|
||||||
|
This loop sleeps on the `safe_read' call until a new event is notified. */
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
char const *name;
|
||||||
|
struct File_spec *fspec;
|
||||||
|
uintmax_t bytes_read;
|
||||||
|
struct stat stats;
|
||||||
|
|
||||||
|
struct inotify_event *ev;
|
||||||
|
|
||||||
|
if (len <= evbuf_off)
|
||||||
|
{
|
||||||
|
len = safe_read (wd, evbuf, evlen);
|
||||||
|
evbuf_off = 0;
|
||||||
|
|
||||||
|
if (len == SAFE_READ_ERROR && errno == EINVAL && max_realloc--)
|
||||||
|
{
|
||||||
|
len = 0;
|
||||||
|
evlen *= 2;
|
||||||
|
evbuf = xrealloc (evbuf, evlen);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == SAFE_READ_ERROR)
|
||||||
|
error (EXIT_FAILURE, errno, _("error reading inotify event"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ev = (struct inotify_event *) (evbuf + evbuf_off);
|
||||||
|
evbuf_off += sizeof (*ev) + ev->len;
|
||||||
|
|
||||||
|
if (ev->len)
|
||||||
|
{
|
||||||
|
for (i = 0; i < n_files; i++)
|
||||||
|
{
|
||||||
|
if (f[i].parent_wd == ev->wd &&
|
||||||
|
STREQ (ev->name, f[i].name + f[i].basename_start))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* It is not a watched file. */
|
||||||
|
if (i == n_files)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
f[i].wd = inotify_add_watch (wd, f[i].name, inotify_wd_mask);
|
||||||
|
|
||||||
|
if (f[i].wd < 0)
|
||||||
|
{
|
||||||
|
error (0, errno, _("cannot watch %s"), quote (f[i].name));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fspec = &(f[i]);
|
||||||
|
if (hash_insert (wd_table, fspec) == NULL)
|
||||||
|
xalloc_die ();
|
||||||
|
|
||||||
|
if (follow_mode == Follow_name)
|
||||||
|
recheck (&(f[i]), false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
struct File_spec key;
|
||||||
|
key.wd = ev->wd;
|
||||||
|
fspec = hash_lookup (wd_table, &key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! fspec)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ev->mask & (IN_ATTRIB | IN_DELETE_SELF | IN_MOVE_SELF))
|
||||||
|
{
|
||||||
|
if (ev->mask & (IN_DELETE_SELF | IN_MOVE_SELF))
|
||||||
|
{
|
||||||
|
inotify_rm_watch (wd, f[i].wd);
|
||||||
|
hash_delete (wd_table, &(f[i]));
|
||||||
|
}
|
||||||
|
if (follow_mode == Follow_name)
|
||||||
|
recheck (fspec, false);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = pretty_name (fspec);
|
||||||
|
|
||||||
|
if (fstat (fspec->fd, &stats) != 0)
|
||||||
|
{
|
||||||
|
close_fd (fspec->fd, name);
|
||||||
|
fspec->fd = -1;
|
||||||
|
fspec->errnum = errno;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (S_ISREG (fspec->mode) && stats.st_size < fspec->size)
|
||||||
|
{
|
||||||
|
error (0, 0, _("%s: file truncated"), name);
|
||||||
|
prev_wd = ev->wd;
|
||||||
|
xlseek (fspec->fd, stats.st_size, SEEK_SET, name);
|
||||||
|
fspec->size = stats.st_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev->wd != prev_wd)
|
||||||
|
{
|
||||||
|
if (print_headers)
|
||||||
|
write_header (name);
|
||||||
|
prev_wd = ev->wd;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_read = dump_remainder (name, fspec->fd, COPY_TO_EOF);
|
||||||
|
fspec->size += bytes_read;
|
||||||
|
|
||||||
|
if (fflush (stdout) != 0)
|
||||||
|
error (EXIT_FAILURE, errno, _("write error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Output the last N_BYTES bytes of file FILENAME open for reading in FD.
|
/* Output the last N_BYTES bytes of file FILENAME open for reading in FD.
|
||||||
Return true if successful. */
|
Return true if successful. */
|
||||||
|
|
||||||
@ -1691,7 +1933,24 @@ main (int argc, char **argv)
|
|||||||
ok &= tail_file (&F[i], n_units);
|
ok &= tail_file (&F[i], n_units);
|
||||||
|
|
||||||
if (forever)
|
if (forever)
|
||||||
tail_forever (F, n_files, sleep_interval);
|
{
|
||||||
|
#if HAVE_INOTIFY
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
int wd = inotify_init ();
|
||||||
|
if (wd < 0)
|
||||||
|
error (0, errno, _("inotify cannot be used, reverting to polling"));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tail_forever_inotify (wd, F, n_files);
|
||||||
|
|
||||||
|
/* The only way the above returns is upon failure. */
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
tail_forever (F, n_files, sleep_interval);
|
||||||
|
}
|
||||||
|
|
||||||
if (have_read_stdin && close (STDIN_FILENO) < 0)
|
if (have_read_stdin && close (STDIN_FILENO) < 0)
|
||||||
error (EXIT_FAILURE, errno, "-");
|
error (EXIT_FAILURE, errno, "-");
|
||||||
|
@ -137,6 +137,7 @@ TESTS = \
|
|||||||
misc/date-next-dow \
|
misc/date-next-dow \
|
||||||
misc/ptx-overrun \
|
misc/ptx-overrun \
|
||||||
misc/xstrtol \
|
misc/xstrtol \
|
||||||
|
tail-2/pid \
|
||||||
misc/od \
|
misc/od \
|
||||||
misc/mktemp \
|
misc/mktemp \
|
||||||
misc/arch \
|
misc/arch \
|
||||||
@ -243,6 +244,7 @@ TESTS = \
|
|||||||
misc/unexpand \
|
misc/unexpand \
|
||||||
misc/uniq \
|
misc/uniq \
|
||||||
misc/xattr \
|
misc/xattr \
|
||||||
|
tail-2/wait \
|
||||||
chmod/c-option \
|
chmod/c-option \
|
||||||
chmod/equal-x \
|
chmod/equal-x \
|
||||||
chmod/equals \
|
chmod/equals \
|
||||||
|
68
tests/tail-2/pid
Executable file
68
tests/tail-2/pid
Executable file
@ -0,0 +1,68 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Test the --pid option of tail.
|
||||||
|
|
||||||
|
# Copyright (C) 2003, 2006-2009 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
if test "$VERBOSE" = yes; then
|
||||||
|
set -x
|
||||||
|
tail --version
|
||||||
|
fi
|
||||||
|
|
||||||
|
. $srcdir/test-lib.sh
|
||||||
|
|
||||||
|
require_proc_pid_status_
|
||||||
|
|
||||||
|
touch here || framework_failure
|
||||||
|
|
||||||
|
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
# Use tail itself to create a background process.
|
||||||
|
|
||||||
|
tail -f here &
|
||||||
|
bg_pid=$!
|
||||||
|
|
||||||
|
tail -s0.1 -f here --pid=$bg_pid &
|
||||||
|
|
||||||
|
pid=$!
|
||||||
|
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
|
state=$(get_process_status_ $pid)
|
||||||
|
|
||||||
|
if test -n "$state"; then
|
||||||
|
case $state in
|
||||||
|
S*) ;;
|
||||||
|
*) echo $0: process dead 1>&2; fail=1 ;;
|
||||||
|
esac
|
||||||
|
kill $pid
|
||||||
|
fi
|
||||||
|
|
||||||
|
kill $bg_pid
|
||||||
|
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
|
state=$(get_process_status_ $pid)
|
||||||
|
|
||||||
|
if test -n "$state"; then
|
||||||
|
case $state in
|
||||||
|
S*) echo $0: process still active 1>&2; fail=1 ;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
kill $pid
|
||||||
|
fi
|
||||||
|
|
||||||
|
Exit $fail
|
@ -2,7 +2,7 @@
|
|||||||
# Make sure that `tail -n0 -f' and `tail -c0 -f' sleep
|
# Make sure that `tail -n0 -f' and `tail -c0 -f' sleep
|
||||||
# rather than doing what amounted to a busy-wait.
|
# rather than doing what amounted to a busy-wait.
|
||||||
|
|
||||||
# Copyright (C) 2003, 2006-2008 Free Software Foundation, Inc.
|
# Copyright (C) 2003, 2006-2009 Free Software Foundation, Inc.
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -28,12 +28,7 @@ fi
|
|||||||
|
|
||||||
. $srcdir/test-lib.sh
|
. $srcdir/test-lib.sh
|
||||||
|
|
||||||
sleep 2 &
|
require_proc_pid_status_
|
||||||
pid=$!
|
|
||||||
sleep .5
|
|
||||||
grep '^State:[ ]*[S]' /proc/$pid/status > /dev/null 2>&1 ||
|
|
||||||
skip_test_ "/proc/$pid/status: missing or 'different'"
|
|
||||||
kill $pid
|
|
||||||
|
|
||||||
touch empty || framework_failure
|
touch empty || framework_failure
|
||||||
echo anything > nonempty || framework_failure
|
echo anything > nonempty || framework_failure
|
||||||
|
131
tests/tail-2/wait
Executable file
131
tests/tail-2/wait
Executable file
@ -0,0 +1,131 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Make sure that `tail -f' returns immediately if a file doesn't exist
|
||||||
|
# while `tail -F' waits for it to appear.
|
||||||
|
|
||||||
|
# Copyright (C) 2003, 2006-2009 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
if test "$VERBOSE" = yes; then
|
||||||
|
set -x
|
||||||
|
tail --version
|
||||||
|
fi
|
||||||
|
|
||||||
|
. $srcdir/test-lib.sh
|
||||||
|
|
||||||
|
require_proc_pid_status_
|
||||||
|
|
||||||
|
touch here || framework_failure
|
||||||
|
touch k || framework_failure
|
||||||
|
(touch not_accessible && chmod 0 not_accessible) || framework_failure
|
||||||
|
|
||||||
|
fail=0
|
||||||
|
|
||||||
|
tail -s0.1 -f not_here &
|
||||||
|
pid=$!
|
||||||
|
sleep .5
|
||||||
|
state=$(get_process_status_ $pid)
|
||||||
|
|
||||||
|
if test -n "$state"; then
|
||||||
|
case $state in
|
||||||
|
S*) echo $0: process still active 1>&2; fail=1 ;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
kill $pid
|
||||||
|
fi
|
||||||
|
|
||||||
|
tail -s0.1 -f not_accessible &
|
||||||
|
pid=$!
|
||||||
|
sleep .5
|
||||||
|
state=$(get_process_status_ $pid)
|
||||||
|
|
||||||
|
if test -n "$state"; then
|
||||||
|
case $state in
|
||||||
|
S*) echo $0: process still active 1>&2; fail=1 ;;
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
kill $pid
|
||||||
|
fi
|
||||||
|
|
||||||
|
(tail -s0.1 -f here 2>tail.err) &
|
||||||
|
pid=$!
|
||||||
|
sleep .5
|
||||||
|
state=$(get_process_status_ $pid)
|
||||||
|
|
||||||
|
if test -n "$state"; then
|
||||||
|
case $state in
|
||||||
|
S*) ;;
|
||||||
|
*) echo $0: process died 1>&2; fail=1 ;;
|
||||||
|
esac
|
||||||
|
kill $pid
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# `tail -F' must wait in any case.
|
||||||
|
|
||||||
|
(tail -s0.1 -F here 2>>tail.err) &
|
||||||
|
pid=$!
|
||||||
|
sleep .5
|
||||||
|
state=$(get_process_status_ $pid)
|
||||||
|
|
||||||
|
if test -n "$state"; then
|
||||||
|
case $state in
|
||||||
|
S*) ;;
|
||||||
|
*) echo $0: process died 1>&2; fail=1 ;;
|
||||||
|
esac
|
||||||
|
kill $pid
|
||||||
|
fi
|
||||||
|
|
||||||
|
tail -s0.1 -F not_accessible &
|
||||||
|
pid=$!
|
||||||
|
sleep .5
|
||||||
|
state=$(get_process_status_ $pid)
|
||||||
|
|
||||||
|
if test -n "$state"; then
|
||||||
|
case $state in
|
||||||
|
S*) ;;
|
||||||
|
*) echo $0: process died 1>&2; fail=1 ;;
|
||||||
|
esac
|
||||||
|
kill $pid
|
||||||
|
fi
|
||||||
|
|
||||||
|
tail -s0.1 -F not_here &
|
||||||
|
pid=$!
|
||||||
|
sleep .5
|
||||||
|
state=$(get_process_status_ $pid)
|
||||||
|
|
||||||
|
if test -n "$state"; then
|
||||||
|
case $state in
|
||||||
|
S*) ;;
|
||||||
|
*) echo $0: process died 1>&2; fail=1 ;;
|
||||||
|
esac
|
||||||
|
kill $pid
|
||||||
|
fi
|
||||||
|
|
||||||
|
test -s tail.err && fail=1
|
||||||
|
|
||||||
|
tail -s.1 -F k > tail.out &
|
||||||
|
pid=$!
|
||||||
|
sleep .5
|
||||||
|
mv k l
|
||||||
|
sleep .5
|
||||||
|
touch k
|
||||||
|
mv k l
|
||||||
|
sleep .5
|
||||||
|
echo NO >> l
|
||||||
|
sleep .5
|
||||||
|
kill $pid
|
||||||
|
test -s tail.out && fail=1
|
||||||
|
|
||||||
|
Exit $fail
|
@ -122,6 +122,11 @@ uid_is_privileged_()
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_process_status_()
|
||||||
|
{
|
||||||
|
sed -n '/^State:[ ]*\([[:alpha:]]\).*/s//\1/p' /proc/$1/status
|
||||||
|
}
|
||||||
|
|
||||||
# Convert an ls-style permission string, like drwxr----x and -rw-r-x-wx
|
# Convert an ls-style permission string, like drwxr----x and -rw-r-x-wx
|
||||||
# to the equivalent chmod --mode (-m) argument, (=,u=rwx,g=r,o=x and
|
# to the equivalent chmod --mode (-m) argument, (=,u=rwx,g=r,o=x and
|
||||||
# =,u=rw,g=rx,o=wx). Ignore ACLs.
|
# =,u=rw,g=rx,o=wx). Ignore ACLs.
|
||||||
@ -234,6 +239,17 @@ of group names or numbers. E.g.,
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Is /proc/$PID/status supported?
|
||||||
|
require_proc_pid_status_()
|
||||||
|
{
|
||||||
|
sleep 2 &
|
||||||
|
local pid=$!
|
||||||
|
sleep .5
|
||||||
|
grep '^State:[ ]*[S]' /proc/$pid/status > /dev/null 2>&1 ||
|
||||||
|
skip_test_ "/proc/$pid/status: missing or 'different'"
|
||||||
|
kill $pid
|
||||||
|
}
|
||||||
|
|
||||||
# Does the current (working-dir) file system support sparse files?
|
# Does the current (working-dir) file system support sparse files?
|
||||||
require_sparse_support_()
|
require_sparse_support_()
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user