Return the number of bytes read from the file. */
static uintmax_t
-dump_remainder (const char *pretty_filename, int fd, uintmax_t n_bytes)
+dump_remainder (bool want_header, const char *pretty_filename, int fd,
+ uintmax_t n_bytes)
{
uintmax_t n_written;
uintmax_t n_remaining = n_bytes;
}
if (bytes_read == 0)
break;
+ if (want_header)
+ {
+ write_header (pretty_filename);
+ want_header = false;
+ }
xwrite_stdout (buffer, bytes_read);
n_written += bytes_read;
if (n_bytes != COPY_TO_EOF)
output the part that is after it. */
if (n != bytes_read - 1)
xwrite_stdout (nl + 1, bytes_read - (n + 1));
- *read_pos += dump_remainder (pretty_filename, fd,
+ *read_pos += dump_remainder (false, pretty_filename, fd,
end_pos - (pos + bytes_read));
return true;
}
/* Not enough lines in the file; print everything from
start_pos to the end. */
xlseek (fd, start_pos, SEEK_SET, pretty_filename);
- *read_pos = start_pos + dump_remainder (pretty_filename, fd,
+ *read_pos = start_pos + dump_remainder (false, pretty_filename, fd,
end_pos);
return true;
}
else
bytes_to_read = COPY_TO_EOF;
- bytes_read = dump_remainder (name, fd, bytes_to_read);
+ bytes_read = dump_remainder (false, name, fd, bytes_to_read);
any_input |= (bytes_read != 0);
f[i].size += bytes_read;
return spec1->wd == spec2->wd;
}
-/* Output (new) data for FSPEC->fd. */
+/* Output (new) data for FSPEC->fd.
+ PREV_FSPEC records the last File_spec for which we output. */
static void
check_fspec (struct File_spec *fspec, struct File_spec **prev_fspec)
{
&& timespec_cmp (fspec->mtime, get_stat_mtime (&stats)) == 0)
return;
- if (fspec != *prev_fspec)
- {
- if (print_headers)
- write_header (name);
- *prev_fspec = fspec;
- }
+ bool want_header = print_headers && (fspec != *prev_fspec);
- uintmax_t bytes_read = dump_remainder (name, fspec->fd, COPY_TO_EOF);
+ uintmax_t bytes_read = dump_remainder (want_header, name, fspec->fd,
+ COPY_TO_EOF);
fspec->size += bytes_read;
- if (fflush (stdout) != 0)
- die (EXIT_FAILURE, errno, _("write error"));
+ if (bytes_read)
+ {
+ *prev_fspec = fspec;
+ if (fflush (stdout) != 0)
+ die (EXIT_FAILURE, errno, _("write error"));
+ }
}
/* Attempt to tail N_FILES files forever, or until killed.
*read_pos = current_pos;
}
- *read_pos += dump_remainder (pretty_filename, fd, n_bytes);
+ *read_pos += dump_remainder (false, pretty_filename, fd, n_bytes);
return true;
}
int t = start_lines (pretty_filename, fd, n_lines, read_pos);
if (t)
return t < 0;
- *read_pos += dump_remainder (pretty_filename, fd, COPY_TO_EOF);
+ *read_pos += dump_remainder (false, pretty_filename, fd, COPY_TO_EOF);
}
else
{
--- /dev/null
+#!/bin/sh
+# inotify-based tail would output redundant headers for
+# overlapping inotify events while it was suspended
+
+# Copyright (C) 2017 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/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ tail sleep
+
+# Function to count number of lines from tail
+# while ignoring transient errors due to resource limits
+countlines_ ()
+{
+ grep -Ev 'inotify (resources exhausted|cannot be used)' out | wc -l
+}
+
+# Function to check the expected line count in 'out'.
+# Called via retry_delay_(). Sleep some time - see retry_delay_() - if the
+# line count is still smaller than expected.
+wait4lines_ ()
+{
+ local delay=$1
+ local elc=$2 # Expected line count.
+ [ "$(countlines_)" -ge "$elc" ] || { sleep $delay; return 1; }
+}
+
+# Speedup the non inotify case
+fastpoll='---dis -s.1 --max-unchanged-stats=1'
+
+# Terminate any background tail process
+cleanup_() {
+ kill $pid 2>/dev/null && wait $pid;
+ kill $sleep 2>/dev/null && wait $sleep
+}
+
+echo start > file1 || framework_failure_
+echo start > file2 || framework_failure_
+
+# Use this as a way to gracefully terminate tail
+env sleep 20 & sleep=$!
+
+tail $fastpoll --pid=$sleep -f file1 file2 > out & pid=$!
+
+kill -0 $pid || fail=1
+
+# Wait for 5 initial lines
+retry_delay_ wait4lines_ .1 6 5 || fail=1
+
+# Suspend tail so single read() caters for multiple inotify events
+kill -STOP $pid || fail=1
+
+# Interleave writes to files to generate overlapping inotify events
+echo line >> file1 || framework_failure_
+echo line >> file2 || framework_failure_
+echo line >> file1 || framework_failure_
+echo line >> file2 || framework_failure_
+
+# Resume tail processing
+kill -CONT $pid || fail=1
+
+# Wait for 8 more lines
+retry_delay_ wait4lines_ .1 6 13 || fail=1
+
+kill $sleep && wait || framework_failure_
+
+test "$(countlines_)" = 13 || fail=1
+
+Exit $fail