[GNU Manual] [POSIX requirement] [Linux man] [FreeBSD man]
Summary
tail - output the last part of files
Lines of code: 2515
Principal syscall: write()
Support syscalls: open(), close()
Options: 25 (8 short, 17 long, does not include legacy digits for line count)
Originated with or shortly before the release of System III (1982)
Added to Textutils in November 1992 [First version]
Number of revisions: 408
tail is necessarily more complex than head because we must buffer input as it's read. Non-seekable steams are the most complex case since we cannot look forward to the end.
Helpers:any_live_files()
- Checks if any target files are already openany_non_regular_fifo()
- Checks if any targets are non-regular files (devices, etc)any_non_remote_file()
- Check if target files are on the local file systemany_remote_file()
- Checks if target files are on remote file systemsany_symlinks()
- Checks if any targets are actually symbolic linkscheck_fspec()
- Checks for new data from target (in forever loops)check_output_alive()
- Checks if the target is still valid (in forever loops)close_fd()
- Close the file descriptor with the associated namedump_remainder()
- Reads and counts input, possibly bufferingfile_lines()
- Output the last lines of a buffered filefremote()
- Tests if a file descriptor is on a remote system withfstatfs()
, if possibleignore_fifo_and_pipe()
- Flags fifo and pipe streams to ignoreparse_obsolete_option()
- Manual parsing of legacy optionsparse_option()
- Performs option parsing with getoptpipe_bytes()
- Prints final buffered characters from a pipe sourcepipe_lines()
- Prints final buffered lines from a pipe sourcepretty_name()
- Prints source name, which may be 'standard input'recheck()
- Tests File_spec and underlying file for changesrecord_open_fd()
- Fills out File_spec information fromstat()
start_bytes()
- Skip a number of bytes from the start of a pipestart_lines()
- Skips a number of lines from the start of a file or pipetail()
- Performs the tail procedure on an open file descriptortail_bytes()
- Tail procedure based on byte counttail_file()
- Opens a single file and performs tail proceduretail_forever()
- Procedure to tail files forever (-f)tail_forever_inotify()
- Procedure to tail files forever (-f)tail_lines()
- Tail procedure based on line counttailable_stdin()
- Check if user requested tail on STDINvalid_file_spec()
- Checks that a file descriptor is valid and without errorswd_comparator()
- Comparison function for the watch descriptor (inotify)wd_hasher()
- Hash function for the watch descriptor (inotify)write_header()
- Prints the name of the new file on changexlseek()
- Attempts tolseek()
on a filexwrite_stdout()
- Writes bytes from a buffer to STDOUT
die()
- Exit with mandatory non-zero error and message to stderrerror()
- Outputs error message to standard error with possible process termination
Setup
tail defines a structure, File_spec
that contains important data about the current file being processed
tail also keeps several flags and variables as globals, including:
count_lines
- Flag set if we're counting lines (not bytes) (-n)disable_inotify
- Flag to disable inotifyfollow_mode
- Determines if we follow names or descriptorsforever
- Flag set if we're looping forever (-F, -f)from_start
- Flag set if output from the start of a fileline_end
- The end of line character, \n or \0monitor_output
- Flag to end processing if pipe closespid
- The user-provided pid to associate with outputpresume_input_pipe
- Flag set if the presume pipe undocumented feature is onprint_headers
- Flag set to print file name headersrepoen_inaccessible_files
- Flag set if we attempt to reopen closed files (-F)
main()
introduces a few local variables:
*F
- The File_spec struct array for the input files**file
- The list of file names as provided by the userheader_mode
- The current header modei
- Generic iterator used in several ways (usually file number)n_files
- The number of files providedn_units
- The number of lines/bytes to process with tailobsolete_options
- Flag if obsolete options were processedok
- The final return status
Parsing
Parsing answers the following questions to define the execution parameters
- Are we counting lines or bytes? And how many?
- Are we following a file descriptor for more output? Is there a wait time?
- Should we display headers?
Parsing failures
These warning and failure cases are explicitly checked:
- Invalid lines/bytes number provided
- Invalid number of seconds provided
- Trying to retry without following (warning)
- Tracking a PID without following (warning)
- Unknown option used
Parsing failures result in a short error message followed by the usage instructions. Warnings may allow processing to continue
Execution
tail execution is more complex that the diagram above suggests. There are two general strategies depending on if the file source is seekable. This is simplified if we're skipping a fixed start size, or if we need to see the end first. Finally, the read/poll forever case also defines a separate execution path.
- It's possible to immediately exit successfully if there's no work to do (0 lines/bytes from end)
- If there is work, start by allocating a File_spec for each target file
- Open the next file
- Sets the File_spec data based on both the file and user options
- Printer headers for the new file (if multiple)
- If this file source is seekable, seek to the end and scan backwards until enough data is found.
- If the file source is not seekable, create a linebuffer linked list and continue to read until EOF
- The 'output-forever case' is distinct from the seekable cases (not pictured above)
- In all cases, close the file and move to the next as needed
Failure cases:
- Unable to
stat()
file streams - Unable to read from input stream
- Unable to write to standard out
- Failure to
fcntl()
to nonblocking mode - Clock failure when trying to wait/sleep
- Watch failures with inotify
All failures at this stage output an error message to STDERR and return without displaying usage help