#
#

# Copyright (C) 2011 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""Script to ensure permissions on files/dirs are accurate.

"""

import os
import os.path
import optparse
import sys
import logging

from ganeti import constants
from ganeti import errors
from ganeti import runtime
from ganeti import ssconf
from ganeti import utils
from ganeti import cli
from ganeti import pathutils
from ganeti import compat


(DIR,
 FILE,
 QUEUE_DIR) = range(1, 4)

ALL_TYPES = compat.UniqueFrozenset([
  DIR,
  FILE,
  QUEUE_DIR,
  ])


def RecursiveEnsure(path, uid, gid, dir_perm, file_perm):
  """Ensures permissions recursively down a directory.

  This functions walks the path and sets permissions accordingly.

  @param path: The absolute path to walk
  @param uid: The uid used as owner
  @param gid: The gid used as group
  @param dir_perm: The permission bits set for directories
  @param file_perm: The permission bits set for files

  """
  assert os.path.isabs(path), "Path %s is not absolute" % path
  assert os.path.isdir(path), "Path %s is not a dir" % path

  logging.debug("Recursively processing %s", path)

  for root, dirs, files in os.walk(path):
    for subdir in dirs:
      utils.EnforcePermission(os.path.join(root, subdir), dir_perm, uid=uid,
                              gid=gid)

    for filename in files:
      utils.EnforcePermission(os.path.join(root, filename), file_perm, uid=uid,
                              gid=gid)


def EnsureQueueDir(path, mode, uid, gid):
  """Sets the correct permissions on all job files in the queue.

  @param path: Directory path
  @param mode: Wanted file mode
  @param uid: Wanted user ID
  @param gid: Wanted group ID

  """
  for filename in utils.ListVisibleFiles(path):
    if constants.JOB_FILE_RE.match(filename):
      utils.EnforcePermission(utils.PathJoin(path, filename), mode, uid=uid,
                              gid=gid)


def ProcessPath(path):
  """Processes a path component.

  @param path: A tuple of the path component to process

  """
  (pathname, pathtype, mode, uid, gid) = path[0:5]

  assert pathtype in ALL_TYPES

  if pathtype in (DIR, QUEUE_DIR):
    # No additional parameters
    assert len(path) == 5
    if pathtype == DIR:
      utils.MakeDirWithPerm(pathname, mode, uid, gid)
    elif pathtype == QUEUE_DIR:
      EnsureQueueDir(pathname, mode, uid, gid)
  elif pathtype == FILE:
    (must_exist, ) = path[5:]
    utils.EnforcePermission(pathname, mode, uid=uid, gid=gid,
                            must_exist=must_exist)


def GetPaths():
  """Returns a tuple of path objects to process.

  """
  getent = runtime.GetEnts()
  masterd_log = constants.DAEMONS_LOGFILES[constants.MASTERD]
  noded_log = constants.DAEMONS_LOGFILES[constants.NODED]
  confd_log = constants.DAEMONS_LOGFILES[constants.CONFD]
  luxid_log = constants.DAEMONS_LOGFILES[constants.LUXID]
  rapi_log = constants.DAEMONS_LOGFILES[constants.RAPI]
  mond_log = constants.DAEMONS_LOGFILES[constants.MOND]

  rapi_dir = os.path.join(pathutils.DATA_DIR, "rapi")
  cleaner_log_dir = os.path.join(pathutils.LOG_DIR, "cleaner")
  master_cleaner_log_dir = os.path.join(pathutils.LOG_DIR, "master-cleaner")

  # A note on the ordering: The parent directory (type C{DIR}) must always be
  # listed before files (type C{FILE}) in that directory. Once the directory is
  # set, only files directly in that directory can be listed.
  paths = [
    (pathutils.DATA_DIR, DIR, 0o755, getent.masterd_uid, getent.masterd_gid),
    (pathutils.CLUSTER_DOMAIN_SECRET_FILE, FILE, 0o640,
     getent.masterd_uid, getent.masterd_gid, False),
    (pathutils.CLUSTER_CONF_FILE, FILE, 0o640,
     getent.masterd_uid, getent.confd_gid, False),
    (pathutils.CONFD_HMAC_KEY, FILE, 0o440,
     getent.confd_uid, getent.masterd_gid, False),
    (pathutils.SSH_KNOWN_HOSTS_FILE, FILE, 0o644,
     getent.masterd_uid, getent.masterd_gid, False),
    (pathutils.RAPI_CERT_FILE, FILE, 0o440,
     getent.rapi_uid, getent.masterd_gid, False),
    (pathutils.SPICE_CERT_FILE, FILE, 0o440,
     getent.noded_uid, getent.masterd_gid, False),
    (pathutils.SPICE_CACERT_FILE, FILE, 0o440,
     getent.noded_uid, getent.masterd_gid, False),
    (pathutils.NODED_CERT_FILE, FILE, pathutils.NODED_CERT_MODE,
     getent.masterd_uid, getent.masterd_gid, False),
    (pathutils.NODED_CLIENT_CERT_FILE, FILE, pathutils.NODED_CERT_MODE,
     getent.masterd_uid, getent.masterd_gid, False),
    (pathutils.WATCHER_PAUSEFILE, FILE, 0o644,
     getent.masterd_uid, getent.masterd_gid, False),
    ]

  ss = ssconf.SimpleStore()
  for ss_path in ss.GetFileList():
    paths.append((ss_path, FILE, constants.SS_FILE_PERMS,
                  getent.noded_uid, getent.noded_gid, False))

  paths.extend([
    (pathutils.QUEUE_DIR, DIR, 0o750, getent.masterd_uid, getent.daemons_gid),
    (pathutils.QUEUE_DIR, QUEUE_DIR, constants.JOB_QUEUE_FILES_PERMS,
     getent.masterd_uid, getent.daemons_gid),
    (pathutils.JOB_QUEUE_DRAIN_FILE, FILE, 0o644,
     getent.masterd_uid, getent.daemons_gid, False),
    (pathutils.JOB_QUEUE_LOCK_FILE, FILE, constants.JOB_QUEUE_FILES_PERMS,
     getent.masterd_uid, getent.daemons_gid, False),
    (pathutils.JOB_QUEUE_SERIAL_FILE, FILE, constants.JOB_QUEUE_FILES_PERMS,
     getent.masterd_uid, getent.daemons_gid, False),
    (pathutils.JOB_QUEUE_VERSION_FILE, FILE, constants.JOB_QUEUE_FILES_PERMS,
     getent.masterd_uid, getent.daemons_gid, False),
    (pathutils.JOB_QUEUE_ARCHIVE_DIR, DIR, 0o750,
     getent.masterd_uid, getent.daemons_gid),
    (rapi_dir, DIR, 0o750, getent.rapi_uid, getent.masterd_gid),
    (pathutils.RAPI_USERS_FILE, FILE, 0o640,
     getent.rapi_uid, getent.masterd_gid, False),
    (pathutils.RUN_DIR, DIR, 0o775, getent.masterd_uid, getent.daemons_gid),
    (pathutils.SOCKET_DIR, DIR, 0o770, getent.masterd_uid, getent.daemons_gid),
    (pathutils.MASTER_SOCKET, FILE, 0o660,
     getent.masterd_uid, getent.daemons_gid, False),
    (pathutils.QUERY_SOCKET, FILE, 0o660,
     getent.luxid_uid, getent.daemons_gid, False),
    (pathutils.BDEV_CACHE_DIR, DIR, 0o755,
     getent.noded_uid, getent.masterd_gid),
    (pathutils.UIDPOOL_LOCKDIR, DIR, 0o750,
     getent.noded_uid, getent.masterd_gid),
    (pathutils.DISK_LINKS_DIR, DIR, 0o755,
     getent.noded_uid, getent.masterd_gid),
    (pathutils.CRYPTO_KEYS_DIR, DIR, 0o700,
     getent.noded_uid, getent.masterd_gid),
    (pathutils.IMPORT_EXPORT_DIR, DIR, 0o755,
     getent.noded_uid, getent.masterd_gid),
    (pathutils.LOG_DIR, DIR, 0o770, getent.masterd_uid, getent.daemons_gid),
    (masterd_log, FILE, 0o600, getent.masterd_uid, getent.masterd_gid, False),
    (confd_log, FILE, 0o600, getent.confd_uid, getent.masterd_gid, False),
    (luxid_log, FILE, 0o600, getent.luxid_uid, getent.masterd_gid, False),
    (noded_log, FILE, 0o600, getent.noded_uid, getent.masterd_gid, False),
    (rapi_log, FILE, 0o600, getent.rapi_uid, getent.masterd_gid, False),
    (mond_log, FILE, 0o600, getent.mond_uid, getent.masterd_gid, False),
    (pathutils.LOG_OS_DIR, DIR, 0o750, getent.noded_uid, getent.daemons_gid),
    (pathutils.LOG_XEN_DIR, DIR, 0o750, getent.noded_uid, getent.daemons_gid),
    (pathutils.LOG_KVM_DIR, DIR, 0o750, getent.noded_uid, getent.daemons_gid),
    (cleaner_log_dir, DIR, 0o750, getent.noded_uid, getent.noded_gid),
    (master_cleaner_log_dir, DIR, 0o750,
     getent.masterd_uid, getent.masterd_gid),
    (pathutils.INSTANCE_REASON_DIR, DIR, 0o755, getent.noded_uid,
     getent.noded_gid),
    (pathutils.LIVELOCK_DIR, DIR, 0o750,
     getent.masterd_uid, getent.daemons_gid),
    (pathutils.LUXID_MESSAGE_DIR, DIR, 0o750, getent.masterd_uid,
     getent.daemons_gid),
    ])

  return paths


def ParseOptions():
  """Parses the options passed to the program.

  @return: Options and arguments

  """
  program = os.path.basename(sys.argv[0])

  parser = optparse.OptionParser(usage="%prog [--full-run]",
                                 prog=program)
  parser.add_option(cli.DEBUG_OPT)
  parser.add_option(cli.VERBOSE_OPT)
  parser.add_option("--full-run", "-f", dest="full_run", action="store_true",
                    default=False, help=("Make a full run and set permissions"
                                         " on archived jobs (time consuming)"))

  return parser.parse_args()


def Main():
  """Main routine.

  """
  (opts, args) = ParseOptions()

  utils.SetupToolLogging(opts.debug, opts.verbose)

  if args:
    logging.error("No arguments are expected")
    return constants.EXIT_FAILURE

  if opts.full_run:
    logging.info("Running in full mode")

  getent = runtime.GetEnts()

  try:
    for path in GetPaths():
      ProcessPath(path)

    if opts.full_run:
      RecursiveEnsure(pathutils.JOB_QUEUE_ARCHIVE_DIR, getent.masterd_uid,
                      getent.daemons_gid, 0o750,
                      constants.JOB_QUEUE_FILES_PERMS)
  except errors.GenericError as err:
    logging.error("An error occurred while setting permissions: %s", err)
    return constants.EXIT_FAILURE

  return constants.EXIT_SUCCESS
