blob: 394f6cfef3dff45614e12f59822f78e319240a8a [file] [log] [blame]
Paul Hobbsbe84dd12016-07-01 11:54:37 -07001#!/usr/bin/python2
2
3# Copyright 2016 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Tails a file, and quits when inotify detects that it has been closed."""
8
9from __future__ import print_function
10
11import argparse
12import select
13import subprocess
14import sys
15import time
16
17
18def TailFile(path, sleep_interval, chunk_size,
19 outfile=sys.stdout,
20 seek_to_end=True):
21 """Tails a file, and quits when there are no writers on the file.
22
23 Args:
24 path: The path to the file to open
25 sleep_interval: The amount to sleep in between reads to reduce wasted IO
26 chunk_size: The amount of bytes to read in between print() calls
27 outfile: A file handle to write to. Defaults to sys.stdout
28 seek_to_end: Whether to start at the end of the file at |path| when reading.
29 """
30
31 writer_closed = subprocess.Popen(['inotifywait', '-qe', 'close_write', path],
32 stdout=subprocess.PIPE)
33
34 # stdout.read is blocking, so use select.select to detect if input is
35 # available.
36 def WriterClosedFile():
37 read_list, _, _ = select.select([writer_closed.stdout], [], [], 0)
38 return bool(read_list)
39
40 def ReadChunks(fh):
41 for chunk in iter(lambda: fh.read(chunk_size), b''):
42 print(chunk, end='', file=outfile)
43
44 with open(path) as fh:
45 if seek_to_end == True:
46 fh.seek(0, 2)
47 while True:
48 ReadChunks(fh)
49 if WriterClosedFile():
50 # We need to read the chunks again to avoid a race condition where the
51 # writer finishes writing some output in between the ReadChunks() and
52 # the WriterClosedFile() call.
53 ReadChunks(fh)
54 break
55
56 # Sleep a bit to limit the number of wasted reads.
57 time.sleep(sleep_interval)
58
59 writer_closed.kill()
60
61
62def main():
63 p = argparse.ArgumentParser(description=__doc__)
64 p.add_argument('file', help='The file to tail')
65 p.add_argument('--sleep_interval', type=float, default=0.1,
66 help='Time sleeping between file reads')
67 p.add_argument('--chunk_size', type=int, default=64 * 2**10,
68 help='Bytes to read before yielding')
69 p.add_argument('--from_beginning', action='store_true',
70 help='If given, read from the beginning of the file.')
71 args = p.parse_args()
72
73 TailFile(args.file, args.sleep_interval, args.chunk_size,
74 seek_to_end=not args.from_beginning)
75
76
77if __name__ == '__main__':
78 main()