#!/usr/bin/python
# Copyright 2012 Google Inc. All Rights Reserved.

"""Antirollback clock user space support.

This daemon serves several purposes:
  1. Maintain a file containing the minimum time, and periodically
     update its value.
  2. At startup, write the minimum time to /proc/ar_clock.
     The kernel will not allow the time to be set substantially
     earlier than this value (there is a small amount of wiggle
     room).
"""

__author__ = 'dgentry@google.com (Denton Gentry)'

import os
import pwd
import sys
import tempfile
import time
import options


optspec = """
antirollback [options...]
--
i,interval=   seconds between updates [28800]
p,persist=    path to persistent file [/fiber/config/ar_clock]
u,user=       setuid to this user to run
"""


# Unit tests can override these.
BIRTHDAY = 1349064000.0  # 10/1/2012
BUILD_FILENAME = '/etc/softwaredate'
PROC_AR = '/proc/ar_clock'
PROC_UPTIME = '/proc/uptime'
SLEEP = time.sleep
TIMENOW = time.time


def GetPersistTime(ar_filename):
  """Return time stored in ar_filename, or 0.0 if it does not exist."""
  try:
    with open(ar_filename) as f:
      return float(f.read())
  except (IOError, ValueError):
    return 0.0


def GetBuildDate(build_filename):
  """Return build_date in floating point seconds since epoch."""
  try:
    with open(build_filename) as f:
      return float(f.readline())
  except (IOError, ValueError):
    return 0.0


def GetMonotime():
  """Return a monotonically increasing count of seconds."""
  return float(open(PROC_UPTIME).read().split()[0])


def GetAntirollbackTime(ar_filename):
  """Return the appropriate antirollback time to use at startup."""
  now = max(TIMENOW(), GetPersistTime(ar_filename),
            GetBuildDate(BUILD_FILENAME), BIRTHDAY)
  return now


def StoreAntirollback(now, ar_filename, kern_f):
  """Write time to /proc/ar_clock and the persistent file."""
  print 'antirollback time now ' + str(now)
  sys.stdout.flush()
  kern_f.write(str(now))
  kern_f.flush()
  tmpdir = os.path.dirname(ar_filename)
  with tempfile.NamedTemporaryFile(mode='w', dir=tmpdir, delete=False) as f:
    f.write(str(now) + '\n')
    f.flush()
    os.fsync(f.fileno())
    os.rename(f.name, ar_filename)


def LoopIterate(uptime, now, sleeptime, ar_filename, kern_f):
  SLEEP(sleeptime)
  new_uptime = GetMonotime()
  now += (new_uptime - uptime)
  uptime = new_uptime
  now = max(now, TIMENOW())
  StoreAntirollback(now=now, ar_filename=ar_filename, kern_f=kern_f)
  return (uptime, now)


def main():
  o = options.Options(optspec)
  (opt, _, _) = o.parse(sys.argv[1:])

  kern_f = open(PROC_AR, 'w')

  # Drop privileges
  if opt.user:
    pd = pwd.getpwnam(opt.user)
    os.setuid(pd.pw_uid)

  uptime = GetMonotime()
  now = GetAntirollbackTime(opt.persist)

  StoreAntirollback(now=now, ar_filename=opt.persist, kern_f=kern_f)

  while True:
    (uptime, now) = LoopIterate(uptime=uptime, now=now,
                                sleeptime=opt.interval,
                                ar_filename=opt.persist,
                                kern_f=kern_f)


if __name__ == '__main__':
  main()
